1. 简介
在本教程中,我们介绍Java 9中新引入的Logging API,并通过一些案例来涵盖最常见的情况。
Java中引入此API是为了提供一种通用机制来处理所有平台日志,并公开可由库和应用程序自定义的服务接口。这样,JDK平台日志就可以和应用程序使用同一个日志框架,减少项目依赖。
2. 创建自定义实现
在本节中,我们介绍我们必须实现以创建新记录器的Logging API的主要类,通过实现一个将所有日志打印到控制台的简单记录器来做到这一点。
2.1 创建记录器
我们必须创建的主要类是Logger,这个类至少要实现System.Logger接口和以下四个方法:
- getName():返回记录器的名称,JDK将使用它来按名称创建记录器
- isLoggable():指示记录器启用的级别
- log():它是将日志打印到应用程序使用的任何底层系统的方法,在我们的例子中是控制台。有2个log()方法可以实现,每个方法接收不同的参数
下面是具体的实现外观:
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, MessageFormat.format(format, params));
}
}
我们的ConsoleLogger类重写了上面提到的四个方法,getName()方法返回一个字符串,而isLoggable()方法在所有情况下都返回true,最后,我们有两个输出到控制台的log()方法。
2.2 创建LoggerFinder
一旦我们创建了记录器,我们需要实现一个LoggerFinder来创建我们的ConsoleLogger实例。
为此,我们必须扩展抽象类System.LoggerFinder并实现getLogger()方法:
public class CustomLoggerFinder extends System.LoggerFinder {
@Override
public System.Logger getLogger(String name, Module module) {
return new ConsoleLogger();
}
}
在这种情况下,我们总是返回我们的ConsoleLogger。
最后,我们需要将LoggerFinder注册为服务,以便它可以被JDK发现。如果我们不提供实现,默认情况下将使用SimpleConsoleLogger。
JDK用来加载实现的机制是ServiceLoader,你可以在本教程中找到有关它的更多信息。
由于我们使用的是Java 9,所以我们将把类打包在一个模块中,并在module-info.java文件中注册我们的服务:
module cn.tuyucheng.taketoday.logging {
provides java.lang.System.LoggerFinder
with cn.tuyucheng.taketoday.logging.CustomLoggerFinder;
exports cn.tuyucheng.taketoday.logging;
}
有关Java模块的更多信息,请查看此其他教程。
2.3 测试我们的例子
为了测试我们的示例,让我们创建另一个用作应用程序的模块,此模块只包含调用我们的服务实现的主类。
此类通过调用System.getLogger()方法获取我们的ConsoleLogger的实例:
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");
}
}
在内部,JDK将获取我们的CustomLoggerFinder实现并创建ConsoleLogger实例。
然后,我们为这个模块创建module-info文件:
module cn.tuyucheng.taketoday.logging.app {
}
此时,我们的项目结构如下:
├── src
│ ├── modules
│ │ ├── cn.tuyucheng.taketoday.logging
│ │ │ ├── cn
│ │ │ │ └── tuyucheng
| | | | └── taketoday
│ │ │ │ └── logging
│ │ │ │ ├── ConsoleLogger.java
│ │ │ │ └── CustomLoggerFinder.java
│ │ │ └── module-info.java
│ │ ├── cn.tuyucheng.taketoday.logging.app
│ │ │ ├── cn
│ │ │ │ └── tuyucheng
│ │ │ │ └── taketoday
| | | | └── logging
│ │ │ │ └── app
│ │ │ │ └── MainApp.java
│ │ │ └── module-info.java
└──
然后,编译我们的两个模块,并将它们放在mods目录中:
# 编译logging模块
javac --module-path mods -d mods/cn.tuyucheng.taketoday.logging src/modules/cn.tuyucheng.taketoday.logging/module-info.java src/modules/cn.tuyucheng.taketoday.logging/cn/tuyucheng/taketoday/logging/.java
# 编译logging slf4j模块
javac --module-path mods -d mods/cn.tuyucheng.taketoday.logging.slf4j src/modules/cn.tuyucheng.taketoday.logging.slf4j/module-info.java src/modules/cn.tuyucheng.taketoday.logging.slf4j/cn/tuyucheng/taketoday/logging/slf4j/.java
最后,我们运行app模块的Main类:
# 运行logging app
java --module-path mods -m cn.tuyucheng.taketoday.logging.app/cn.tuyucheng.taketoday.logging.app.MainApp
如果我们观察控制台输出,我们可以看到日志是使用我们的ConsoleLogger打印的:
ConsoleLogger [ERROR]: error test
ConsoleLogger [INFO]: info test
3. 添加外部日志框架
在我们之前的示例中,我们将所有消息记录到控制台,这与默认记录器的操作相同。Java 9中Logging API最有用的用途之一是让应用程序将JDK日志路由到应用程序正在使用的同一个日志框架,这就是我们在本节中要做的事情。
我们将创建一个使用SLF4J作为日志门面和Logback作为日志框架的新模块。由于我们已经在上一节中解释了基础知识,现在我们专注于如何添加外部日志框架。
3.1 使用SLF4J的自定义实现
首先,我们将实现另一个Logger,它将为每个实例创建一个新的SLF4J记录器:
public class Slf4jLogger implements System.Logger {
private final String name;
private final Logger logger;
public Slf4jLogger(String name) {
this.name = name;
logger = LoggerFactory.getLogger(name);
}
@Override
public String getName() {
return name;
}
//...
}
请注意,这个Logger是org.slf4j.Logger。
对于其余的方法,我们依赖于SLF4J Logger实例上的实现。因此,如果启用了SLF4J记录器,我们的记录器将被启用:
@Override
public boolean isLoggable(Level level) {
switch (level) {
case OFF:
return false;
case TRACE:
return logger.isTraceEnabled();
case DEBUG:
return logger.isDebugEnabled();
case INFO:
return logger.isInfoEnabled();
case WARNING:
return logger.isWarnEnabled();
case ERROR:
return logger.isErrorEnabled();
case ALL:
default:
return true;
}
}
并且log方法将根据使用的日志级别调用适当的SLF4J记录器方法:
@Override
public void log(Level level, ResourceBundle bundle, String msg, Throwable thrown) {
if (!isLoggable(level)) {
return;
}
switch (level) {
case TRACE:
logger.trace(msg, thrown);
break;
case DEBUG:
logger.debug(msg, thrown);
break;
case INFO:
logger.info(msg, thrown);
break;
case WARNING:
logger.warn(msg, thrown);
break;
case ERROR:
logger.error(msg, thrown);
break;
case ALL:
default:
logger.info(msg, thrown);
}
}
@Override
public void log(Level level, ResourceBundle bundle, String format, Object... params)
if (!isLoggable(level)) {
return;
}
String message = MessageFormat.format(format, params);
switch (level) {
case TRACE:
logger.trace(message);
break;
case DEBUG:
logger.debug(message);
break;
case INFO:
logger.info(message);
break;
case WARNING:
logger.warn(message);
break;
case ERROR:
logger.error(message);
break;
case ALL:
default:
logger.info(message);
}
}
最后,我们创建一个使用Slf4jLogger的新LoggerFinder:
public class Slf4jLoggerFinder extends System.LoggerFinder {
@Override
public System.Logger getLogger(String name, Module module) {
return new Slf4jLogger(name);
}
}
3.2 模块配置
一旦我们实现了所有类,接下来就是在模块中注册我们的服务并添加SLF4J模块的依赖项:
module cn.tuyucheng.taketoday.logging.slf4j {
requires org.slf4j;
provides java.lang.System.LoggerFinder
with cn.tuyucheng.taketoday.logging.slf4j.Slf4jLoggerFinder;
exports cn.tuyucheng.taketoday.logging.slf4j;
}
该模块的结构如下:
├── src
│ ├── modules
│ │ ├── cn.tuyucheng.taketoday.logging.slf4j
│ │ │ ├── cn
│ │ │ │ └── tuyucheng
| | | | └── taketoday
│ │ │ │ └── logging
│ │ │ │ └── slf4j
│ │ │ │ ├── Slf4jLoggerFinder.java
│ │ │ │ └── Slf4jLogger.java
│ │ │ └── module-info.java
└──
现在我们可以像上一节那样将此模块编译到mods目录中。
请注意,我们必须将slf4j-api jar放在mods目录中才能编译此模块。另外,请记住使用库的模块化版本。
3.3 添加Logback
至此,我们还需要添加Logback依赖项和配置;为此,请将logback-classic和logback-core的jar包放在mods目录中,和以前一样,我们必须确保我们使用的是模块化版本的库。
最后,让我们创建一个Logback配置文件并将其放在我们的mods目录中:
<?xml version="1.0" encoding="UTF-8"?>
<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>
<appender-ref ref="STDOUT"/>
</root>
</configuration>
3.4 运行我们的应用程序
此时,我们可以使用SLF4J模块运行我们的应用程序。在这种情况下,我们还需要指定Logback配置文件:
java --module-path mods
-Dlogback.configurationFile=mods/logback.xml
-m cn.tuyucheng.taketoday.logging.app/cn.tuyucheng.taketoday.logging.app.MainApp
最后,如果我们观察输出,我们可以看到日志是使用我们的Logback配置打印的:
2022-11-09 14:02:40 [main] ERROR MainApp -- error test
2022-11-09 14:02:40 [main] INFO MainApp -- info test
4. 总结
我们在本文中演示了如何使用新的平台日志记录器API在Java 9中创建自定义记录器。此外,我们还使用外部日志框架实现了一个示例,这是这个新API最有用的用例之一。
与往常一样,本教程的完整源代码可在GitHub上获得。