By Alvin Alexander. Last updated: December 1, 2024
If you’re interested in logging in a ZIO application, the following example shows a collection of different ways you can write log messages. I also show how to create your own custom log format, so the output logging from this application looks like this:
2024-11-28T16:09:08.669276-05:00 | INFO | zio-fiber-249876708 | Basic log message (defaults to 'info') |
2024-11-28T16:09:08.673841-05:00 | INFO | zio-fiber-249876708 | Info level message |
2024-11-28T16:09:08.674505-05:00 | WARN | zio-fiber-249876708 | Warning level message |
2024-11-28T16:09:08.675342-05:00 | ERROR | zio-fiber-249876708 | Error level message |
2024-11-28T16:09:08.678958-05:00 | INFO | zio-fiber-249876708 | inside logSpan-1 |
2024-11-28T16:09:08.679334-05:00 | INFO | zio-fiber-249876708 | inside logSpan-2 |
2024-11-28T16:09:08.68125-05:00 | INFO | zio-fiber-249876708 | Starting customer operation |
2024-11-28T16:09:08.682087-05:00 | INFO | zio-fiber-249876708 | Completed customer operation |
2024-11-28T16:09:08.686092-05:00 | INFO | zio-fiber-249876708 | This log message will include the user-id annotation | user-id=12345
2024-11-28T16:09:08.690269-05:00 | INFO | zio-fiber-249876708 | Processing value: some-value |
2024-11-28T16:09:08.69066-05:00 | INFO | zio-fiber-249876708 | Successfully processed: some-value |
An example ZIO 2 logging application
Given that introduction, here’s my example ZIO 2 logging application:
//> using scala "3"
//> using dep "dev.zio::zio::2.1.13"
//> using dep "dev.zio::zio-logging:2.4.0"
// //> using dep "dev.zio::zio-logging-slf4j:2.4.0"
// //> using dep "ch.qos.logback:logback-classic:1.5.12"
import zio.*
// needed for configuring:
import zio.logging.consoleLogger
import zio.logging.ConsoleLoggerConfig
import zio.logging.LogFilter
import zio.logging.LogFormat
object ZioLoggingExample extends ZIOAppDefault:
// define custom log format
val logSeparator = LogFormat.text(" | ")
val customLogFormat =
LogFormat.timestamp + logSeparator +
LogFormat.level + logSeparator +
LogFormat.fiberId + logSeparator +
LogFormat.line + logSeparator + // our ‘message’
LogFormat.annotations // ZIO.logAnnotate values
// configure logger with custom format and INFO as root level
val loggerConfig = ConsoleLoggerConfig(
format = customLogFormat,
filter = LogFilter.LogLevelByNameConfig(
rootLevel = LogLevel.Info,
mappings = Map[String, LogLevel]()
)
)
// remove default loggers and replace them with the custom console logger.
// note that >>> is for composing multiple ZLayer values in sequence.
override val bootstrap = Runtime.removeDefaultLoggers >>>
consoleLogger(config = loggerConfig)
val program = for
_ <- ZIO.log("Basic log message (defaults to 'info')")
_ <- ZIO.logInfo("Info level message")
_ <- ZIO.logWarning("Warning level message")
_ <- ZIO.logError("Error level message")
_ <- ZIO.logDebug("Debug message: won’t show with Info level")
_ <- ZIO.logTrace("Trace message: won’t show with Info level")
// use logSpan to group related log messages (not 100% working)
_ <- ZIO.logSpan("operation-name") {
ZIO.log("inside logSpan-1") *> ZIO.log("inside logSpan-2")
}
_ <- ZIO.logSpan("customer-operation") {
for
_ <- ZIO.log("Starting customer operation")
// do your ‘customer operation’ here
_ <- ZIO.log("Completed customer operation")
yield ()
}
// Log with annotations
// todo: this does not work with my customizations (yet)
_ <- ZIO.logAnnotate("user-id", "12345") {
ZIO.log("This log message will include the user-id annotation")
}
// using `tap` and `tapError`
_ <- ZIO.attempt("some-value")
.tap(value => ZIO.log(s"Processing value: $value"))
.tap(value => ZIO.logInfo(s"Successfully processed: $value"))
.tapError(err => ZIO.logError(s"Failed processing: ${err.getMessage}"))
yield
()
val run = program
I hope those ZIO logging examples and custom log formatting are helpful!