bigywater.tech
Java · 404

System.Logger:模块化环境中的轻量日志接口

System.Logger 是 JDK 自带的、与具体日志框架无关的日志门面。这篇用真实运行回答:它默认走哪里、级别怎么映射、占位符怎么写、禁用时的开销,以及怎样用 LoggerFinder 把它接到自己的后端——并看到 JDK 自己也在用它。

JULWrapper
JDK 21 默认的 System.Logger 实现(包装 java.util.logging;ERROR 输出为 SEVERE)
0 vs 1000
级别被禁用时 Supplier 与直接传 expensive() 的求值次数
Runtime.exit() called with status: 3
自定义 LoggerFinder 收到的 JDK 内部日志(DEBUG 级别,logger 名 java.lang.Runtime)

默认后端

实测 · 本机执行输出
      Oct 05, 2026 6:31:31 PM Main main
      INFO: 消息 level=INFO
      Oct 05, 2026 6:31:31 PM Main main
      WARNING: 消息 level=WARNING
      Oct 05, 2026 6:31:31 PM Main main
      SEVERE: 消息 level=ERROR
      (RES) isLoggable:TRACE=false DEBUG=false INFO=true WARNING=true ERROR=true ALL=false OFF=true;Logger 实现类 sun.util.logging.internal.LoggingProviderImpl$JULWrapper;名字 demo.app

配置级别

实测 · 本机执行输出
      [FINER] demo.app: 消息 level=TRACE
      [FINE] demo.app: 消息 level=DEBUG
      [INFO] demo.app: 消息 level=INFO
      [WARNING] demo.app: 消息 level=WARNING
      [SEVERE] demo.app: 消息 level=ERROR
      (RES) isLoggable:TRACE=true DEBUG=true INFO=true WARNING=true ERROR=true ALL=true OFF=true;Logger 实现类 sun.util.logging.internal.LoggingProviderImpl$JULWrapper;名字 demo.app

惰性消息

实测 · 本机执行输出
      DEBUG 被禁用:1000 次 log(level, Supplier) 调用后 Supplier 被执行了 0 次
      DEBUG 被禁用:1000 次 log(level, String) 里直接调用 expensive():被执行了 1000 次
      INFO 启用:1000 次 log(level, Supplier):Supplier 被执行了 1000 次(且每次都会输出到 stderr)

占位符

实测 · 本机执行输出
      [INFO] 带占位符 A 和 42
      [INFO] 用 %s 和 %d 占位
      [INFO] 引号 '单引号' 与 x
      [INFO] 3.14 与 0
      [WARNING] 带异常
      java.lang.IllegalStateException: 出错了
      	at Main.main(Main.java:22)
      [INFO] 没有参数但含 {0}
      [INFO] null
      [INFO] p1 的参数是数组

禁用时的成本

实测 · 本机执行输出
      DEBUG 被禁用,5,000,000 次调用:log(DEBUG, Supplier)               11 ms(每次约 2.1 ns)
      DEBUG 被禁用,5,000,000 次调用:log(DEBUG, "x=" + i) 字符串拼接         54 ms(每次约 10.8 ns)
      DEBUG 被禁用,5,000,000 次调用:log(DEBUG, "x={0}", i) 参数形式        12 ms(每次约 2.5 ns)
      DEBUG 被禁用,5,000,000 次调用:if (isLoggable) log(...)           15 ms(每次约 3.1 ns)
      DEBUG 被禁用,5,000,000 次调用:不调用(空循环)                           11 ms(每次约 2.2 ns)

自定义 LoggerFinder

实测 · 本机执行输出
    不带 System.exit:
      [CUSTOM INFO] demo.app : 用自定义 LoggerFinder 记录的消息
      [CUSTOM DEBUG] demo.app : DEBUG 级别的消息
      [CUSTOM WARNING] other.name : 另一个 logger
      finder = MyFinder
    调用 System.exit(3),退出码 3:
      [CUSTOM INFO] demo.app : 用自定义 LoggerFinder 记录的消息
      [CUSTOM DEBUG] demo.app : DEBUG 级别的消息
      [CUSTOM WARNING] other.name : 另一个 logger
      finder = MyFinder
      [CUSTOM DEBUG] java.lang.Runtime (模块 java.base) : Runtime.exit() called with status: 3 + 异常 java.lang.Throwable: Runtime.exit(3)

分步演示

System.Logger
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / OpenJDK(Temurin 17.0.20、Homebrew 21.0.12 与 27,每篇注明实际用的版本);用 javac 编译、java 直接运行,需要第三方库时用 Maven 解析依赖;计时用 System.nanoTime,数字每次运行会有小幅抖动。JDK 21.0.12;默认后端与 Runtime.exit 日志是 JDK 21 的行为(其它版本可能不同);成本测量是 System.nanoTime 的单次 5,000,000 次循环(第 3 轮),不是 JMH
    • 没有验证:模块路径下以 provides … with 声明的 LoggerFinder、SLF4J / Log4j2 / Logback 提供的 LoggerFinder 适配器、ResourceBundle 本地化消息、JDK 的其它内部 logger(java.net.http 等)的输出、多线程下的性能
    京ICP备2026067502号-1京公网安备11010502063487号