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.0", 
                            "com.avast" %% "slog4s-slf4j" % "0.6.0")

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@bce156e
val logger = loggerFactory.make("test-logger")
// logger: Logger[IO] = slog4s.slf4j.Slf4jLogger@1859b77e

It’s finally time to log something!

logger.info("Hello world!").unsafeRunSync()

Output:

{
  "@timestamp" : "2020-11-21T09:22:20.382Z",
  "@version" : "1",
  "file" : "index.md",
  "level" : "INFO",
  "level_value" : 20000,
  "line" : 41,
  "logger_name" : "test-logger",
  "message" : "Hello world!",
  "thread_name" : "run-main-0"
}

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" : "2020-11-21T09:22:20.387Z",
  "@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.Session$App.<init>(index.md:47)\n\tat repl.Session$.app(index.md:3)\n\tat mdoc.internal.document.DocumentBuilder$$doc$.$anonfun$build$2(DocumentBuilder.scala:82)\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:82)\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:81)\n\tat mdoc.internal.markdown.MarkdownCompiler$.buildDocument(MarkdownCompiler.scala:53)\n\tat mdoc.internal.markdown.Processor.processScalaInputs(Processor.scala:133)\n\tat mdoc.internal.markdown.Processor.processDocument(Processor.scala:48)\n\tat mdoc.internal.markdown.Markdown$.toMarkdown(Markdown.scala:118)\n\tat mdoc.internal.cli.MainOps.handleMarkdown(MainOps.scala:62)\n\tat mdoc.internal.cli.MainOps.handleFile(MainOps.scala:90)\n\tat mdoc.internal.cli.MainOps.$anonfun$generateCompleteSite$1(MainOps.scala:130)\n\tat scala.collection.LinearSeqOptimized.foldLeft(LinearSeqOptimized.scala:126)\n\tat scala.collection.LinearSeqOptimized.foldLeft$(LinearSeqOptimized.scala:122)\n\tat scala.collection.immutable.List.foldLeft(List.scala:89)\n\tat mdoc.internal.cli.MainOps.generateCompleteSite(MainOps.scala:128)\n\tat mdoc.internal.cli.MainOps.run(MainOps.scala:149)\n\tat mdoc.internal.cli.MainOps$.process(MainOps.scala:233)\n\tat mdoc.Main$.process(Main.scala:26)\n\tat mdoc.Main$.process(Main.scala:21)\n\tat mdoc.Main$.main(Main.scala:16)\n\tat mdoc.Main.main(Main.scala)\n\tat sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)\n\tat sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)\n\tat sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)\n\tat java.lang.reflect.Method.invoke(Method.java:498)\n\tat sbt.Run.invokeMain(Run.scala:133)\n\tat sbt.Run.execute$1(Run.scala:82)\n\tat sbt.Run.$anonfun$runWithLoader$5(Run.scala:110)\n\tat scala.runtime.java8.JFunction0$mcV$sp.apply(JFunction0$mcV$sp.java:23)\n\tat sbt.util.InterfaceUtil$$anon$1.get(InterfaceUtil.scala:17)\n\tat sbt.TrapExit$App.run(TrapExit.scala:258)\n\tat java.lang.Thread.run(Thread.java:748)\n",
  "thread_name" : "run-main-0"
}

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" : "2020-11-21T09:22:20.396Z",
  "@version" : "1",
  "bool_value" : true,
  "file" : "index.md",
  "level" : "INFO",
  "level_value" : 20000,
  "line" : 57,
  "list_value" : [
    1,
    2,
    3
  ],
  "logger_name" : "test-logger",
  "message" : "Message with arguments",
  "string_value" : "<VALUE>",
  "thread_name" : "run-main-0"
}

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.Session.App.That]
// logger.info
// ^

However there is built-in support for case classes and sealed traits provided by generic module.