How to create your logging system using SLF4J
Tags: java, javaunderthehood, tutorial
Published: 2024-05-18 05:17:21

> 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?


![IDE with a error](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7i93tqnyqfvvakc2g8nl.png)

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](https://dev.to/c4rlosmonteiro/java-service-provider-interface-spi-what-is-it-and-how-to-use-it-3kn). 

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

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](https://dev.to/c4rlosmonteiro/java-service-provider-interface-spi-what-is-it-and-how-to-use-it-3kn) 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.


![Main class logging](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/65wb18d851hgpzyzvkyl.png)

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](https://github.com/c4rlosmonteiro/slf4j-worst-provider).

---

> _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_