Skip to main content
Version: 2.x

Introduction to Logging in ZIO

ZIO supports a lightweight built-in logging facade that standardizes the interface to logging functionality. So it doesn't replace existing logging libraries, but also we can plug it into one of the existing logging backends.

We can easily log using the ZIO.log function:

import zio._

val app =
for {
_ <- ZIO.log("Application started!")
name <- Console.readLine("Please enter your name: ")
_ <- ZIO.log("User entered its name: $name")
_ <- Console.printLine("Hello, $name")
} yield ()

Logging Levels

To log with a specific log-level, we can use the ZIO.logLevel combinator:

ZIO.logLevel(LogLevel.Warning) {
ZIO.log("The response time exceeded its threshold!")
}

Or we can use the following functions directly:

  • ZIO.logDebug
  • ZIO.logError
  • ZIO.logFatal
  • ZIO.logInfo
  • ZIO.logWarning

For example, for log with the error level, we can use ZIO.logError like this:

ZIO.logError("File does not exist: ~/var/www/favicon.ico")

Spans

It also supports spans:

ZIO.logSpan("myspan") {
ZIO.sleep(1.second) *> ZIO.log("The job is finished!")
}

ZIO Logging calculates the running duration of that span and includes that in the logging data corresponding to its span label.

Log Annotations

ZIO by default adds some contextual information to the log messages, like the timestamp, log level, fiber ID, and source location. Sometimes these default contextual information are not sufficient to understand the circumstances under which they were generated. In such cases, we need to add custom contextual information to the log messages. We can do this using log annotations.

ZIO's Built-in Log Annotation

For example, in microservice environments, we might have several services that are communicating with each other. In such cases, we might want to correlate the logs generated by different services. We can do this by adding a log annotation called correlation_id. This log annotation can be very simple, just a string, that is passed along with requests and responses. So, when we log a message, we know which request or response it is related to. ZIO has a built-in log annotation API that allows us to add such custom contextual information to the log messages:

import zio._

object MainApp extends ZIOAppDefault {
def randomDelay = Random.nextIntBounded(1000).flatMap(t => ZIO.sleep(t.millis))

def run =
ZIO.foreachParDiscard(Chunk("UserA", "UserB", "UserC")) { user =>
ZIO.logAnnotate("correlation_id", user) {
for {
_ <- ZIO.log("fetching user from database") *> randomDelay
_ <- ZIO.log("downloading user's profile picture") *> randomDelay
} yield ()
}
}
}

Here is an example of the log messages generated by the above code, each log message contains the correlation_id log annotation:

timestamp=2024-05-14T15:44:50.734129Z level=INFO thread=#zio-fiber-851563977 message="fetching user from database" location=zio.examples.MainApp.run file=MainApp.scala line=12 correlation_id=UserC
timestamp=2024-05-14T15:44:50.734127Z level=INFO thread=#zio-fiber-41969365 message="fetching user from database" location=zio.examples.MainApp.run file=MainApp.scala line=12 correlation_id=UserA
timestamp=2024-05-14T15:44:50.734123Z level=INFO thread=#zio-fiber-1775966732 message="fetching user from database" location=zio.examples.MainApp.run file=MainApp.scala line=12 correlation_id=UserB
timestamp=2024-05-14T15:44:50.928248Z level=INFO thread=#zio-fiber-851563977 message="downloading user's profile picture" location=zio.examples.MainApp.run file=MainApp.scala line=13 correlation_id=UserC
timestamp=2024-05-14T15:44:51.054287Z level=INFO thread=#zio-fiber-41969365 message="downloading user's profile picture" location=zio.examples.MainApp.run file=MainApp.scala line=13 correlation_id=UserA
timestamp=2024-05-14T15:44:51.534263Z level=INFO thread=#zio-fiber-1775966732 message="downloading user's profile picture" location=zio.examples.MainApp.run file=MainApp.scala line=13 correlation_id=UserB

Typed Log Annotations

In more complex scenarios, we might want to add more structured information to the log messages. For example, we might want to add the user information to the log messages. In such cases, we need a typed log annotation that supports structured information, e.g. a User case class that contains the user's id, name, email, etc.

Using ZIO Logging, we can define typed log annotations using the LogAnnotation class. So let's add required dependencies to the build.sbt file:

libraryDependencies ++= Seq(
"dev.zio" %% "zio-logging" % "4.0.2",
"dev.zio" %% "zio-json" % "0.6.2"
)

Now, let's assume we have a User case class:

case class User(firstName: String, lastName: String)

We can define a typed log annotation for the User case class like this:

import zio.json.{DeriveJsonEncoder, EncoderOps}
import zio.logging.{ConsoleLoggerConfig, LogAnnotation, LogFormat, consoleJsonLogger}
import zio._

object TypedLogAnnotationExample extends ZIOAppDefault {

case class User(firstName: String, lastName: String)

object User {
implicit val encoder = DeriveJsonEncoder.gen[User]
}

private val userLogAnnotation = LogAnnotation[User]("user", (_, u) => u, _.toJson)

private val logConfig = ConsoleLoggerConfig.default.copy(
format = LogFormat.default + LogFormat.annotation(LogAnnotation.TraceId) + LogFormat.annotation(userLogAnnotation)
)

override val bootstrap: ZLayer[ZIOAppArgs, Any, Any] =
Runtime.removeDefaultLoggers >>> consoleJsonLogger(logConfig)

def run =
for {
_ <- ZIO.foreachPar(List(User("John", "Doe"), User("Jane", "Doe"))) { user =>
{
ZIO.logInfo("Starting operation") *>
ZIO.sleep(500.millis) *>
ZIO.logInfo("Stopping operation")
} @@ userLogAnnotation(user)
}
_ <- ZIO.logInfo("Done")
} yield ()

}

The log messages generated by the above code will contain the user log annotation:

{"timestamp":"2024-05-14T20:37:33.744171+04:30","level":"INFO","thread":"zio-fiber-6","message":"Starting operation","user":{"firstName":"Jane","lastName":"Doe"}}
{"timestamp":"2024-05-14T20:37:33.744166+04:30","level":"INFO","thread":"zio-fiber-5","message":"Starting operation","user":{"firstName":"John","lastName":"Doe"}}
{"timestamp":"2024-05-14T20:37:34.334837+04:30","level":"INFO","thread":"zio-fiber-5","message":"Stopping operation","user":{"firstName":"John","lastName":"Doe"}}
{"timestamp":"2024-05-14T20:37:34.334848+04:30","level":"INFO","thread":"zio-fiber-6","message":"Stopping operation","user":{"firstName":"Jane","lastName":"Doe"}}
{"timestamp":"2024-05-14T20:37:34.337953+04:30","level":"INFO","thread":"zio-fiber-4","message":"Done"}

Further Reading