关于 Java 9 的新特性从某本书的最后一个说起:平台日志 API。个人没感觉这个有什么实质的用途,所谓的平台日志是指 JDK 自身代码,或者是 JVM 组件中的日志输出,而在自己应用程序代码中却不会去用这个平台日志 API。这个所谓的 Platform Logging API 名称的意义也就是在这里,平台用的,在诊断时用来观察 JDK 类或 JVM 中的日志输出,比如应该可以截获到 JVM 本地代码实现中的日志输出。对我们在项目中如何处理日志并不会有什么影响,该怎么还是怎么,不过了解多一点东西应该不会浪费脑容量的。
新加的平台日志体现在 java.lang.System
中新加的几个方法和类
我们可以尝试着在代码使用一下它
System.Logger logger = System.getLogger(TestLogging.class.getName()); logger.log(System.Logger.Level.INFO, "Hello Java 9 Platform Logging API");
输出如下
May 26, 2018 10:56:51 AM cc.unmi.TestLogging main INFO: Hello Java 9 Platform Logging API
新 API 有
java.lang.System.Logger 接口
java.lang.System.LoggerFinder 用来查找上面 Logger 的实现类
java.lang.System.getLogger(...) 方法,它们会通过 LoggerFinder 找到相应的 Logger 实现
默认的,System.getLogger(...) 会使用 JDK 的 java.util.logging
(如果该模块存在时,即 JUL,它默认只输入 INFO 及更高级别的日志) 作为它的日志实现,所以前面的日志输出其实就是 JUL 的输出。见包 sun.util.logging.internal
中的
public final class LoggingProviderImpl extends DefaultLoggerFinder { ........ static final class JULWrapper extends LoggerConfiguration implements System.Logger, PlatformLogger.Bridge, PlatformLogger.ConfigurableBridge { ........ } }
System.LoggerFinder 和 System.Logger 默认就是这两个实现类。
如果 JUL 模块不存在, System.Logger
将把 INFO 及更高级别的日志输出到 System.err
标准错误输出。
由此我们看到 System.Logger
也就是作为 JUL 的一个门面,老实说它提供的日志输出方法还不如 JUL 的 java.util.logging.Logger
友好。 System.Logger
的每个日志方法都要带上日志级别
JUL
的 Logger 除了提供带日志级别为参数的通用 log(Level, ...)
方法外,还为每一个日志级别提供了像下面那些便利方法(以 warning 为例)
warning(String msg) warning(Supplier<String> msgSuppiler)
既然 System.Logger
是一个门面,那么它通过自定的 System.Logger
和 System.LoggerFinder
也能让它的日志桥接到其他日志框架上去,如 Apache Commong Logging(JCL - Jakarta Commons Logging), SLF4J, 或具体的日志实现 Log4j 或 Logback 去。
Java 9 新加这么一个日志框架门面显得有些累赘了, JUL
我们就不会去用它,有了更好的 SLF4J 更不会去用 System.Logger
。不过它也说了 JDK 自己的代码会用 System.Logger
, 那么我们有可能要做的就是如何把 JDK 中的 System.Logger
日志输出导向到我们熟悉的日志框架中来。
日志框架越发复杂了,应该说来,目前更广为人知的用法还是 SLF4J + Log4J 或 SLF4J + Logback, 我更推崇后者。至于 JCL + Log4J 的用法越发稀少,而 Logback 比之 JCL 是个新鲜事物,它基本就是为 SLF4J 设计的。当前业界为了各个组件中使用的不同日志框架而统一日志输出目标,产生了诸如以下适配器
jcl-over-slf4j, slf4j-jcl, log4j-over-slf4j, slf4j-log4j12, jul-to-slf4j
倒是没有找到 slf4j-over-jcl 这样的东西,因此 SLF4J 作为日志框架的门面还是当前趋势。简单项目中直接用 Log4J 或 Logback 也是可以的,Logback 自身就依赖了 SLF4J。
那么 Java 9 新推个 System.Logger
目的何在呢?可能也就是程序诊断上,基本不用去理会它,如果 JDK 自己用它来输出日志,那么就让它用默认的 JUL(当该模块存在时) 实现,我们只要用 jul-to-slf4j bridge
就能按需把 JDK 的日志输出到我们想要的地方去。
附:前面说了 JUL 默认只显示 INFO 或更高级别的日志,也就是 CONFIG, FINE, FINER, FINEST 的日志不会显示,那么要降低日志输出级别该怎么做呢?下面代码将开启所有日志输出
Logger rootLogger = LogManager.getLogManager().getLogger(""); rootLogger.setLevel(Level.ALL); for (Handler hander : rootLogger.getHandlers()) { hander.setLevel(Level.ALL); }
JUL 的日志级别还与 System.Logger
的日志级别还有差异(它们原本就不是一家人)
JUL 的日志级别:OFF, SEVERE, WARNING, INFO, CONFIG, FINE, FINER, FINEST, ALL
System.Logger 的日志级别分别是:OFF, ERROR, WARNING, INFO, DEBUG, TRACE, ALL ---- 这个更符合主流意识形态
当 System.Logger
默认采用 JUL 来输出日志时,它们的日志级别映射关系可查看 sun.util.logging.PlatformLogger
ALL(System.Logger.Level.ALL), FINEST(System.Logger.Level.TRACE), FINER(System.Logger.Level.TRACE), FINE(System.Logger.Level.DEBUG), CONFIG(System.Logger.Level.DEBUG), INFO(System.Logger.Level.INFO), WARNING(System.Logger.Level.WARNING), SEVERE(System.Logger.Level.ERROR), OFF(System.Logger.Level.OFF);
Java 9 的 Platform Logging API
基本上就说这么多了,只是让大家了解一下 Java 9 中有这么一个东西,它能做什么 事。并没有像某书中那样纠缠于如何自定义 System.Logger
和 System.LoggerFinder
,以及应用 SPI
来把平台日志输出到 Log4J
去,因为那么做的现实意义不大,我们项目中只需要专注于 SLF4J + Logback
的应用。
java.util.Currency
类中就使用到了 System.Logger
,
private static void info(String message, Throwable t) { PlatformLogger logger = PlatformLogger.getLogger("java.util.Currency"); if (logger.isLoggable(PlatformLogger.Level.INFO)) { if (t != null) { logger.info(message, t); } else { logger.info(message); } } }
当该类在初始化时如果不能解析文件 JAVA_HOME/lib/currency.properties
文件时就报错
static { ...... // look for the properties file for overrides String propsFile = System.getProperty("java.util.currency.data"); if (propsFile == null) { propsFile = System.getProperty("java.home") + File.separator + "lib" + File.separator + "currency.properties"; } try { File propFile = new File(propsFile); if (propFile.exists()) { ......//parse currency.properties file } } catch (IOException e) { info("currency.properties is ignored because of an IOException", e); } ...... }
所以假如我们创建文件 JAVA_HOME/lib/currency.properties
, 并在其中放入不合法的内容,如
ABadCurrencyFile
然后执行代码
Currency.getInstance("USD");
这会去触发 Currency
的 static 块,文件无法解析,控制台可以看到输出
May 26, 2018 1:19:15 PM java.util.Currency info INFO: currency.properties entry for ABADCURENCYFILE is ignored because of the invalid country code.
这就是平台日志的用法,还有新增的虚拟机参数 -Xlog
也是在控制平台的日志输出。