1. 概述

Java 9 引入了一个全新的平台日志 API —— System.LoggerSystem.LoggerFinder,旨在统一 JDK 内部日志与应用程序日志的输出机制。✅

这个设计的初衷很明确:
让 JDK 自身的日志(比如模块系统、GC、JFR 等)能走应用层使用的同一套日志框架,避免项目里同时存在 java.util.logginglog4jlogback 等多种日志实现,减少依赖冲突和配置复杂度。

核心优势:

  • ✅ JDK 日志可桥接到 SLF4J、Logback 等主流框架
  • ✅ 提供标准服务接口,便于自定义实现
  • ✅ 基于 Java 9 的模块系统(JPMS),服务发现更清晰

接下来我们通过两个实战案例,带你彻底搞懂这套 API 的用法。


2. 实现一个自定义日志器

要接入 Java 9 日志体系,你需要实现两个核心组件:System.LoggerSystem.LoggerFinder。⚠️ 缺一不可。

2.1 实现 System.Logger

System.Logger 是实际执行日志输出的接口。你需要实现以下四个方法:

public class ConsoleLogger implements System.Logger {

    @Override
    public String getName() {
        return "ConsoleLogger";
    }

    @Override
    public boolean isLoggable(Level level) {
        return true; // 简单粗暴:所有级别都记录
    }

    @Override
    public void log(Level level, ResourceBundle bundle, String msg, Throwable thrown) {
        System.out.printf("ConsoleLogger [%s]: %s - %s%n", level, msg, thrown);
    }

    @Override
    public void log(Level level, ResourceBundle bundle, String format, Object... params) {
        System.out.printf("ConsoleLogger [%s]: %s%n", level, 
          java.text.MessageFormat.format(format, params));
    }
}

📌 要点说明:

  • getName() 返回 logger 名称,JDK 会按名称缓存实例
  • isLoggable() 控制日志级别过滤,返回 false 则跳过输出
  • 两个 log() 方法分别处理异常和格式化字符串,注意使用 MessageFormat 而非 String.format

2.2 实现 System.LoggerFinder

LoggerFinder 是工厂类,负责创建 Logger 实例。你必须继承抽象类 System.LoggerFinder

public class CustomLoggerFinder extends System.LoggerFinder {

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

⚠️ 关键步骤:必须通过 ServiceLoader 注册该实现,否则 JDK 会 fallback 到默认的 SimpleConsoleLogger

在模块化项目中,通过 module-info.java 注册服务:

module com.example.logging {
    provides java.lang.System.LoggerFinder
        with com.example.logging.CustomLoggerFinder;
    exports com.example.logging;
}

📌 踩坑提示:
如果你用的是非模块化项目(class-path 模式),需要在 META-INF/services/java.lang.System$LoggerFinder 文件中写入实现类全名。

2.3 测试验证

写个简单的 MainApp 测试一下:

public class MainApp {
    private static final System.Logger LOGGER = System.getLogger("MainApp");

    public static void main(String[] args) {
        LOGGER.log(Level.ERROR, "error test");
        LOGGER.log(Level.INFO, "info test");
    }
}

编译并运行(假设模块已打包到 mods/ 目录):

java --module-path mods -m com.example.app/com.example.app.MainApp

输出结果:

ConsoleLogger [ERROR]: error test - null
ConsoleLogger [INFO]: info test

✅ 成功!说明 JDK 已加载你的 CustomLoggerFinder 并使用了 ConsoleLogger

项目结构参考:

src/
├── modules/
│   ├── com.example.logging/
│   │   ├── com/example/logging/ConsoleLogger.java
│   │   ├── com/example/logging/CustomLoggerFinder.java
│   │   └── module-info.java
│   └── com.example.app/
│       ├── com/example/app/MainApp.java
│       └── module-info.java

3. 集成外部日志框架(SLF4J + Logback)

真正实用的场景是把 JDK 日志桥接到 SLF4J,这样整个系统的日志都能统一由 Logback 控制。这才是 Java 9 日志 API 的核心价值所在。🔥

3.1 基于 SLF4J 的 Logger 实现

我们封装一个 Slf4jLogger,内部委托给 org.slf4j.Logger

public class Slf4jLogger implements System.Logger {
    private final String name;
    private final org.slf4j.Logger logger;

