1. Introduction
1.介绍
In this tutorial, we’ll explore the newly introduced Logging API in Java 9 and implement some examples to cover the most common cases.
在本教程中,我们将探讨Java 9中新引入的日志API,并实现一些例子来涵盖最常见的情况。
This API has been introduced in Java to provide a common mechanism to handle all the platform logs and to expose a service interface that can be customized by libraries and applications. This way, the JDK platform logs can use the same logging framework as the application, and the project dependencies can be reduced.
在Java中引入这个API是为了提供一个处理所有平台日志的通用机制,并暴露一个可由库和应用程序定制的服务接口。这样,JDK平台日志可以使用与应用程序相同的日志框架,并且可以减少项目依赖性。
2. Creating a Custom Implementation
2.创建一个自定义的实现
In this section, we’re going to show the main classes of the Logging API that we have to implement to create a new logger. We’ll do so by implementing a simple logger that prints all the logs to the console.
在这一节中,我们将展示创建一个新的日志记录器所必须实现的日志API的主要类。我们将通过实现一个简单的日志器来实现,该日志器将所有的日志打印到控制台。
2.1. Creating the Logger
2.1.创建记录器
The main class that we have to create is the Logger. This class has to implement the System.Logger interface and these four methods at least:
我们要创建的主类是Logger。这个类必须实现System.Logger接口和至少这四个方法。
- getName(): returns the name of the logger. It’ll be used by the JDK to create loggers by name
- isLoggable(): indicates what levels the logger is enabled for
- log(): it’s the method that prints the log to whatever underlying system the application is using- the console in our case. There are 2 log() methods to implement, each of them receiving different parameters
Let’s see how our implementation will look like:
让我们看看我们的实现将是什么样子的。
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));
}
}
Our ConsoleLogger class overrides the four methods mentioned. The getName() method returns a String, while the isLoggable() method returns true in all cases. Finally, we have the 2 log() method that output to the console.
我们的ConsoleLogger类重写了上述四个方法。getName()方法返回一个字符串,而isLoggable()方法在所有情况下都返回真。最后,我们有2个log()方法,输出到控制台。
2.2. Creating the LoggerFinder
2.2.创建LoggerFinder
Once we have our logger created, we need to implement a LoggerFinder that creates instances of our ConsoleLogger.
一旦我们创建了记录器,我们需要实现一个LoggerFinder,以创建我们ConsoleLogger的实例。
To do so, we have to extend the abstract class System.LoggerFinder and implement the getLogger() method:
要做到这一点,我们必须扩展抽象类System.LoggerFinder,并实现getLogger()方法。
public class CustomLoggerFinder extends System.LoggerFinder {
@Override
public System.Logger getLogger(String name, Module module) {
return new ConsoleLogger();
}
}
In this case, we’re always returning our ConsoleLogger.
在这种情况下,我们总是返回我们的ConsoleLogger。
Finally, we need to register our LoggerFinder as a Service so it can be discovered by the JDK. If we don’t provide an implementation, the SimpleConsoleLogger will be used by default.
最后,我们需要将我们的LoggerFinder注册为一个服务,这样它就能被JDK发现。如果我们不提供一个实现,SimpleConsoleLogger将被默认使用。
The mechanism used by the JDK to load the implementations is the ServiceLoader. You can find more information about it in this tutorial.
JDK用于加载实现的机制是ServiceLoader。你可以在本教程中找到关于它的更多信息。
Since we’re using Java 9, we’ll package our class in a module and register our service in the module-info.java file:
由于我们使用的是Java 9,我们将把我们的类打包成一个模块,并在module-info.java文件中注册我们的服务。
module com.baeldung.logging {
provides java.lang.System.LoggerFinder
with com.baeldung.logging.CustomLoggerFinder;
exports com.baeldung.logging;
}
For more information about Java modules, check out this other tutorial.
有关Java模块的更多信息,请查看这个其他教程。
2.3. Testing Our Example
2.3.测试我们的例子
To test our example, let’s create another module that will act as an application. This will only contain the Main class that uses our service implementation.
为了测试我们的例子,让我们创建另一个模块,它将充当一个应用程序。这将只包含使用我们服务实现的Main类。
This class will get an instance of our ConsoleLogger by calling the System.getLogger() method:
这个类将通过调用System.getLogger()方法获得我们的ConsoleLogger实例。
public class MainApp {
private static System.Logger LOGGER = System.getLogger("MainApp");
public static void main(String[] args) {
LOGGER.log(Level.ERROR, "error test");
LOGGER.log(Level.INFO, "info test");
}
}
Internally, the JDK will pick up our CustomLoggerFinder implementation and create an instance of our ConsoleLogger.
在内部,JDK将接收我们的CustomLoggerFinder实现并创建我们的ConsoleLogger.的实例。
After that, let’s create the module-info file for this module:
之后,让我们为这个模块创建module-info文件。
module com.baeldung.logging.app {
}
At this point, our project structure will look like this:
在这一点上,我们的项目结构将看起来像这样。
├── src
│ ├── modules
│ │ ├── com.baeldung.logging
│ │ │ ├── com
│ │ │ │ └── baeldung
│ │ │ │ └── logging
│ │ │ │ ├── ConsoleLogger.java
│ │ │ │ └── CustomLoggerFinder.java
│ │ │ └── module-info.java
│ │ ├── com.baeldung.logging.app
│ │ │ ├── com
│ │ │ │ └── baeldung
│ │ │ │ └── logging
│ │ │ │ └── app
│ │ │ │ └── MainApp.java
│ │ │ └── module-info.java
└──
Finally, we’re going to compile our two modules, and we’ll place them in a mods directory:
最后,我们要编译我们的两个模块,并将它们放在mods目录中。
javac --module-path mods -d mods/com.baeldung.logging \
src/modules/com.baeldung.logging/module-info.java \
src/modules/com.baeldung.logging/com/baeldung/logging/*.java
javac --module-path mods -d mods/com.baeldung.logging.app \
src/modules/com.baeldung.logging.app/module-info.java \
src/modules/com.baeldung.logging.app/com/baeldung/logging/app/*.java
Finally, let’s run the Main class of the app module:
最后,让我们运行app模块的Main类。
java --module-path mods \
-m com.baeldung.logging.app/com.baeldung.logging.app.MainApp
If we take a look at the console output we can see that our logs are printed using our ConsoleLogger:
如果我们看一下控制台输出,我们可以看到我们的日志是用我们的ConsoleLogger打印的。
ConsoleLogger [ERROR]: error test
ConsoleLogger [INFO]: info test
3. Adding an External Logging Framework
3.添加一个外部日志框架
In our previous example, we were logging all our messages to the console, which is the same as what the default logger does. One of the most useful uses of the Logging API in Java 9 is to let applications route the JDK logs to the same logging framework the application is using, and that’s what we’re going to do in this section.
在我们之前的例子中,我们把所有的消息都记录到了控制台,这与默认日志器的做法相同。Java 9中日志API最有用的用途之一是让应用程序将JDK日志路由到应用程序使用的相同的日志框架,这就是我们在本节要做的。
We’ll create a new module that uses SLF4J as logging facade and Logback as logging framework.
我们将创建一个新的模块,使用SLF4J作为日志界面,Logback作为日志框架。
Since we’ve already explained the basics in the previous section, now we can focus on how to add an external logging framework.
由于我们已经在上一节中解释了基础知识,现在我们可以专注于如何添加一个外部日志框架。
3.1. Custom Implementations Using SLF4J
3.1.使用SLF4J的自定义实现
First, we’ll implement another Logger that will create a new SLF4J logger for each instance:
首先,我们将实现另一个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;
}
//...
}
Notice that this Logger is an org.slf4j.Logger.
注意到这个Logger是一个org.slf4j.Logger.。
For the rest of the methods, we’ll rely on the implementation on the SLF4J logger instance. Therefore, our Logger will be enabled if the SLF4J logger is enabled:
对于其余的方法,我们将依赖于SLF4J日志器实例上的实现。因此,如果SLF4J日志器被启用,我们的Logger将被启用。
@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;
}
}
And the log methods will call the appropriate SLF4J logger method depending on the log level used:
而日志方法将根据所使用的日志级别调用适当的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;
// ...
// same as the previous switch
}
}
Finally, let’s create a new LoggerFinder that uses our Slf4jLogger:
最后,让我们创建一个新的LoggerFinder,使用我们的Slf4jLogger。
public class Slf4jLoggerFinder extends System.LoggerFinder {
@Override
public System.Logger getLogger(String name, Module module) {
return new Slf4jLogger(name);
}
}
3.2. Module Configuration
3.2.模块配置
Once we have all our classes implemented, let’s register our service in our module and add the dependency of the SLF4J module:
一旦我们实现了所有的类,让我们在我们的模块中注册我们的服务,并添加SLF4J模块的依赖性。
module com.baeldung.logging.slf4j {
requires org.slf4j;
provides java.lang.System.LoggerFinder
with com.baeldung.logging.slf4j.Slf4jLoggerFinder;
exports com.baeldung.logging.slf4j;
}
This module will have the following structure:
本模块将有以下结构。
├── src
│ ├── modules
│ │ ├── com.baeldung.logging.slf4j
│ │ │ ├── com
│ │ │ │ └── baeldung
│ │ │ │ └── logging
│ │ │ │ └── slf4j
│ │ │ │ ├── Slf4jLoggerFinder.java
│ │ │ │ └── Slf4jLogger.java
│ │ │ └── module-info.java
└──
Now we can compile this module into the mods directory as we did in the previous section.
现在我们可以像上一节那样把这个模块编译到mods目录中。
Notice that we have to place the slf4j-api jar in the mods directory to compile this module. Also, keep in mind to use a modularized version of the library. The latest version can be found in Maven Central.
请注意,我们必须将slf4j-api jar放在mods目录下才能编译这个模块。此外,请记住要使用模块化版本的库。最新版本可以在Maven Central中找到。
3.3. Adding Logback
3.3.添加Logback
We’re almost done, but we still need to add the Logback dependencies and configuration. To do so, place the logback-classic and logback-core jars in the mods directory.
我们几乎已经完成了,但我们仍然需要添加Logback的依赖和配置。为此,将logback-classic和logback-corejars放入mods目录。
As before, we have to make sure we’re using a modularized version of the library. Again, the latest version can be found in Maven Central.
和以前一样,我们必须确保我们使用的是模块化版本的库。同样,最新版本可以在Maven中心找到。
Finally, let’s create a Logback configuration file and place it in our mods directory:
最后,让我们创建一个Logback配置文件,并把它放在我们的mods目录下。
<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. Running Our Application
3.4.运行我们的应用程序
At this point, we can run our app using our SLF4J module.
在这一点上,我们可以使用SLF4J模块运行我们的app。
In this case, we also need to specify our Logback configuration file:
在这种情况下,我们还需要指定我们的 Logback 配置文件。
java --module-path mods \
-Dlogback.configurationFile=mods/logback.xml \
-m com.baeldung.logging.app/com.baeldung.logging.app.MainApp
Finally, if we check the output we can see that our logs are printed using our Logback configuration:
最后,如果我们检查输出,可以看到我们的日志是用我们的Logback配置打印的。
2018-08-25 14:02:40 [main] ERROR MainApp -- error test
2018-08-25 14:02:40 [main] INFO MainApp -- info test
4. Conclusion
4.结论
We’ve shown in this article how to create a custom logger in Java 9 by using the new Platform Logging API. Also, we’ve implemented an example using an external logging framework, which is one of the most useful use cases of this new API.
我们在本文中展示了如何通过使用新的平台日志API在Java 9中创建一个自定义的日志器。此外,我们还实现了一个使用外部日志框架的例子,这是这个新API最有用的使用案例之一。
As always, the full source code of the examples is available over on GitHub.
一如既往,这些示例的完整源代码可在GitHub上获得。