This is a quick start with slog4s on the JVM with logback backend. It doesn’t mean we are limited to JVM or logback! Au contraire!
Installation
Add new library dependencies to your build.sbt
libraryDependencies ++= Seq("com.avast" %% "slog4s-api" % "0.6.1+7-cb5b3c2d-SNAPSHOT",
"com.avast" %% "slog4s-slf4j" % "0.6.1+7-cb5b3c2d-SNAPSHOT")
We will be using logback with logstash encoder. So make sure it’s in your dependency list and it’s configured properly.
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="net.logstash.logback.encoder.LogstashEncoder" />
</appender>
<root level="INFO">
<appender-ref ref="STDOUT" />
</root>
</configuration>
Usage
Obligatory imports before we start:
import cats.effect._
import cats.syntax.all._
import slog4s._
import slog4s.slf4j._
Let’s start by creating a LoggerFactory
that is backed by slf4j (and logback under the hood). With that, we
can create a named Logger
.
val loggerFactory = Slf4jFactory[IO].withoutContext.loggerFactory
// loggerFactory: LoggerFactory[IO] = slog4s.slf4j.Slf4jFactory$WithoutContextBuilder$$anon$3@42e4d8c
val logger = loggerFactory.make("test-logger")
// logger: Logger[IO] = slog4s.slf4j.Slf4jLogger@6b182cdf
It’s finally time to log something!
logger.info("Hello world!").unsafeRunSync()
Output:
{
"@timestamp" : "2023-02-24T17:57:19.522+01:00",
"@version" : "1",
"correlation_id" : "generated-correlation-id",
"file" : "contextual-logging.md",
"level" : "INFO",
"level_value" : 20000,
"line" : 37,
"logger_name" : "bar",
"message" : "Hellow from bar!",
"thread_name" : "Thread-18"
}
That was pretty boring, except file
and line
attributes that denote location of the log message within a file.
We can also provide an exception:
logger.error(new Exception("Boom!"), "Something went horribly wrong.").unsafeRunSync()
Output:
{
"@timestamp" : "2023-02-24T17:57:19.931+01:00",
"@version" : "1",
"file" : "index.md",
"level" : "INFO",
"level_value" : 20000,
"line" : 41,
"logger_name" : "test-logger",
"message" : "Hello world!",
"thread_name" : "Thread-19"
}
Let’s make our message more content rich with additional arguments:
logger.info
.withArg("string_value", "<VALUE>")
.withArg("bool_value", true)
.withArg("list_value", List(1,2,3))
.log("Message with arguments")
.unsafeRunSync()
Output:
{
"@timestamp" : "2023-02-24T17:57:19.934+01:00",
"@version" : "1",
"file" : "index.md",
"level" : "ERROR",
"level_value" : 40000,
"line" : 47,
"logger_name" : "test-logger",
"message" : "Something went horribly wrong.",
"stack_trace" : "java.lang.Exception: Boom!\n\tat repl.MdocSession$MdocApp.<init>(index.md:47)\n\tat repl.MdocSession$.app(index.md:3)\n\tat mdoc.internal.document.DocumentBuilder$$doc$.$anonfun$build$2(DocumentBuilder.scala:89)\n\tat scala.runtime.java8.JFunction0$mcV$sp.apply(JFunction0$mcV$sp.java:23)\n\tat scala.util.DynamicVariable.withValue(DynamicVariable.scala:62)\n\tat scala.Console$.withErr(Console.scala:196)\n\tat mdoc.internal.document.DocumentBuilder$$doc$.$anonfun$build$1(DocumentBuilder.scala:89)\n\tat scala.runtime.java8.JFunction0$mcV$sp.apply(JFunction0$mcV$sp.java:23)\n\tat scala.util.DynamicVariable.withValue(DynamicVariable.scala:62)\n\tat scala.Console$.withOut(Console.scala:167)\n\tat mdoc.internal.document.DocumentBuilder$$doc$.build(DocumentBuilder.scala:88)\n\tat mdoc.internal.markdown.MarkdownBuilder$.$anonfun$buildDocument$2(MarkdownBuilder.scala:47)\n\tat mdoc.internal.markdown.MarkdownBuilder$$anon$1.run(MarkdownBuilder.scala:104)\n",
"thread_name" : "Thread-19"
}
Additional arguments are type safe. Following code will fail in compile time:
class That(value: String)
logger.info
.withArg("that_value", new That("value"))
.log("Does not compile")
.unsafeRunSync()
// error: could not find implicit value for evidence parameter of type slog4s.LogEncoder[repl.MdocSession.MdocApp.That]
// logger.info
// ^
However there is built-in support for case class
es and sealed trait
s provided by generic
module.