BlogtechnicalA Preview of Logging in ZIO 2.0

A Preview of Logging in ZIO 2.0

Learn how a new feature in ZIO 2.0 promises to give you richer application logs across your own custom logic and the whole ZIO ecosystem

29 August 2021# scala# zio

ZIO 2.0's new logging functionality reflects our commitment to tackle the everyday problems of modern app development

A Preview of Logging in ZIO 2.0

By John A. De Goes

ZIO has differentiated itself from Scala's own Future by not only embracing pure functional programming (with benefits to performance and power), but also by taking a batteries included approach to application development.

Practically speaking, this has meant ZIO ships with much more than just a "better Future", including:

  • Async concurrent data structures, which power high-performance, non-blocking applications
  • Software transactional memory, which enables any user to create more deadlock-free, race-free concurrency
  • A lightweight, type-safe form of dependency injection, which enables snapping together applications out of smaller pieces
  • A powerful testkit (ZIO Test) for quickly and deterministically testing ZIO applications

As we prepare ZIO 2.0, we have been thinking about some of the biggest and most universal pain points that ZIO developers have to solve on their own, and looking for ways to bring lightweight, tightly integrated solutions into ZIO core.

Integrated metrics is one such example, but in this post, I want to talk about another feature coming to ZIO 2.0: built-in logging.

Facades versus Backends

ZIO 2.0 now has preliminary support for logging. This logging support does not replace the need for a logger backend, such as LogStage, ZIO Logging, slf4j, log4j, or equivalent.

Rather, ZIO 2.0 logging support brings a lightweight but powerful logging facade to all ZIO applications and libraries.

A logging facade is different than a logging backend. A logging facade, like slf4j, is a small, standardized interface to logging functionality, which enables many different applications to consume logging services in a standardized way.

A logging backend, on the other hand, is a high-performance, concurrent-safe, feature-filled implementation of logging functionality, combining features like log formatting, with different choices for log output, configurable log rotation, log filtering, and much more.

By focusing on just providing a logging facade, ZIO 2.0 can empower standardized logging across ZIO applications and libraries without taking the place of tried and trusted logging backends that most applications already ship with.

Now this means it is not practical to use ZIO 2.0 logging without a logging backend. However, for local development, ZIO 2.0 logging will print out simply formatted log messages to the console, so you can experiment with logging before plugging in your own logging backend of choice.

Let's take a peek at what the ZIO 2.0 logging facade looks like.

A Tour of ZIO 2.0 Logging

The primary interface to logging in ZIO 2.0 is the ZIO.log function, which can be used quite simply:

ZIO.log("Hello World!")

The log function returns a ZIO effect, and can therefore be utilized anywhere in your ZIO application without wrapping.

The default log level is informational, which can be changed by using the ZIO.logLevel combinator:

ZIO.logLevel(LogLevel.DEBUG) {
  ZIO.log("Hello World from a DEBUG block!")
}

This allows you to write a whole bunch of log statements at the same logging level, without having to explicitly specify this log level at the level of each individual statement.

Of course, in many cases, you want control over which log level you use on a per-statement basis, and for that, you can use the ZIO.log*** family of methods:

ZIO.logDebug("Another debug message!")

In many applications, we would like to record spans. Spans associate labels with log messages, and many times, we also want timing on the length of each span, so we can see how long it takes different parts of our application to execute.

For logging spans, ZIO 2.0 includes the ZIO.logSpan combinator, which can be used quite simply:

ZIO.logSpan("test span") {
  ZIO.log("It's alive!")
}

ZIO 2.0 automatically computes the running duration for each span, and makes this information available to logging backends.

In addition, ZIO 2.0 leverages execution tracing to provide locations on log messages, which means that you get out-of-the-box support for file, line, and class metadata, without having to create separate loggers for each service in your application.

If you wrote the preceding logging code and took advantage of the developer-mode experience, you would see the following message printed out to the console:

timestamp=2021-08-28T10:28:30.299785200Z level=INFO 
  thread=#262 message="It's alive!" "test span"=6ms file=ZIO.scala 
    line=677 class=zio.ZIO method=exit

This text format chosen by the developer-mode experience is semi-structured, but different logging backends may choose to provide support for fully-structured log entries like JSON, or unstructured plaintext logging.

Fully Fiber-Aware

A key advantage of ZIO 2.0 logging is that it is fully fiber-aware. If you were paying close attention to the log output shown in the preceding section, you noticed a thread label attached to the log line (thread=#262).

In fact, this thread identifier is actually the identifier of the fiber that executed the log statement. Because ZIO 2.0 logging tracks logging to the level of each fiber, rather than physical threads, this means all your logs will be fiber-aware, and reflect your natural application logic, without you having to do any extra work.

In addition to tracking log messages at the level of fibers, ZIO 2.0 provides logging backends access to fiber-specific data, stored in fiber refs. Fiber-specific data could include correlation or request identifiers, telemetry data, logger names, event payloads, or other forms of semi-structured or structured data, which logging backends can extract and place into any desired format.

Towards 2.0

As you can see from this preview, ZIO 2.0 logging offers a thin but incredibly well-integrated, rich, and compositional logging experience.

ZIO 2.0 logging will not replace your logging backend, but rather, work with your logging backend to provide standardized, rich data that will make analyzing application behavior easier than ever.

In addition, we hope that ZIO 2.0 logging will ensure that all ZIO libraries in the open source ecosystem standardize on a uniform way to integrate into your logging backend, rather than using individual and ad hoc logging approaches.

This standardization will mean you can configure your logging in one place and get all your logs in one place.

Eager to give this new feature a try? Keep on the lookout for ZIO 2.0 M3, which will be the first milestone release to include the new logging functionality.

Be sure to let us know what you think!

Related Posts

14 July 2020

African Scala Development Program

African Scala Development Program We are thrilled to announce that Ziverge Inc. has launched an African Scala Development Program, in…

See More

Functional Design

Functional Design by John A. De Goes Although functional programming theory is useful, most day-to-day functional programming does not…

See More
16 November 2020

Functional Scala 2020 Scholarship Program

Functional Scala 2020 Scholarship Program The countdown to Functional Scala 2020 is underway, and today, we are excited to announce the…

See More
Subscribe to our newsletter