ZIO 2.0's new logging functionality reflects our commitment to tackle the everyday problems of modern app development
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:
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.
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.
The primary interface to logging in ZIO 2.0 is the ZIO.log function, which can be used quite simply:
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:
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:
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 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
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.
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.
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!
Stay ahead with the latest insights and breakthroughs from the world of technology. Our newsletter delivers curated news, expert analysis, and exclusive updates right to your inbox. Join our community today and never miss out on what's next in tech.