Java 9 - Platform Logging API and Service

[Updated: Nov 26, 2017, Created: Nov 25, 2017]

The new Java 9 Platform Logging API allows applications and frameworks to route JDK internal logs to the desired logging framework (e.g. Log4j2, SLF4J etc). This is the similar to what SLF4J does via its bridges (an example here) but now it is built into JDK and is meant to redirect JDK internal logs to the desired logging destination.

From JEP 264:

The proposed service enables applications to configure the JDK to use the same logging framework as the application: It would only need to provide an implementation of the service that returns platform loggers that wrap the loggers of the preferred logging framework.

To make use of this new API, we need to get familiar with the following interfaces/classes:

System.LoggerFinder

This is a new nested class of java.lang.System. The implementation of this abstract class can be used to select a suitable logging implementation. We just need to implement one abstract method:

 public static abstract class LoggerFinder {
    public abstract Logger getLogger(String name, Module module);
 }

We need to register our implementation of above class as a service (based on ServiceLoader SPI) which will be discovered and used by JDK internal logging mechanism.

System.Logger

This is a new nested interface of java.lang.System. The implementation of this interface can be used to route JDK internal logs to the target logging framework implementation.

 public interface Logger {

        String getName();
        boolean isLoggable(Level level);
        void log(Level level, ResourceBundle bundle, String msg,
                Throwable thrown);
        void log(Level level, ResourceBundle bundle, String format,
                Object... params);

        //some default methods

          public enum Level {
            ALL(Integer.MIN_VALUE),  // typically mapped to/from j.u.l.Level.ALL
            TRACE(400),   // typically mapped to/from j.u.l.Level.FINER
            DEBUG(500),   // typically mapped to/from j.u.l.Level.FINEST/FINE/CONFIG
            INFO(800),    // typically mapped to/from j.u.l.Level.INFO
            WARNING(900), // typically mapped to/from j.u.l.Level.WARNING
            ERROR(1000),  // typically mapped to/from j.u.l.Level.SEVERE
            OFF(Integer.MAX_VALUE);  // typically mapped to/from j.u.l.Level.OFF
            private final int severity;
            private Level(int severity) {
                this.severity = severity;
            }
            public final String getName() {
                return name();
            }
            public final int getSeverity() {
                return severity;
            }
        }
    }

As seen above, the new classes provide a typical logging facility which can be used to map logs to any destination logging framework.

By default all JDK internal logs are mapped to Java Util logging.

Example

Following example shows how to redirect JDK internal logs to SLF4J (with logback implementation) framework.

SLF4J/Logback dependency

pom.xml

<dependency>
   <groupId>ch.qos.logback</groupId>
   <artifactId>logback-classic</artifactId>
   <version>1.2.3</version>
</dependency>

LoggerFinder implementation

package com.logicbig.example;

public class MyLoggerFinder extends System.LoggerFinder {
  @Override
  public System.Logger getLogger(String name, Module module) {
      return new Slf4jLogger(name, module);
  }
}

System.Logger implementation

package com.logicbig.example;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ResourceBundle;

public class Slf4jLogger implements System.Logger {
  private final Logger slf4jLogger;
  private final String name;

  public Slf4jLogger(String name, Module module) {
      this.name = name;
      slf4jLogger = LoggerFactory.getLogger(module.getName() + "-" + name);
  }

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

  @Override
  public boolean isLoggable(Level level) {
      //enable for all levels
      return true;
  }

  @Override
  public void log(Level level, ResourceBundle bundle, String msg, Throwable thrown) {
      //todo redirect to different methods based on level
      slf4jLogger.trace(msg);
  }

  @Override
  public void log(Level level, ResourceBundle bundle, String format, Object... params) {
      //todo use ResourceBundle.getString().
      //todo redirect to different methods based on level
      slf4jLogger.trace(format, params);
  }
}

Registering our LogFinder Service

src/main/resources/META-INF/services/java.lang.System$LoggerFinder

com.logicbig.example.MyLoggerFinder

Do not miss '$' for the service file name java.lang.System$LoggerFinder otherwise it won't work. Instead of Dot (.) separator, $ is used for the nested class qualified name.

