A brief intro to SLF4J and the Java logging hell

ardc_overflow

Rodolpho Alves

Posted on September 4, 2022

A brief intro to SLF4J and the Java logging hell

Intro

This will be breaking my chain of Articles written in Brazilian Portuguese, I've fallen into the habit of making my study notes in English to make them easier to share. I'll soon translate everything here and create a translated version!

A brief history of logging in Java

At the dawn of Java logging still wasn’t as important as it is today, and as most languages of at that time everything boiled down to writing lines to the console and that was good enough! Why do you need standards and fancy structures when memory is sparse, right?

So it was common to see folks using the rather annoying System.out and System.err and doing their own solutions to write logs to files and other places!

log4j is born

This is where the Open Source community first steps in! Since Java lacked any logging standard whatsoever and it’s “native logger” (being generous here given it was err and out...) was rather poor when it came to features the community itself rose to the challenge and log4j was created as a mean to provide extensible, configurable and standardized logging for Java.

Nowadays log4j is infamous due to the security incident that wreaked havoc across the industry back in December 2021, but at its time it was a good thing. Not perfect, but good!

Of course Sun, the Java owner back then, quickly got wind of a need for standards and native ways to log stuff so they decided to make that happen!

The ‘native’ library enters the fray

With JDK 1.4, in 2002, java.util.logging was released as a native offering for logging. But… it wasn’t well received. First it was hard to decouple and isolate, then for some unknown reason they also decided it’d be nice to reinvent the wheel and use a bunch of random names for log levels instead of the standardized ones the industry had. Thanks to that you had pearls such as being able to log.fine("this is fine") but also being able to log.finer("this is even finer!") and not to mention the best of all log.finest("really great logging levels, innit?")... as you may have notice the way they decided to standardize log levels was quite log.severe("not good")!

🤦🏻 Worth noting that up to this day the java.util.logging library keeps this standard due to backwards compatibility. So even when doing Kotlin stuff in Android you can still log great fine and severe messages if you wish!

I need to switch loggers! Now what?

Now that there were two ‘popular’ contenders a new problem began to surface: What if I want to move my project from the amazing java.util.logging library into log4j?

Since both libraries were coupled to their own implementation that meant you’d need to refactor your whole code at once or roll with two logging libraries for a while. You could also create your own interfaces and implementations to hide the logging details from caller! And that’s what the community did!

Apache common-logging was born to provide means to abstract the implementation details of any logging library and provide a common, standard, API that any Application may use without caring who’s writing the logs behind the curtains! It had its shortcomings and wasn’t that widely adopted, but it’s idea lived on to see a better implementation be born. Enter SLF4J.

SLF4J to the Rescue

Simple Logging Facade for Java (SLF4J for short) is an out-of-the-box set of Interface and Abstractions that aim to consolidate and standardize logging in the JVM environment, while allowing decoupling.

The decoupling is achieved because you need only to rely on SLF4J interfaces during our implementation. This wizardry is achievable because logging libraries itself need to provide “Wrappers” or “Adapters” that plug into SLF4J abstractions to then deliver their logging functionalities.

import org.slf4j.Logger
import org.slf4j.LoggerFactory

/**
 * get a SL4J logger and do something
 */
fun getLoggerAndLog() {
    val logger: Logger = LoggerFactory.getLogger("my logger") // note that this is a SLF4J Logger Interface!
    // whichever library is 'wrapped' by SLF4J now handles the levels, formatting and everything!
    logger.debug("this is a debug message")
    logger.info("this is some information")
    logger.warn("this is a warning!")
    logger.error("this is an error")
}
Enter fullscreen mode Exit fullscreen mode

In practical terms what happens here is that you need at least two dependencies to be added to your project for this to work:

  1. 'org.slf4j:slf4j-api' for the SLF4J interfaces
  2. log4j-slf4j18-impl, com.github.tony19:logback-android, ch.qos.logback:logback-classic for the SLF4J ‘providers’

The provides themselves use Reflection and other technologies to “tie” into the LoggerFactory class and routines provided by SLF4J!

What is a “Facade”

The “Facade” within SLF4J stands for the Facade Design Pattern. In a nutshell a Facade attempts to provide a simpler (and common) access method to complex systems beneath it. This is good because it can allow for decoupling between clients of those subsystems and themselves while also reducing the need to completely understand those subsystems!

Imagine you want to buy something online. As a developer you know that you’re only inputting information in a website (or a mobile app) but you also know that there’s something beneath it. Beneath every ecommerce out there there’ll be APIs, Messaging and Databases! In a way you could say that the Frontend is a Facade for everything that happens underneath. As an online shopper you don’t need to know about which API calls are happening and which Messages are being sent over RabbitMQ, you just care that interacting with the ‘buy’ routine does all the magic for you.

Sample - Logback on Android!

First things first: Let’s start by adding the SLF4J dependency to our Application’s Gradle file:

/* snip */
dependencies {
        /* Adding SLF4J's Interfaces */
    implementation 'org.slf4j:slf4j-api:1.7.36'
}
Enter fullscreen mode Exit fullscreen mode

Now we’d need to add Logback’s library, but since there are some additional steps required to run Logback on Android we can use a forked version that does all the extra work for us already: Logback-android. Now, to get Logback working we’ll need to first add the dependency and add the logback xml configuration to the application’s assets:

/* snip */
dependencies {
    implementation 'org.slf4j:slf4j-api:1.7.36'
        /* Logback-android */
    implementation 'com.github.tony19:logback-android:2.0.0'
}
Enter fullscreen mode Exit fullscreen mode
<configuration>
  <appender name="logcat" class="ch.qos.logback.classic.android.LogcatAppender">
    <tagEncoder>
      <pattern>%logger{12}</pattern>
    </tagEncoder>
    <encoder>
      <pattern>[%-20thread] %msg</pattern>
    </encoder>
  </appender>

  <root level="DEBUG">
    <appender-ref ref="logcat" />
  </root>
</configuration>
Enter fullscreen mode Exit fullscreen mode

Now all that’s left is to request a Logger by invoking the LoggerFactory.GetLogger method from SLF4J and you can Log away!

lateinit var log: Logger

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    log = LoggerFactory.getLogger("test")
    log.debug("Initializing, now with 101% more HILT")
    setContent {
        AppCanvas {
            Greeting(log)
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Additional Resources

Complete notes available on my Notion workspace.

This post's cover is from https://undraw.co/

💖 💪 🙅 🚩
ardc_overflow
Rodolpho Alves

Posted on September 4, 2022

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related