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")
}
In practical terms what happens here is that you need at least two dependencies to be added to your project for this to work:
-
'org.slf4j:slf4j-api'
for the SLF4J interfaces -
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'
}
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'
}
<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>
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)
}
}
}
Additional Resources
Complete notes available on my Notion workspace.
This post's cover is from https://undraw.co/
Top comments (2)
Great article, short yet informative :)
Thanks for the intro, always wondered how we got into this mess.