How to create your logging system using SLF4J

c4rlosmonteiro

Carlos Monteiro

Posted on May 18, 2024

How to create your logging system using SLF4J

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);


Enter fullscreen mode Exit fullscreen mode

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>


Enter fullscreen mode Exit fullscreen mode

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?

IDE with a error

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>


Enter fullscreen mode Exit fullscreen mode

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();
}


Enter fullscreen mode Exit fullscreen mode

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();
    }
}


Enter fullscreen mode Exit fullscreen mode
  1. Overrides the getRequestedApiVersion() method to return the SLF4J version you depend on

  2. 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);
}


Enter fullscreen mode Exit fullscreen mode

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();
    }


    /.../
}



Enter fullscreen mode Exit fullscreen mode

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);
    }
}


Enter fullscreen mode Exit fullscreen mode

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));
    }
}



Enter fullscreen mode Exit fullscreen mode

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


Enter fullscreen mode Exit fullscreen mode

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


Enter fullscreen mode Exit fullscreen mode

Finally, we can use this provider.

Main class logging

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! (×͜×)


Enter fullscreen mode Exit fullscreen mode

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>


Enter fullscreen mode Exit fullscreen mode

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

💖 💪 🙅 🚩
c4rlosmonteiro
Carlos Monteiro

Posted on May 18, 2024

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

Sign up to receive the latest update from our blog.

Related