Alternative to log4j

Posted by Perry Mason on Mon, 21 Feb 2022 02:45:20 +0100

Last December, as Log4J exposed high-risk vulnerabilities, it was not good news for java developers, especially for Ops. The former must repackage their applications with a fixed version of Log4J, while the latter must be redeployed. But for the program log, it is not just. Today, let's take a look at the log mechanism of the java system

In short, system Logger is an API of the log engine. Instead of using the API of SFL4J and the desired implementation, use system Logger. Since the beginning of Java 9, java has opened system Logger, but I only learned about it recently. It's a pity.

System.Logger API

This API is a little different from other logging APIs: it avoids different logging methods, such as debug(), and supports a single method of passing the logging parameter info(). log()``Level


If you do not provide any corresponding implementation on the classpath, system The logger defaults to JUL.

public class LoggerExample {

  private static final System.Logger LOGGER = System.getLogger("c.f.b.DefaultLogger"); // 1

  public static void main(String[] args) {
      LOGGER.log(DEBUG, "A debug message");
      LOGGER.log(INFO, "Hello world!");
  }
}
  1. Get the logger.

Running the above code snippet will output the following:

Dec 24, 2021 10:38:15 AM c.f.b.DefaultLogger main
INFO: Hello world!

Widely compatible with other logging systems

Most applications currently use Log4J2 or SLF4J. Both provide compatible system Logger implementation.

For Log4J, we need to add two dependencies:

<dependencies>
    <dependency>
        <groupId>org.apache.logging.log4j</groupId>
        <artifactId>log4j-core</artifactId>            <!-- 1 -->
        <version>2.17.0</version>
    </dependency>
    <dependency>
        <groupId>org.apache.logging.log4j</groupId>    <!-- 2 -->
        <artifactId>log4j-jpl</artifactId>
        <version>2.17.0</version>
    </dependency>
</dependencies>
  1. Log4J implements system Logger
  2. System.Logger support from to Log4J.

The same logging fragment as above now outputs the following:

11:00:07.373 [main] INFO  c.f.b.DefaultLogger - Hello world!

To use SLF4J instead, you need to add the following dependencies:

<dependencies>
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-simple</artifactId>               <!-- 1 -->
        <version>2.0.0-alpha5</version>
    </dependency>
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-jdk-platform-logging</artifactId> <!-- 2 -->
        <version>2.0.0-alpha5</version>
    </dependency>
</dependencies>
  1. Basic SLF4J implementation. Any other implementation can, such as Logback.
  2. System.Logger support from to SLF4J.
[main] INFO c.f.b.DefaultLogger - Hello world!

Your own system Logger implementation

System.Logger relies on the ServiceLoader mechanism of Java. Both log4j JPL include slf4j JDK platform logging, a meta-inf / services / Java Lang. system $LoggerFinder points to the file implemented by LoggerFinder.


We can be based on system Out aims to create our own logging system\
The first step is to implement the log itself.

public class ConsoleLogger implements System.Logger {

    private final String name;

    public ConsoleLogger(String name) {
        this.name = name;
    }

    @Override
    public String getName() {
        return name;
    }

    @Override
    public boolean isLoggable(Level level) {
        return level.getSeverity() >= Level.INFO.getSeverity();
    }

    @Override
    public void log(Level level, ResourceBundle bundle, String msg, Throwable thrown) {
        if (isLoggable(level)) {
            System.out.println(msg);
            thrown.printStackTrace();
        }
    }

    @Override
    public void log(Level level, ResourceBundle bundle, String format, Object... params) {
        if (isLoggable(level)) {
            System.out.println(MessageFormat.format(format, params));
        }
    }
}

Then we need to write the code system LoggerFinder:

public class ConsoleLoggerFinder extends System.LoggerFinder {

    private static final Map<String, ConsoleLogger> LOGGERS = new HashMap<>(); // 1

    @Override
    public System.Logger getLogger(String name, Module module) {
        return LOGGERS.computeIfAbsent(name, ConsoleLogger::new);              // 2
    }
}

Keep the path of all existing logs:

  1. If it does not exist, create a logger and store it.

Finally, we create a service file:

ch.frankel.blog.ConsoleLoggerFinder

Now run the same snippet output:

Hello world!

conclusion

Although the api is more limited than other more mature logging system APIs, this is system Logger is a good choice. It provides APIs as part of the JDK. Therefore, it avoids the vulnerability risk of other third-party APIs, such as SLF4J to Log4J2.

For this reason, it can be based on system The logger implements the system log function.

Topics: Java Spring Back-end