Logging in Clojure / JVM – Part 2
Cross-posted to Zolo Labs
In part 1 , we looked at the history of logging in Java. This time, we’ll learn more about SLF4J (Simple Logging Facade for Java).
From part 1, we saw that SLF4J is not a proxy to other logging frameworks, rather it is an API that allows end users to inject their desired logging framework at deployment time. SLF4J comes with adapters for many commonly used logging frameworks.
For my side project, I’m using SLF4J API. As many other projects, my project is dependent on many libraries. Unfortunately, not all libraries use SLF4J; and indeed, some of them use log4j API. You’ll be surprised to see how many newly written libraries use log4j (even though log4j is old and horrid). Even if they see the benefits to changing to SLF4J, it probably won’t happen soon. SLF4J comes with bridging modules for JCL, JUL and log4j to consolidate logging. These bridging modules redirect calls made to log4j, JCL and JUL to SLF4J instead. The image below explains the idea.
Mapped Diagnostic Context
Another awesome feature of SLF4J is MDC ( Mapped Diagnostic Context). Even though it sounds very complicated, it is simple to use and yet so powerful. MDC is essentially a hash map maintained by the logging framework that can be inserted into log messages. Applications can update this hash map using SLF4J. Currently only log4j and logback offer MDC functionality. SLF4J will simply delegate to log4j or logback. If you use some other logging framework, then SLF4J will maintain the hash map, but you’ll need to write some custom code to retrieve the information from the map.
What’s the use of MDC?
One of the main goals of logging is to audit and debug complex real-world, distributed systems. These systems handle multiple clients simultaneously. So log messages are going to be interleaved. So, it’s very important to consolidate log messages of a single client or a single API call. The simplest way is to tag all log messages with client-info and trace-id (we’ll discuss more about this in the next part of this series). Without MDC, we’d need to put this information in every logging call. With MDC, all we have to do is setup the context (client-info , trace-id etc) and all our log messages will automatically have this information. This transforms our log messages into an amazing resource to learn about the system and its users.
Using MDC in Clojure:
Unfortunately, as of now, clojure.tools.logging does not support MDC. I think this a big hole in clojure.tools.logging, as Clojure is known for building complex systems. Luckily the Clojure community is vibrant, and there’s an open source project by Malcolm Sparks called clj-logging-config. The main purpose of this library is to programmatically setup logging config files. But there is one function, with-logging-context that allows us to setup MDC. Even though I’m not programmatically setting up logging config in my project right now, I am using this library just for this with-logging-context function. I strongly believe this library (or at least the with-logging-context function) should be part of clojure.tools.logging.
So there! In this part we learnt more about SLF4J and MDC. In the next part we will learn more about structured logging.