How to create your logging system using SLF4J
Carlos Monteiro
Posted on May 18, 2024
As a software developer, it's not always possible to dive deep into the language you're working with, and sometimes it's difficult to find situations where you need to use advanced or non-trivial features. It's easy to use a magic framework, the hard part is having the necessary knowledge to create it. For this reason, I created the tag #javaunderthehood to gather this content and help other people understand how this magic happens. Feel free to join me with an interesting new topic.
Do you know what is SLF4J, LOG4J, Logbook or any other variation?
SLF4J is the acronym for Simple Logging Facade for Java, this library provides an interface for various logging frameworks.
In other words, SLF4J provides an interface for anyone who wants to create their own version, allowing you to use a common interface to register the logs of your application. When desired, you can replace the final output (such as a console log) with any different implementation. You can write your logs to a console, file, or database, or send them via email to one of the developers on the team, who will be responsible for writing a letter with the logs and sending it to you.
Knowing it, have you ever seen the line below in any project?
private static final Logger LOGGER = LoggerFactory.getLogger(Main.class);
Both Logger and LoggerFactory are classes of this service. To be able to use them, you only need to add the below dependency to your project.
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>2.0.13</version>
</dependency>
By adding this dependency, you can create the logging structure of the application. But what will happen if you try to run only with this depedency?
As you can see in the above image, the app indicates that you don't have any log provider. Here, you can introduce LOG4J or Logbook, which are providers for this service. However, we don't want them; we want our version because 4 hours of development is better than 23 years of history.
After this brief explanation, let's implement a provider for the SLF4J api
As the first step, create a folder called provider in the root directory of your project, this folder will contain a maven .pom file and the source code of this new provider implementation.
Inside the .pom file, let's add the required dependency.
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>2.0.13</version>
</dependency>
The most important class to keep in mind is the SLF4JServiceProvider, which is the interface, the service, for which we are creating a provider. For an in-depth understanding of what we are doing here, you can read the article Java Service Provider Interface (SPI), what is it and how to use it.
public interface SLF4JServiceProvider {
ILoggerFactory getLoggerFactory();
IMarkerFactory getMarkerFactory();
MDCAdapter getMDCAdapter();
String getRequestedApiVersion();
void initialize();
}
Now you can create the provider, I'll call it SLF4JWorstProvider.
public class SLF4JWorstProvider implements SLF4JServiceProvider {
private final String REQUESTED_API_VERSION = "2.0.13";
private SLF4JWorkProviderLoggerFactory slf4JWorkProviderLoggerFactory;
@Override
public ILoggerFactory getLoggerFactory() {
return slf4JWorkProviderLoggerFactory;
}
@Override
public IMarkerFactory getMarkerFactory() {
return null;
}
@Override
public MDCAdapter getMDCAdapter() {
return new NOPMDCAdapter();
}
@Override
public String getRequestedApiVersion() {
return REQUESTED_API_VERSION;
}
@Override
public void initialize() {
slf4JWorkProviderLoggerFactory = new SLF4JWorkProviderLoggerFactory();
}
}
Overrides the getRequestedApiVersion() method to return the SLF4J version you depend on
Overrides the getLoggerFactory() method to return the factory implementation that creates the Logger object used to write logs.
The factory class needs to implement the ILoggerFactory interface, which has a method to return an implementation of another interface called Logger, responsible for writing logs in whatever place you want.
This is the ILoggerFactory interface.
public interface ILoggerFactory {
Logger getLogger(String var1);
}
To be concise, I removed some of the code from the Logger interface.
public interface Logger {
String ROOT_LOGGER_NAME = "ROOT";
String getName();
default LoggingEventBuilder makeLoggingEventBuilder(Level level) {
return new DefaultLoggingEventBuilder(this, level);
}
@CheckReturnValue
default LoggingEventBuilder atLevel(Level level) {
return this.isEnabledForLevel(level) ? this.makeLoggingEventBuilder(level) : NOPLoggingEventBuilder.singleton();
}
default boolean isEnabledForLevel(Level level) {
int levelInt = level.toInt();
switch(levelInt) {
case 0:
return this.isTraceEnabled();
case 10:
return this.isDebugEnabled();
case 20:
return this.isInfoEnabled();
case 30:
return this.isWarnEnabled();
case 40:
return this.isErrorEnabled();
default:
throw new IllegalArgumentException("Level [" + level + "] not recognized.");
}
}
boolean isInfoEnabled();
void info(String var1);
@CheckReturnValue
default LoggingEventBuilder atInfo() {
return this.isInfoEnabled() ? this.makeLoggingEventBuilder(Level.INFO) : NOPLoggingEventBuilder.singleton();
}
/.../
}
A possible implementation of both interfaces.
First, we have the factory.
public class SLF4JWorkProviderLoggerFactory implements ILoggerFactory {
@Override
public Logger getLogger(final String name) {
return new SLF4JWorkProviderLogger(name);
}
}
Lastly, we have the logger.
public class SLF4JWorkProviderLogger implements Logger {
private final String name;
public SLF4JWorkProviderLogger(final String name) {
this.name = name;
}
@Override
public String getName() {
return name;
}
@Override
public boolean isDebugEnabled() {
return true;
}
@Override
public void debug(String s) {
logMessage(LevelEnum.DEBUG, s);
}
@Override
public boolean isInfoEnabled() {
return true;
}
@Override
public void info(String s) {
logMessage(LevelEnum.INFO, s);
}
private void logMessage(final LevelEnum level, final String message) {
System.out.println(String.format("[%s][%s] %s (×͜×)", level, name, message));
}
}
If you read article Java Service Provider Interface (SPI), what is it and how to use it you should know what to do so that LSF4J can load this new provider implementation. So, let's continue by creating a META-INF folder (inside the resources directory), with a services subfolder that contains a file whose name is the reference of the service class and its contents contain the implementation refence of our provider.
org.slf4j.spi.SLF4JServiceProvider
com.github.c4rlosmonteiro.sl4jworstprovider.SLF4JWorstProvider
At this point, we have the following project structure.
.
├── pom.xml
└── src
└── main
├── java
│ └── com
│ └── github
│ └── c4rlosmonteiro
│ └── sl4jworstprovider
│ ├── internalcomponents
│ │ ├── LevelEnum.java
│ │ ├── SLF4JWorkProviderLoggerFactory.java
│ │ └── SLF4JWorkProviderLogger.java
│ └── SLF4JWorstProvider.java
└── resources
└── META-INF
└── services
└── org.slf4j.spi.SLF4JServiceProvider
Finally, we can use this provider.
As you can see in the image, our logger records the logs below.
[INFO][com.github.c4rlosmonteiro.slf4jworstprovideruser.Main] Generating hello world messages! (×͜×)
[DEBUG][com.github.c4rlosmonteiro.slf4jworstprovideruser.Main] Generating hello world message for [Bob] (×͜×)
[INFO][com.github.c4rlosmonteiro.slf4jworstprovideruser.Main] :: Hello Bob! (×͜×)
[DEBUG][com.github.c4rlosmonteiro.slf4jworstprovideruser.Main] Generating hello world message for [Paul] (×͜×)
[INFO][com.github.c4rlosmonteiro.slf4jworstprovideruser.Main] :: Hello Paul! (×͜×)
[DEBUG][com.github.c4rlosmonteiro.slf4jworstprovideruser.Main] Generating hello world message for [Marcus] (×͜×)
[INFO][com.github.c4rlosmonteiro.slf4jworstprovideruser.Main] :: Hello Marcus! (×͜×)
You can test this version by adding the dependency below to your project.
<dependency>
<groupId>com.github.c4rlosmonteiro</groupId>
<artifactId>slf4j-worst-provider</artifactId>
<version>1.0.0</version>
</dependency>
Check the source code on github.com.
Before you go!
Follow for more ;)
Suggest new topics
Use the comments section to discuss the feature and >comment on real-world use cases to help others understand >its usage
#javaunderthehood
Posted on May 18, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.