Note that, if we are using Java 9 named modules we can alternatively register above service in our module-info.java (example here).

Logging configurations

In our example, we are going to use java.net.URLConnection, so we need to enable URLConnection logging in logging.properties file:

src/main/resources/logging.properties

handlers= java.util.logging.ConsoleHandler
java.util.logging.ConsoleHandler.level = FINEST
sun.net.www.protocol.http.HttpURLConnection.level=ALL

Since we are going to redirect logs to SLF4J/Logback, we need to provide logback.xml configuration as well:

src\main\resources\logback.xml

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss:SSS} %5p %t %c{2}:%L - %m%n</pattern>
        </encoder>
    </appender>
    <root level="TRACE">
        <appender-ref ref="stdout"/>
    </root>
</configuration>

Main class

package com.logicbig.example;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.URL;
import java.net.URLConnection;

public class ExampleMain {
  static {
      //loading logging.properties
      String path = ExampleMain.class.getClassLoader()
                                     .getResource("logging.properties")
                                     .getFile();
      System.setProperty("java.util.logging.config.file", path);
  }

  public static void main(String[] args) throws Exception {
      URL url = new URL("http://www.example.com/");
      URLConnection yc = url.openConnection();
      try (BufferedReader in = new BufferedReader(new InputStreamReader(
              yc.getInputStream()))) {
          // do something
          // in.lines().forEach(System.out::println);
      }
  }
}

Output

2017-11-25 23:24:14:096 TRACE main j.b.n.w.p.h.HttpURLConnection:36 - ProxySelector Request for http://www.example.com/
2017-11-25 23:24:14:117 TRACE main j.b.n.w.p.h.HttpURLConnection:36 - Proxy used: DIRECT
2017-11-25 23:24:14:118 TRACE main j.b.n.w.p.h.HttpURLConnection:36 - sun.net.www.MessageHeader@4c9f8c135 pairs: {GET / HTTP/1.1: null}{User-Agent: Java/9.0.1}{Host: www.example.com}{Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2}{Connection: keep-alive}
2017-11-25 23:24:14:132 TRACE main j.b.n.w.p.h.HttpURLConnection:36 - KeepAlive stream used: http://www.example.com/
2017-11-25 23:24:14:133 TRACE main j.b.n.w.p.h.HttpURLConnection:36 - sun.net.www.MessageHeader@4a22f9e211 pairs: {null: HTTP/1.1 200 OK}{Cache-Control: max-age=604800}{Content-Type: text/html}{Date: Sun, 26 Nov 2017 05:24:15 GMT}{Etag: "359670651+ident"}{Expires: Sun, 03 Dec 2017 05:24:15 GMT}{Last-Modified: Fri, 09 Aug 2013 23:54:35 GMT}{Server: ECS (ftw/FBA9)}{Vary: Accept-Encoding}{X-Cache: HIT}{Content-Length: 1270}

Without MyLoggerFinder registration, the above code will output the java util logging (example here).

New related methods in System class

 public static System.Logger getLogger(String name)
 public static System.Logger getLogger(String name, ResourceBundle bundle)

Above methods return the System.Logger instances via currently installer LoggerFinder service. These methods are meant to be used by JDK platform but since these are public API, applications are also free to use them. Libraries and frameworks will be more suitable candidates for this API where applications using those libraries/frameworks are not desired to be tied down with a particular logging implementation.

In above example setup, we can get named logger instances but their log messages will be redirected to Logback:

package com.logicbig.example;

public class SystemGetLoggerExample {
  private static System.Logger LOGGER = System.getLogger(SystemGetLoggerExample.class.getName());

  public static void main(String[] args) {
      LOGGER.log(System.Logger.Level.TRACE, "Just a test message");
  }
}

Output

2017-11-26 10:05:02:702 TRACE main n.l.e.SystemGetLoggerExample:36 - Just a test message

Example Project

Dependencies and Technologies Used :

  • logback-classic 1.2.3: logback-classic module.
  • JDK 9
  • Maven 3.3.9

Java 9 Platform Logging Example Select All Download
  • platform-logging-example
    • src
      • main
        • java
          • com
            • logicbig
              • example
        • resources
          • META-INF
            • services

See Also