    public Slf4jLogger(String name) {
        this.name = name;
        this.logger = org.slf4j.LoggerFactory.getLogger(name);
    }

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

    @Override
    public boolean isLoggable(Level level) {
        return switch (level) {
            case OFF -> false;
            case TRACE -> logger.isTraceEnabled();
            case DEBUG -> logger.isDebugEnabled();
            case INFO -> logger.isInfoEnabled();
            case WARNING -> logger.isWarnEnabled();
            case ERROR -> logger.isErrorEnabled();
            case ALL -> true;
            default -> true;
        };
    }

    @Override
    public void log(Level level, ResourceBundle bundle, String msg, Throwable thrown) {
        if (!isLoggable(level)) return;

        switch (level) {
            case TRACE -> logger.trace(msg, thrown);
            case DEBUG -> logger.debug(msg, thrown);
            case INFO -> logger.info(msg, thrown);
            case WARNING -> logger.warn(msg, thrown);
            case ERROR -> logger.error(msg, thrown);
            default -> logger.info(msg, thrown);
        }
    }

    @Override
    public void log(Level level, ResourceBundle bundle, String format, Object... params) {
        if (!isLoggable(level)) return;
        String msg = java.text.MessageFormat.format(format, params);

        switch (level) {
            case TRACE -> logger.trace(msg);
            case DEBUG -> logger.debug(msg);
            case INFO -> logger.info(msg);
            case WARNING -> logger.warn(msg);
            case ERROR -> logger.error(msg);
            default -> logger.info(msg);
        }
    }
}

配套的 LoggerFinder

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

3.2 模块配置

确保你的模块依赖 slf4j-api 并导出服务:

module com.example.logging.slf4j {
    requires org.slf4j;
    provides java.lang.System.LoggerFinder
        with com.example.logging.slf4j.Slf4jLoggerFinder;
    exports com.example.logging.slf4j;
}

📌 注意:

  • 使用 modularized 版本 的 SLF4J(即 Automatic-Module-Name 在 manifest 中声明)
  • 推荐从 Maven Central 下载最新版

3.3 添加 Logback 支持

除了 slf4j-api,还需引入:

  • logback-classic
  • logback-core

同样放入 mods/ 目录,并确保是模块化版本。

创建 logback.xml 配置文件:

<configuration>
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>
                %d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} -- %msg%n
            </pattern>
        </encoder>
    </appender>

    <root level="INFO">
        <appender-ref ref="STDOUT"/>
    </root>
</configuration>

3.4 运行应用

启动时指定 Logback 配置文件路径:

java --module-path mods \
  -Dlogback.configurationFile=mods/logback.xml \
  -m com.example.app/com.example.app.MainApp

输出:

2025-04-05 10:20:30 [main] ERROR MainApp -- error test
2025-04-05 10:20:30 [main] INFO  MainApp -- info test

✅ 成功接入 Logback!所有日志格式、级别、输出方式均由 logback.xml 控制。


4. 总结

Java 9 的平台日志 API 虽然低调,但在大型项目中价值巨大:

  • ✅ 实现了 JDK 日志与应用日志的统一治理
  • ✅ 通过 LoggerFinder + ServiceLoader 实现灵活扩展
  • ✅ 无缝桥接到 SLF4J 生态,避免日志碎片化

📌 生产建议:

  • 推荐使用 SLF4J + Logback 方案,成熟稳定
  • 注意第三方库是否支持模块化,否则可能触发 requires transitive 问题
  • 非模块化项目可通过 META-INF/services 手动注册服务

示例代码已托管至 GitHub:https://github.com/baeldung/core-java-modules/tree/java-9-logging


原始标题:Java 9 Platform Logging API

« 上一篇: Java Weekly, 第244期