Skip to content
Open

PR #2

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
feab227
move to sub-project
sideeffffect Nov 18, 2019
d3f4c13
monix
sideeffffect Nov 18, 2019
9499276
clean up ZIO
sideeffffect Nov 18, 2019
f57b49c
logstage
sideeffffect Nov 18, 2019
0ade317
gson
sideeffffect Nov 18, 2019
6ac8d8c
jackson
sideeffffect Nov 18, 2019
04a4c03
cats-mtl ApplicativeLocal based solution
sideeffffect Nov 27, 2019
7302efd
add README.md
sideeffffect Nov 28, 2019
3b3e757
Merge pull request #1 from sideeffffect/readme
sideeffffect Nov 28, 2019
fdf33a3
README.md: other useful metadata in logs
sideeffffect Nov 28, 2019
b1cf19d
keys
sideeffffect Dec 2, 2019
2e92b3c
MTL builder, basic macro working
sideeffffect Dec 6, 2019
a9a3d55
MTL builder, LoggerInfo implemented
sideeffffect Dec 6, 2019
4b05bd8
purge, leave only MTL builder
sideeffffect Dec 6, 2019
161ccce
Contexter
sideeffffect Dec 9, 2019
04638a1
split Contexter and Logger
sideeffffect Dec 10, 2019
7982815
Merge pull request #3 from sideeffffect/contexter
sideeffffect Dec 11, 2019
1c7ca9f
memoization
sideeffffect Dec 11, 2019
a9c6959
update README
sideeffffect Dec 11, 2019
072fa6a
tmpContext -> localContext, use Async.memoize where appropriate (inst…
sideeffffect Dec 12, 2019
15434db
JSON keys README
sideeffffect Dec 17, 2019
70598ea
extract common macro part
sideeffffect Dec 17, 2019
8f1cccb
use Jackson
sideeffffect Dec 17, 2019
0b87960
remove circe dependency
sideeffffect Dec 18, 2019
efd45ef
customizable `toJson: Any => String`
sideeffffect Dec 18, 2019
dfafd74
withArg with toJson
sideeffffect Dec 20, 2019
9acd848
Merge pull request #4 from sideeffffect/jackson
sideeffffect Dec 20, 2019
1d8c997
separate API and implementation
sideeffffect Dec 20, 2019
0a7ce9c
add warn
sideeffffect Dec 20, 2019
b2dbe6c
update README.md
sideeffffect Dec 20, 2019
6dafc58
Update README.md
sideeffffect Dec 20, 2019
b1510b5
Update README.md
sideeffffect Dec 20, 2019
9e0894f
rename ContextManager -> LoggingContext
sideeffffect Jan 7, 2020
9592c8a
fix withArg and local toJson
sideeffffect Jan 7, 2020
6e9cb16
update README
sideeffffect Jan 7, 2020
5832e42
Jackson for Scala
sideeffffect Jan 8, 2020
fb95dde
monix logger
sideeffffect Jan 10, 2020
a83101f
Introducing LogEncoder (#5)
jveselka Jan 13, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
target
/log
target
/log
.idea/
2 changes: 2 additions & 0 deletions .scalafmt.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
version=2.3.2
trailingCommas = always
195 changes: 195 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
# Structured logging framework 4 Cats

This repo contains experiments with possible implementations of structured logging with `cats-effect`.

## Goals

* log commands are side-effecting programs, based on `cats-effect`
* objects provided to log commands should appear as JSON in logs
* they're searchable in Kibana
* we won't duplicate the objects in message itself to reduce size
* logs will contain appropriate context
* the context can be programmatically augmented
* works in a stack-like manner, including shadowing
* works well with `cats-effect` and related libraries (is not bound to `ThreadLocal`/fat JVM thread like slf4j's MDC)
* other useful metadata in logs
* timestamp
* file name
* line number
* loglevel as both string and number
* ... to be specified
* JSON keys will be just strings, at lest for the beginning

## Features

* thin wrapper over `slf4j`
* each logging statement is inlined on the same line using macros
* rest of the logging pipeline works as expected
* JSON logging
* uses `logstash-logback-encoder` for JSON log formatting
* uses Jackson for the encoding of log parameters by default (imported by Logback Logstash encoder anyway; can be overridden if necessary)
* CPU expensive or side-effectful actions can be passed to logs
* will be memoized == computed only once or never at all, if log level too low

## Interface

```scala
trait LoggingContext[F[_]] {
type Self <: LoggingContext[F]
def withArg[A](name: String,
value: => A,
toJson: Option[A => String] = None): Self
def withComputed[A](name: String,
value: F[A],
toJson: Option[A => String] = None): Self
def withArgs[A](map: Map[String, A], toJson: Option[A => String] = None): Self
def use[A](inner: F[A]): F[A]
}

trait Logger[F[_]] {
type Self <: Logger[F]
def withArg[A](name: String,
value: => A,
toJson: Option[A => String] = None): Self
def withComputed[A](name: String,
value: F[A],
toJson: Option[A => String] = None): Self
def withArgs[A](map: Map[String, A], toJson: Option[A => String] = None): Self
// excerpt for `info` logging
def info: LoggerInfo[F]
//...
}

trait ContextLogger[F[_]] extends LoggingContext[F] with Logger[F] {
type Self <: ContextLogger[F]
}
class LoggerInfo[F[_]]() {
def apply(message: String): F[Unit] = macro ???
def apply(message: String, throwable: Throwable): F[Unit] = macro ???
}
```

## Usage

```scala
def program(logger: ContextLogger[Task]): Task[Unit] = {
val ex = new InvalidParameterException("BOOOOOM")
for {
_ <- logger
.withArg("a", A(1, "x", Array[Byte](127)))
.withArg("o", o)
.info("Hello Monix")
_ <- logger.warn("Hello MTL", ex)
_ <- logger.withArg("x", 123).withArg("o", o).use {
logger.withArg("x", 9).info("Hello2 meow")
}
} yield ()
}
```

### Multiple contexts
```scala
_ <- logger
.withArg("a", A(1, "x", Array[Byte](127)))
.withArg("o", o)
.info("Hello Monix")
```
```json
{
"@timestamp": "2020-01-08T02:31:16.503+01:00",
"@version": "1",
"message": "Hello Monix",
"logger_name": "slf4cats.example.Main$",
"thread_name": "scala-execution-context-global-13",
"level": "INFO",
"level_value": 20000,
"a": {
"x": 1,
"y": "x",
"bytes": "fw=="
},
"o": {
"a": {
"x": 123,
"y": "Hello",
"bytes": "AQID"
},
"b": [
false,
true
],
"c": {
"r": 456
}
},
"application": "loggingexperiment",
"caller_class_name": "slf4cats.example.Main$",
"caller_method_name": "$anonfun$program$5",
"caller_file_name": "Main.scala",
"caller_line_number": 66
}
```

### Logging exception
```scala
_ <- logger.warn("Hello MTL", ex)
```
```json
{
"@timestamp": "2020-01-08T02:31:16.520+01:00",
"@version": "1",
"message": "Hello MTL",
"logger_name": "slf4cats.example.Main$",
"thread_name": "scala-execution-context-global-13",
"level": "WARN",
"level_value": 30000,
"stack_trace": "java.security.InvalidParameterException: BOOOOOM\n\tat slf4cats.example.Main$.program(Main.scala:61)\n...",
"application": "loggingexperiment",
"caller_class_name": "slf4cats.example.Main$",
"caller_method_name": "$anonfun$program$9",
"caller_file_name": "Main.scala",
"caller_line_number": 67
}
```

### Context overriding
```scala
_ <- logger.withArg("x", 123).withArg("o", o).use {
logger.withArg("x", 9).info("Hello2 meow")
}
```
```json
{
"@timestamp": "2020-01-08T02:31:16.539+01:00",
"@version": "1",
"message": "Hello2 meow",
"logger_name": "slf4cats.example.Main$",
"thread_name": "scala-execution-context-global-13",
"level": "INFO",
"level_value": 20000,
"x": [
1,
2,
3
],
"o": {
"a": {
"x": 123,
"y": "Hello",
"bytes": "AQID"
},
"b": [
false,
true
],
"c": {
"r": 456
}
},
"application": "loggingexperiment",
"caller_class_name": "slf4cats.example.Main$",
"caller_method_name": "$anonfun$program$16",
"caller_file_name": "Main.scala",
"caller_line_number": 69
}
```
100 changes: 89 additions & 11 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,92 @@ version := "0.1"

scalaVersion := "2.12.10"

val circeVersion = "0.11.1"

libraryDependencies ++= Seq(
"org.slf4j" % "slf4j-api" % "1.7.29",
"ch.qos.logback" % "logback-classic" % "1.2.3",
"net.logstash.logback" % "logstash-logback-encoder" % "6.2",
"io.circe" %% "circe-core" % circeVersion,
"io.circe" %% "circe-generic" % circeVersion,
// "org.typelevel" %% "cats-effect" % "2.0.0",
"dev.zio" %% "zio" % "1.0.0-RC16",
)
lazy val Version = new {
val slf4j = "1.7.29"
val logback = "1.2.3"
val jacksonScala = "2.10.2"
val logstashLogback = "6.2"
val monix = "3.1.0"
val catsMtl = "0.7.0"
val catsEffect = "2.0.0"
val meowMtl = "0.4.0"
val circe = "0.13.0-M2"
}

lazy val root = project
.in(file("."))
.settings(
name := "slf4cats",
publish / skip := true, // doesn't publish ivy XML files, in contrast to "publishArtifact := false"
)
.aggregate(
slf4catsApi,
slf4catsImpl,
slf4catsCirce,
slf4catsExample,
)

lazy val slf4catsApi = project
.in(file("slf4cats-api"))
.settings(
name := "slf4cats-api",
libraryDependencies ++= Seq(
"org.slf4j" % "slf4j-api" % Version.slf4j,
"org.typelevel" %% "cats-effect" % Version.catsEffect,
"org.scala-lang" % "scala-reflect" % scalaVersion.value,
),
)

lazy val slf4catsImpl = project
.in(file("slf4cats-impl"))
.settings(
name := "slf4cats-impl",
libraryDependencies ++= Seq(
"net.logstash.logback" % "logstash-logback-encoder" % Version.logstashLogback,
"org.typelevel" %% "cats-mtl-core" % Version.catsMtl,
"org.typelevel" %% "cats-effect" % Version.catsEffect,
),
)
.dependsOn(slf4catsApi)

lazy val slf4catsMonix = project
.in(file("slf4cats-monix"))
.settings(
name := "slf4cats-monix",
libraryDependencies ++= Seq(
"io.monix" %% "monix" % Version.monix,
"com.olegpy" %% "meow-mtl-monix" % Version.meowMtl,
),
)
.dependsOn(slf4catsImpl)

lazy val slf4catsCirce = project
.in(file("slf4cats-circe"))
.settings(
name := "slf4cats-circe",
libraryDependencies ++= Seq(
"io.circe" %% "circe-core" % Version.circe,
),
)
.dependsOn(slf4catsApi)

lazy val slf4catsJackson = project
.in(file("slf4cats-jackson"))
.settings(
name := "slf4cats-jackson",
libraryDependencies ++= Seq(
"com.fasterxml.jackson.module" %% "jackson-module-scala" % Version.jacksonScala,
),
)
.dependsOn(slf4catsApi)

lazy val slf4catsExample = project
.in(file("slf4cats-example"))
.settings(
name := "slf4cats-example",
libraryDependencies ++= Seq(
"ch.qos.logback" % "logback-classic" % Version.logback,
"io.circe" %% "circe-generic" % Version.circe,
),
)
.dependsOn(slf4catsMonix, slf4catsCirce, slf4catsJackson)
1 change: 1 addition & 0 deletions project/plugins.sbt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
addSbtPlugin("io.github.davidgregory084" % "sbt-tpolecat" % "0.1.10")
Loading