logging4s
Structured logging for Scala 3. You describe how your types render with a Loggable[A] type class, get a
Logging[F[_]] for your effect type, and log values directly. Each value is rendered to JSON and attached to the log
event as structured data; the message string carries a human-readable rendering of the same values.
Install
libraryDependencies ++= Seq(
"now.sendline" %% "logging4s-cats" % "7.0.0",
"now.sendline" %% "logging4s-logback" % "7.0.0",
)
import logging4s.cats.CatsInstances.givenlibraryDependencies ++= Seq(
"now.sendline" %% "logging4s-zio" % "7.0.0",
"now.sendline" %% "logging4s-logback" % "7.0.0",
)
import logging4s.zio.ZioInstances.givenlibraryDependencies ++= Seq(
"now.sendline" %% "logging4s-kyo" % "7.0.0",
"now.sendline" %% "logging4s-logback" % "7.0.0",
)
import logging4s.kyo.KyoInstances.givenlibraryDependencies ++= Seq(
"now.sendline" %% "logging4s-rapid" % "7.0.0",
"now.sendline" %% "logging4s-logback" % "7.0.0",
)
import logging4s.rapid.RapidInstances.givenThe runtime you pick here is remembered on every page and in every library. A backend goes next to the runtime: logback here, or log4j2, slf4j or the console. logging4s-kyo needs JDK 25; every other module runs on JDK 17.
At a glance
The library is a backend-agnostic core plus thin integration modules. The logging backend (logback / log4j2 / slf4j /
console), the effect type (cats-effect / ZIO / Kyo / rapid), and the JSON codec (circe / jsoniter / …) are
independent modules, each wired in through a given import. core has no dependency on any of them.
flowchart LR
app["your application"] --> core
subgraph core["logging4s-core"]
L["Loggable · JsonEncoder · PlainEncoder"]
G["Logging[F]"]
end
B["backend<br/>logback · log4j2 · slf4j · console"] -->|given LoggingFactory| G
R["runtime<br/>cats-effect · ZIO · Kyo · rapid"] -->|given Delay, PlainEncoder| L
J["json<br/>circe · jsoniter · …"] -->|given JsonEncoder| L
Quick start
Pick a backend and an effect runtime (a JSON library is optional — derives Loggable needs none):
libraryDependencies ++= Seq(
"now.sendline" %% "logging4s-cats" % "7.0.0",
"now.sendline" %% "logging4s-logback" % "7.0.0"
)import cats.effect.{IO, IOApp}
import logging4s.core.{Loggable, Logging}
import logging4s.cats.CatsInstances.given // Delay[IO], cats.Show/data bridges
import logging4s.logback.LogbackInstances.given // the LoggingFactory
final case class User(id: Int, name: String) derives Loggable
object Main extends IOApp.Simple:
def run: IO[Unit] =
for
log <- Logging.create[IO]("Main")
_ <- log.info("user created", User(1, "John"))
yield ()The logback backend attaches User as a nested JSON object; the message string gets its plain rendering
(abridged — LogstashEncoder also writes @timestamp, logger_name, thread_name and the rest of its envelope):
{"message":"user created: user -> (id -> (1), name -> (John))","user":{"id":1,"name":"John"},"source":"Main.scala:13","level":"INFO"}Adopters
Using logging4s? Open a PR to add your logo.