1. 概述
Java 9 引入了一个全新的平台日志 API —— System.Logger
和 System.LoggerFinder
,旨在统一 JDK 内部日志与应用程序日志的输出机制。✅
这个设计的初衷很明确:
让 JDK 自身的日志(比如模块系统、GC、JFR 等)能走应用层使用的同一套日志框架,避免项目里同时存在 java.util.logging
、log4j
、logback
等多种日志实现,减少依赖冲突和配置复杂度。
核心优势:
- ✅ JDK 日志可桥接到 SLF4J、Logback 等主流框架
- ✅ 提供标准服务接口,便于自定义实现
- ✅ 基于 Java 9 的模块系统(JPMS),服务发现更清晰
接下来我们通过两个实战案例,带你彻底搞懂这套 API 的用法。
2. 实现一个自定义日志器
要接入 Java 9 日志体系,你需要实现两个核心组件:System.Logger
和 System.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