Logging4s
is small logging library for structured (json) logs build on top of logback
and logstash-encoder
.
logging4s-core
- type classes for abstract encoding, base Logging support work with Try, Either and unsafe variants.logging4s-cats
- implementation forcats
andcats-effect
logging4s-cats-core
- implementation forPlainEncoder
viacats.Show
logging4s-ce-2
- implementation forcats-effect 2
Sync
logging4s-ce-3
- implementation forcats-effect 3
Sync
logging4s-zio
- implementation on top ofzio.Task
for runtime andzio.prelude.Debug
for plain logs.logging4s-json
- implementation json logs for different libslogging4s-circe
- implementation forcirce.Encoder
logging4s-jsoniter
- implementation forjsoneter-scala JsonValueCodec
logging4s-argonaut
- implementation forargonaut EncodeJson
logging4s-play-json
- implementation forplay-json Writes
logging4s-json4s
- implementation forjson4s Formats
logging4s-spray-json
- implementation forspray-json JsonWriter
Let's say you are using cats-effect 3
and circe
.
Plug the library in for sbt
libraryDependencies ++= Seq(
"org.logging4s" %% "logging4s-ce-3" % version,
"org.logging4s" %% "logging4s-circe" % version
)
Create Loggable
implementation for your domain objects, create Logging
instance and log your objects.
// Your domain
import java.util.UUID
import cats.Show
import io.circe.Encoder
import io.circe.generic.semiauto.deriveEncoder
import logging4s.core.Loggable
import logging4s.cats.instances.given
import logging4s.json.circe.instances.given
final case class User(id: UUID, name: String, age: Int)
object User:
given Show[User] = user => s"id=${user.id}, name=${user.name}, age=${user.age}"
given Encoder[User] = deriveEncoder
given Loggable[User] = Loggable.make("user")
// Your program
import java.util.UUID
import cats.effect.std.UUIDGen
import cats.effect.{ExitCode, IO, IOApp}
import logging4s.cats.Logging
import logging4s.core.syntax.withKey
import logging4s.cats.instances.given
import logging4s.json.circe.instances.given
object CatsEffect3Example extends IOApp:
private def createUser(name: String, age: Int): IO[User] =
for id <- UUIDGen.randomUUID[IO]
yield User(id, name, age)
override def run(args: List[String]): IO[ExitCode] =
for
context <- IO.randomUUID.map(uuid => LoggingContext(uuid.withKey("session_id")))
logging <- Logging.create[IO]("CatsEffect3Example", context)
johnShow <- createUser("John Show", 22)
_ <- logging.info("User created", johnShow)
daenerys <- createUser("Daenerys Targaryen", 22)
_ <- logging.info("User created", daenerys)
_ <- logging.info("All users created", Seq(johnShow, daenerys))
yield ExitCode.Success
This will output:
{"@timestamp":"2023-01-30T13:42:13.249+03:00","message":"User created: session_id -> (9602ed80-e54b-4e0a-8b9c-64762d28d05e), user -> (id=5db8c5e2-6275-437a-bca8-1ad8cd84fbd8, name=John Show, age=22)","name":"CatsEffect3Example","level":"INFO","user":{"id":"5db8c5e2-6275-437a-bca8-1ad8cd84fbd8","name":"John Show","age":22}}
{"@timestamp":"2023-01-30T13:42:13.249+03:00","message":"User created: session_id -> (9602ed80-e54b-4e0a-8b9c-64762d28d05e), user -> (id=c5e4bd53-abd8-4922-bcd2-5e40322e6b9b, name=Daenerys Targaryen, age=22)","name":"CatsEffect3Example","level":"INFO","user":{"id":"c5e4bd53-abd8-4922-bcd2-5e40322e6b9b","name":"Daenerys Targaryen","age":22}}
{"@timestamp":"2023-01-30T13:42:13.249+03:00","message":"All users created: session_id -> (9602ed80-e54b-4e0a-8b9c-64762d28d05e), users -> ([id=5db8c5e2-6275-437a-bca8-1ad8cd84fbd8, name=John Show, age=22,id=c5e4bd53-abd8-4922-bcd2-5e40322e6b9b, name=Daenerys Targaryen, age=22])","name":"CatsEffect3Example","level":"INFO","users":[{"id":"5db8c5e2-6275-437a-bca8-1ad8cd84fbd8","name":"John Show","age":22},{"id":"c5e4bd53-abd8-4922-bcd2-5e40322e6b9b","name":"Daenerys Targaryen","age":22}]}
In the logback.xml
file, you can configure the output of logs as you need.
See ./examples
for more examples.
Have a library for structured logging that supports Scala 3
and various implementations of effects
and json
libraries
cause izumi logstage
and tofu-logging
still not ported for new scala.