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 classes and sealed traits provided by generic module.