在对应用程序性能进行故障排除时,您掌握的信息越多越好。Java 日志与JVM 指标和跟踪相结合,使您可以全面了解 Java 应用程序。可以通过多种方式登录 Java 应用程序 - 例如,您可以只将数据写入文件,但正如我们在Java 日志记录教程中所解释的那样,还有更好的方法可以做到这一点。
今天,我们将研究Log4j 2,这是在 Apache 软件基金会下开发的广为人知的 Log4j 库的最新版本。
什么是 Log4j 2 以及为什么要使用它
Log4j 2及其前身Log4j是 Java 最常见和广为人知的日志记录框架。Log4j 2 承诺改进 Log4j 库的第一个版本并修复 Logback 框架中发现的一些问题。[R EAD我们的logback教程,如果你好奇,想了解更多关于如何这个库的作品。
它还支持异步日志记录。出于本教程的目的,我创建了一个使用 Log4j 2 的简单 Java 项目,您可以在我们的Github帐户中找到它。请记住,Log4j 1.x 已弃用且不再维护,因此在选择您选择的日志库时,您应该选择 Log4j 2.x。
让我们从我们将用于测试的代码开始:
package com.sematext.blog.logging;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class Log4JDefaultConfig {
private static final Logger
LOGGER = LogManager.getLogger(Log4JDefaultConfig.class);
public static void main(String[] args) { LOGGER.info("This is an INFO level log message!");
LOGGER.error("This is an ERROR level log message!");
}
}
为了编译代码,我们需要两个依赖项——Log4j 2 API 和 Core。我们的 build.gradle 包括以下内容:
dependencies {
implementation 'org.apache.logging.log4j:log4j-api:2.13.3'
implementation 'org.apache.logging.log4j:log4j-core:2.13.3'
}
这里的事情很简单——我们通过使用 Log4j 2 LogManager 类及其 getLogger 方法来获取 Logger 实例。一旦我们有了它,我们就使用 Logger 的 info 和 error 方法来写入日志记录。我们当然可以使用 SLF4J,我们将在其中一个示例中使用。不过现在,让我们坚持使用纯 Log4j 2 API。
与 java.util.logging 相比,我们无需进行任何设置。默认情况下,Log4j 2 将使用ConsoleAppender将日志消息写入控制台。日志记录将使用附加到上述 ConsoleAppander的PatternLayout打印,模式定义如下:%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} – %msg%n。但是,根记录器是为 ERROR 级别定义的。这意味着,默认情况下,错误级别的日志及更高级别的日志将仅可见。级别为 INFO、DEBUG、TRACE 的日志消息将不可见。
Log4j 2 将使用默认配置的情况有多种,但一般来说,当应用程序启动参数中不存在log4j.configurationFile属性并且该属性未指向有效的配置文件时,您可以预期它会启动. 此外,类路径中不存在log4j2-test.[properties|xml|yaml|jsn]文件,并且类路径中不存在log4j2.[properties|xml|yaml|jsn]文件。是的,这意味着您可以轻松地为测试使用不同的日志配置,将不同的配置作为代码库的默认配置,然后使用log4j.configurationFile属性并指向给定环境的配置。
一个简单的测试——运行上面的代码给出以下输出:
09:27:10.312 [main] ERROR com.sematext.blog.logging.Log4JDefaultConfig - This is an ERROR level log message!
当然,除了简单的代码之外,这种默认行为对于几乎任何事情都不够,所以让我们看看我们可以做些什么。
Log4j 2 配置
Log4j 2 可以通过以下两种方式之一进行配置:
- 通过使用配置文件。默认情况下,Log4j 2 理解在 Java 属性文件和 XML 文件中编写的配置,但您也可以包含其他依赖项以使用 JSON 或 YAML。在这篇博文中,我们将使用这种方法。
- 通过创建 ConfigurationFactory 和 Configuration 实现,或通过使用 Configuration 接口中公开的 API 或通过调用内部 Logger 方法,以编程方式进行。
让我们首先创建一个简单的老式 XML 配置文件,以打印与日志记录关联的时间、级别和消息。Log4j 2 要求我们调用该文件 log4j2.xml。我们将它放在示例项目的资源文件夹中并运行它。这次的输出如下所示:
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{HH:mm:ss.SSS} %-5level - %msg%n"/>
</Console>
</Appenders>
<Loggers>
<Root level="info">
<AppenderRef ref="Console"/>
</Root>
</Loggers>
</Configuration>
让我们在这里停留一两分钟,讨论我们在这里做了什么。我们从配置定义开始。在这个元素中,我们定义了所有与状态相关的消息都将记录在 WARN 级别 – status=”WARN”。
接下来,我们定义了 appender。Appender 负责将 LogEvent 传送到其目的地。你可以有多个。在我们的例子中,Appenders 部分包含一个 Console 类型的 appender:
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{HH:mm:ss.SSS} %-5level - %msg%n"/>
</Console>
我们设置它的名称,稍后我们将使用它,我们说目标是标准系统输出 - SYSTEM_OUT。我们还为 PatternLayout 提供了模式,它定义了我们的 LogEvent 在 Console appender 中的格式化方式。
最后一部分定义了记录器。我们定义了在我们的代码或我们正在使用的库中定义的不同记录器的配置。在我们的例子中,这部分只包含根记录器定义:
<Root level="info">
<AppenderRef ref="Console"/>
</Root>
根记录器定义告诉 Log4j 在未找到记录器的专用配置时使用该配置。在我们的 Root logger 定义中,我们说默认日志级别应该设置为 INFO,并且日志事件应该发送到名为 Console 的 appender。
如果我们使用新的 XML 配置运行上面提到的示例代码,输出将如下所示:
09:29:58.735 INFO - This is an INFO level log message!
09:29:58.737 ERROR - This is an ERROR level log message!
正如我们预期的那样,我们在控制台中出现了两条日志消息。
如果我们想使用属性文件而不是 XML 文件,我们可以创建一个 log4j2.properties 文件并将其包含在类路径中,或者只使用 log4j.configuration 属性并将其指向我们选择的属性文件。为了获得与基于 XML 的配置类似的结果,我们将使用以下属性:
appender.console.type = Console
appender.console.name = STDOUT
appender.console.layout.type = PatternLayout
appender.console.layout.pattern = %d{HH:mm:ss.SSS} %-5level - %msg%n
rootLogger.level = info
rootLogger.appenderRef.stdout.ref = STDOUT
它不那么冗长且有效,但如果您想使用稍微复杂的功能,它也不太灵活。
Log4j 2 记录器
Logger 是我们的应用程序用来创建 LogRecord 的主要实体,所以基本上是记录我们想要作为日志消息输出的内容。创建记录器非常简单。在大多数情况下,它将是类中的私有静态对象,例如:
private static final Logger LOGGER = LogManager.getLogger(SomeFancyClassName.class);
上面的代码部分使用 Log4j 2 类来创建 Logger 对象,但您也可以基于 SLF4J 进行初始化,因此您可以随时更改您选择的日志记录框架:
私有静态最终记录器 LOGGER = LoggerFactory.getLogger(SomeFancyClassName.class);
无论我们选择哪种初始化方法,一旦我们有了 Logger 实例,我们就可以使用它来生成我们的日志事件:
LOGGER.info("This is an INFO level log message");
Log4j 2 日志级别
一个日志级别或日志严重性个资料片讲述的重要一给定的日志事件是如何。这是一种简单但非常强大的区分日志事件的方法。如果在您的应用程序中正确使用了日志级别,您需要首先查看严重性。它会告诉您是否可以在待命之夜继续睡觉,或者您是否需要立即起床并在卧室和客厅的笔记本电脑之间跑来跑去再创佳绩。
您可以将日志级别视为一种过滤有关系统状态的关键信息和纯粹提供信息的方式。日志级别有助于减少信息噪音和减少警报疲劳。
如果您不熟悉日志级别的概念,我们建议您查看我们的日志级别教程,我们在其中详细解释了它们的工作原理以及使用它们的最佳方法。
Log4j 2 附加程序
Log4j 2 appender负责将 LogEvents 传送到目的地。一些 appender 只发送 LogEvents,而其他 appender 包装其他 appender 以提供附加功能。让我们看看 Log4j 2 中可用的一些附加程序。请记住,这不是附加程序的完整列表,要查看所有附加程序,请查看Log4j 2 附加程序文档。
Log4j 2 中不那么完整的附加程序列表:
- ConsoleAppender – 将数据写入 System.out 或 System.err,默认为第一个(登录容器时的Java 最佳实践)
- FileAppender – 使用 FileManager 将数据写入定义文件的附加程序
- RollingFileAppender – 将数据写入定义的文件并根据定义的策略滚动文件的附加程序
- MemoryMappedFileAppender – 2.1 版本新增,使用内存映射文件并依赖操作系统虚拟内存管理器将文件中的更改与存储设备同步
- FlumeAppender – 将数据写入Apache Flume 的appender
- CassandraAppender – 将数据写入Apache Cassandra 的appender
- JDBCAppender – 使用标准 JDBC 驱动程序将数据写入数据库
- HTTPAppender – 将数据写入定义的 HTTP 端点
- KafkaAppender – 将数据写入Apache Kafka
- SyslogAppender – 将数据写入与Syslog兼容的目的地
- ZeroMQAppender – 将数据写入ZeroMQ
- AsyncAppender – 封装另一个 appender 并使用不同的线程写入数据,从而导致异步日志记录
Appender 配置
现在让我们看几个示例,说明如何为 Log4j 2 配置 appender。
使用多个 Appender – 文件和控制台一起使用
现在让我们看看File appender,看看如何同时将日志消息记录到控制台和文件。
假设我们有以下用例——我们希望将我们的应用程序设置为将所有内容都记录到一个文件中,以便我们可以使用它来将数据传送到外部日志管理和分析解决方案,例如Sematext Logs,我们还希望拥有来自 com.sematext.blog loggers 的日志打印到控制台。
我将使用如下所示的相同示例应用程序:
package com.sematext.blog.logging;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class Log4j2 { private static final Logger
LOGGER = LogManager.getLogger(Log4j2.class);
public static void main(String[] args) {
LOGGER.info("This is an INFO level log message!");
LOGGER.error("This is an ERROR level log message!");
}
}
该log4j2.xml将如下所示:
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{HH:mm:ss.SSS} %-5level - %msg%n"/>
</Console>
<File name="File" fileName="/tmp/log4j2.log" append="true">
<PatternLayout>
<Pattern>%d{HH:mm:ss.SSS} [%t] %-5level - %msg%n</Pattern>
</PatternLayout>
</File>
</Appenders>
<Loggers>
<Logger name="com.sematext.blog" level="info" additivity="true">
<AppenderRef ref="Console"/>
</Logger>
<Root level="info">
<AppenderRef ref="File"/>
</Root>
</Loggers>
</Configuration>
您可以看到我定义了两个附加程序——一个称为控制台,将数据附加到 System.out 或 System.err。第二个叫做文件。我提供了一个 fileName 配置属性来告诉 appender 数据应该写在哪里,我说我想将数据附加到文件的末尾。请记住,这两个 appender 的模式略有不同 - File 一个也记录线程,而 Console 一个没有。
我们还定义了两个记录器。我们有将数据附加到我们的文件附加器的根记录器。它对具有 INFO 日志级别和更高级别的所有日志事件执行此操作。我们还有一个名称为 com.sematext.blog 的 Logger,它使用 Console appender 以及 INFO 级别附加数据。
运行上述代码后,我们将在控制台上看到以下输出:
15:19:54.730 INFO - This is an INFO level log message!
15:19:54.732 ERROR - This is an ERROR level log message!
/tmp/log4j2.log 文件中的以下输出:
15:19:54.730 [main] INFO - This is an INFO level log message!
15:19:54.732 [main] ERROR - This is an ERROR level log message!
您可以看到时间完全相同,线条不同,但就像我们的模式一样。
在正常的生产环境中,您可能会使用滚动文件附加程序每天或在文件达到特定大小时滚动文件。
系统日志附加程序
有用的 appender 之一是 SyslogAppender,它可以将我们的应用程序生成的日志事件发送到与Syslog兼容的目的地。这可以是您的本地 Syslog 服务器、组织中的远程 Syslog 服务器,甚至可以是作为日志集中解决方案的一部分公开的服务器,例如Sematext Logs。
例如,以下配置将向作为Sematext Cloud一部分公开的 Syslog 服务器发送日志消息:
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="warn">
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d %level [%t] %c - %m%n"/>
</Console>
<Syslog
name="Syslog"
host="logsene-syslog-receiver.sematext.com"
port="514"
protocol="TCP"
format="RFC5424"
appName="ab6253c2-f76s-23s1-5dwx-d221dswf232s"
facility="LOCAL0"
mdcId="mdc"
newLine="true"/>
</Appenders>
<Loggers>
<Root level="debug">
<AppenderRef ref="Console"/>
<AppenderRef ref="Syslog"/>
</Root>
</Loggers>
</Configuration>
你可以看到我们定义了两个 appender。一个是已知的——控制台附加程序将日志事件写入标准输出。第二个是 Syslog appender,它将数据发送到 logsene-syslog-receiver.sematext.com 上可用的 Syslog 兼容目的地。我们还包括一些配置属性,如端口、协议和格式。如您所见,将日志事件从 Java 应用程序发送到 Syslog 非常简单。
自定义 Appender
如果 Log4j 2 自带的 appender 不够用,我们可以创建自己的实现。我们只需要扩展抽象 appender 类之一,例如 AbstractAppender 并实现 append 和 createAppender 方法。例如:
package com.sematext.blog.logging;
import org.apache.logging.log4j.core.Appender;
import org.apache.logging.log4j.core.Core;
import org.apache.logging.log4j.core.Filter;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.appender.AbstractAppender;
import org.apache.logging.log4j.core.config.plugins.Plugin;
import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
import org.apache.logging.log4j.core.config.plugins.PluginElement;
import org.apache.logging.log4j.core.config.plugins.PluginFactory;
@Plugin(name="TestAppender", category=Core.CATEGORY_NAME, elementType=Appender.ELEMENT_TYPE)
public class TestAppender extends AbstractAppender {
protected TestAppender(String name, Filter filter) {
super(name, filter, null, false, null);
}
@PluginFactory
public static TestAppender createAppender(@PluginAttribute("name") String name, @PluginElement("Filter") Filter filter) {
return new TestAppender(name, filter);
}
@Override
public void append(LogEvent event) {
// do some custom append code
}
}
您可以看到我创建了扩展 AbstractAppender 的 TestAppender 类。append 方法是负责处理 LogEvent 对象的方法。createAppender 方法负责创建我们的 appender 的实例。我们还使用了 @Plugin 和 @PluginFactory 注释来告诉 Log4j 如何创建 appender 本身。
最后,我们只需要配置 Log4j 以包含我们的插件。我们这样做:
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN" xmlns:xi="http://www.w3.org/2001/XInclude" packages="com.sematext.blog.logging">
<Appenders>
<TestAppender name="TestAppender" />
</Appenders>
<Loggers>
<Root level="info">
<AppenderRef ref="TestAppender"/>
</Root>
</Loggers>
</Configuration>
你可以看到我们已经添加了我们的 appender。要记住的关键是在根配置标记中包含包部分,以便 Log4j 知道在哪里查找插件。
Log4j 2 布局
appender 使用布局将 LogEvent 格式化为定义的表单。
默认情况下,Log4j 2 中有一些可用的布局(其中一些需要额外的运行时依赖项):
- 模式- 使用字符串模式来格式化日志事件(了解有关模式布局的更多信息)
- CSV – 以 CSV 格式写入数据的布局
- GELF – 以 Graylog 扩展日志格式 1.1 写入事件的布局
- HTML – 以 HTML 格式写入数据的布局
- JSON – 以 JSON 格式写入数据的布局
- RFC5424 – 根据RFC 5424 – 扩展系统日志格式写入数据
- 序列化- 使用 Java 序列化将日志事件序列化为字节数组
- Syslog – 将日志事件格式化为 Syslog 兼容格式
- XML – 以 XML 格式写入数据的布局
- YAML – 以 YAML 格式写入数据的布局
模式布局
Log4j PatternLayout是一个强大的工具。它允许我们在不需要编写任何代码的情况下构建我们的日志事件——我们只需要定义我们将使用的模式并将其包含在我们的配置中。就是这么简单。
让我们从一个简单的配置开始,它将记录事件的时间和消息。配置如下所示:
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{HH:mm:ss.SSS} %msg%n"/>
</Console>
</Appenders>
<Loggers>
<Root level="info">
<AppenderRef ref="Console"/>
</Root>
</Loggers>
</Configuration>
运行示例代码的输出如下:
13:44:28.397 This is an INFO level log message!
您可以在那里看到两件事 - %d 和 %msg 变量。%d 负责日志事件日期,%msg 负责我们作为日志事件传递的消息。如您所见,可以使用模式进一步配置与事件相关的日期。您可以在与PatternLayout相关的文档中了解这一点。
但是当然 %d 和 %msg 变量并不是我们可以使用的所有变量。我们使用了记录器名称、类、包含异常、配置突出显示颜色、消息位置等等。所有这些都在与PatternLayout相关的文档中进行了描述。但为了给你一个更复杂的例子,这里是另一种配置:
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%-5level] %c:%L [PID:%pid] - %msg%n"/>
</Console>
</Appenders>
<Loggers>
<Root level="info">
<AppenderRef ref="Console"/>
</Root>
</Loggers>
</Configuration>
这次 Log4j 2 生成的日志事件将如下所示:
13:56:16.502 [INFO ] com.sematext.blog.logging.Log4j2:10 [PID:44993] - This is an INFO level log message!
您可以看到,除了日期和消息之外,我们还有消息的级别、类和产生日志事件的行。我们甚至包括了进程标识符。
HTML 布局
如果我们希望将日志格式化为 HTML,我们可以使用以下配置:
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<HTMLLayout>
</HTMLLayout>
</Console>
</Appenders>
<Loggers>
<Root level="info">
<AppenderRef ref="Console"/>
</Root>
</Loggers>
</Configuration>
使用上述配置运行示例代码的输出非常冗长,如下所示:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html>
<head>
<meta charset="UTF-8"/>
<title>Log4j Log Messages</title>
<style type="text/css">
<!-- body, table {font-family:arial,sans-serif; font-size: medium ;}th {background: #336699; color: #FFFFFF; text-align: left;} -->
</style>
</head>
<body bgcolor="#FFFFFF" topmargin="6" leftmargin="6">
<hr size="1" noshade="noshade">
Log session start time Wed Jul 01 15:45:08 CEST 2020
<br>
<br>
<table cellspacing="0" cellpadding="4" border="1" bordercolor="#224466" width="100%">
<tr>
<th>Time</th>
<th>Thread</th>
<th>Level</th>
<th>Logger</th>
<th>Message</th>
</tr>
<tr>
<td>652</td>
<td title="main thread">main</td>
<td title="Level">INFO</td>
<td title="com.sematext.blog.logging.Log4j2 logger">com.sematext.blog.logging.Log4j2</td>
<td title="Message">This is an INFO level log message!</td>
</tr>
<tr>
<td>653</td>
<td title="main thread">main</td>
<td title="Level"><font color="#993300"><strong>ERROR</strong></font></td>
<td title="com.sematext.blog.logging.Log4j2 logger">com.sematext.blog.logging.Log4j2</td>
<td title="Message">This is an ERROR level log message!</td>
</tr>
</table>
<br>
</body>
</html>
Log4j 2 过滤器
过滤器允许检查日志事件以确定是否或如何发布它们。过滤器执行可以以三个值之一结束 - ACCEPT、DENY 或 NEUTRAL。
可以在以下位置之一配置过滤器:
- 直接在上下文的配置中 – 宽过滤器
- 在 Logger 中进行特定于记录器的过滤
- 在 Appender 中进行 appender 特定过滤
- 在 Appender 参考中确定日志事件是否应该到达给定的 appender
有一些开箱即用的过滤器,我们也可以开发自己的过滤器。开箱即用的有:
- Burst – 提供一种机制来控制处理日志事件的速率,并在达到限制时静默丢弃它们
- Composite – 提供组合多个过滤器的机制
- 阈值- 允许过滤日志事件的日志级别
- 动态阈值 - 类似于阈值过滤器,但允许包含其他属性,例如用户
- Map – 允许过滤 MapMessage 中的数据
- Marker – 将过滤器中定义的 Marker 与日志事件中的 Marker 进行比较,并在此基础上进行过滤
- 无标记 - 允许检查日志事件中是否存在标记,并根据该信息进行过滤
- Regex – 根据定义的正则表达式过滤
- 脚本- 执行应返回布尔结果的脚本
- 结构化数据 – 允许过滤日志事件的 id、类型和消息
- 线程上下文映射——允许过滤当前上下文中的数据
- 时间- 允许将日志事件限制在特定时间
阈值过滤器
例如,如果我们想要包含级别为 WARN 或更高级别的所有日志,我们可以使用具有以下配置的 ThresholdFilter:
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{HH:mm:ss.SSS} %-5level - %msg%n"/>
<ThresholdFilter level="WARN" onMatch="ACCEPT" onMismatch="DENY"/>
</Console>
</Appenders>
<Loggers>
<Root level="info">
<AppenderRef ref="Console"/>
</Root>
</Loggers>
</Configuration>
使用上述 Log4j 2 配置运行我们的示例代码将产生以下输出:
16:34:30.797 ERROR - This is an ERROR level log message!
Log4j 2 无垃圾日志记录
Log4j 2.6 引入了部分实现的无垃圾日志记录。该框架重用存储在 ThreadLocal 中的对象,并在将文本转换为字节时尝试重用缓冲区。
在 2.6 版本的框架中,有两个属性用于控制 Log4j 2 的无垃圾日志记录功能。log4j2.enableThreadlocals对于非 Web 应用程序默认设置为 true,而log4j2.enableDirectEncoders也设置为默认情况下为 true 是在 Log4j 2 中启用优化的那些。
在 2.7 版本的框架中,添加了第三个属性 - log4j2.garbagefreeThreadContextMap。如果设置为 true,则 ThreadContext 映射也将使用无垃圾方法。默认情况下,此属性设置为 false。
Log4j 2 中的无垃圾日志记录有一系列限制。并非所有过滤器、附加器和布局都可用。如果您决定使用它,请查看有关该主题的Log4j 2 文档。
对于关键任务系统,调整Java 垃圾收集过程对于稳定性和效率至关重要。拥有一个日志库,可以潜在地生成许多不同的 String 实例,支持无垃圾日志记录是一个受到很多开发人员和 DevOps 欢迎的特性。
Log4j 2 异步日志记录
异步日志记录是 Log4j 2 的新增功能。目的是通过在单独的线程中执行日志记录操作,尽快从 Logger.log 方法调用返回到应用程序。
使用异步日志记录有几个优点和缺点。好处是:
- 更高的峰值吞吐量
- 更低的日志响应时间延迟
也有缺点:
- 复杂的错误处理
- 不会在 CPU 数量较少的机器上提供更高的性能
- 如果您记录的日志超过了 appender 可以处理的数量,那么由于队列已满,日志速度将由最慢的 appender 专用
如果此时您仍然考虑打开异步日志记录,则需要注意需要额外的运行时依赖项。Log4j 2 使用Disruptor库并要求将其作为运行时依赖项。您还需要将log4j2.contextSelector系统属性设置为org.apache.logging.log4j.core.async.AsyncLoggerContextSelector。
Log4j 2 线程上下文
Log4j 将 Mapped Diagnostic Context 与 Thread Context 中的 Nested Diagnostic Context 结合起来。但让我们分别讨论一下。
的映射诊断上下文或MDC基本上是可以被用于存储在历境正在运行的特定线程的上下文数据的地图。例如,我们可以存储用户标识符或算法的步骤。在 Log4j 2 而不是 MDC 中,我们将使用线程上下文。线程上下文用于将多个事件与有限数量的信息相关联。
所述嵌套诊断上下文或NDC类似于MDC,但可以用来区分来自不同源的交织日志输出,所以在情况下的服务器或应用程序处理多个客户端在一次。在 Log4j 2 中,我们将使用线程上下文堆栈而不是 NDC。
让我们看看如何使用它。我们想要做的是在我们的日志消息中添加额外的信息——用户名和算法的步骤。这可以通过以下方式使用线程上下文来完成:
package com.sematext.blog.logging;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.ThreadContext;
public class Log4j2ThreadContext {
private static final Logger
LOGGER = LogManager.getLogger(Log4j2ThreadContext.class);
public static void main(String[] args) {
ThreadContext.put("user", "rafal.kuc@sematext.com");
LOGGER.info("This is the first INFO level log message!");
ThreadContext.put("executionStep", "one");
LOGGER.info("This is the second INFO level log message!");
ThreadContext.put("executionStep", "two");
LOGGER.info("This is the third INFO level log message!");
}
}
为了在日志消息中显示来自线程上下文的附加信息,我们需要不同的配置。我们用来实现这种外观的配置如下:
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%X{user}] [%X{executionStep}] %-5level - %msg%n"/>
</Console>
</Appenders>
<Loggers>
<Root level="info"> <AppenderRef ref="Console"/> </Root>
</Loggers>
</Configuration>
您可以看到我们已将 [%X{user}] [%X{executionStep}] 部分添加到我们的模式中。这意味着我们将从线程上下文中获取 user 和 executionStep 属性值并将其包含在内。如果我们想包括所有存在的,我们只需添加 %X。
使用上述配置运行上述代码的输出如下:
18:05:40.181 [rafal.kuc@sematext.com] [] INFO - This is the first INFO level log message!
18:05:40.183 [rafal.kuc@sematext.com] [one] INFO - This is the second INFO level log message!
18:05:40.183 [rafal.kuc@sematext.com] [two] INFO - This is the third INFO level log message!
您可以看到,当上下文信息可用时,它会与日志消息一起写入。它将一直存在,直到它被更改或清除。
什么是标记
标记是用于丰富数据的命名对象。线程上下文用于为日志事件提供附加信息,而标记可用于标记单个日志语句。您可以想象用 IMPORTANT 标记来标记日志事件,这意味着 appender 应该例如将事件存储在单独的日志文件中。
怎么做?查看以下使用过滤来实现此目的的代码示例:
package com.sematext.blog.logging;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.Marker;
import org.slf4j.MarkerFactory;
public class Log4J2MarkerFiltering {
private static Logger LOGGER = LoggerFactory.getLogger(Log4J2MarkerFiltering.class);
private static final Marker IMPORTANT = MarkerFactory.getMarker("IMPORTANT");
public static void main(String[] args) {
LOGGER.info("This is a log message that is not important!");
LOGGER.info(IMPORTANT, "This is a very important log message!");
}
}
上面的示例使用 SLF4J 作为选择的 API,使用 Log4j 2 作为日志记录框架。这意味着 Gradle 构建文件的依赖项部分如下所示:
dependencies {
implementation 'org.slf4j:slf4j-api:1.7.30'
implementation 'org.apache.logging.log4j:log4j-api:2.13.3'
implementation 'org.apache.logging.log4j:log4j-core:2.13.3'
implementation 'org.apache.logging.log4j:log4j-slf4j-impl:2.13.3'
}
上面的代码示例中有两件重要的事情。首先,我们正在创建一个静态标记:
私有静态最终标记重要 = MarkerFactory.getMarker(“重要”);
我们使用 MarkerFactory 类通过调用它的 getMarker 方法并提供标记的名称来做到这一点。通常,您会在整个代码可以访问的单独的公共类中设置这些标记。
最后,为了向 Logger 提供 Marker,我们使用 Logger 类的适当日志记录方法并将标记作为第一个参数提供:
LOGGER.info(IMPORTANT, "This is a very important log message!");
运行代码后,控制台将显示以下输出:
12:51:25.905 INFO - This is a log message that is not important! 12:51:25.907 INFO - This is a very important log message!
虽然由 File appender 创建的文件将包含以下日志消息:
12:51:25.907 [main] INFO - This is a very important log message!
正是我们想要的!
请记住,标记不仅在 Log4j 中可用(阅读我们的Log4j 教程以了解有关此库的更多信息)。正如你在上面的例子中看到的,它们来自 SLF4J,所以 SLF4J 兼容框架应该支持它们。如果您对匹配的库感到好奇,您应该查看我们的SLF4J 教程。
使用日志管理工具进行 Java 日志记录:Sematext 日志如何提供帮助
现在我们知道如何在我们的 Java 应用程序中使用和配置Log4j 2 的日志记录,您可能会被应用程序生成的日志数量所淹没。对于大型分布式应用程序尤其如此。
您可能会避免记录到文件并仅在需要故障排除时使用它们,但是处理大量数据很快变得无法管理,您最终应该使用日志管理解决方案来集中和监控您的日志。您可以选择基于开源软件的内部解决方案,也可以使用市场上可用的产品。
完全托管的日志监控和集中式解决方案将使您无需管理基础架构的另一个通常非常复杂的部分。它将允许您管理大量日志源。如果您想了解 Sematext 如何与类似工具相比较,我们编写了深入的比较来帮助您了解可用的选项。阅读我们对最佳云日志服务、日志分析软件和日志管理解决方案的评论。
您可能希望在托管日志解决方案中包含诸如 JVM垃圾收集日志之类的日志。在为运行在 JVM 上的应用程序和系统打开它们之后,您将希望将它们放在一个地方进行关联、分析,并帮助您调整JVM 实例中的垃圾收集。对日志发出警报、汇总数据、保存并重新运行查询、连接您最喜欢的事件管理软件。
使用日志管理解决方案只是让您的工作更轻松、更快捷的一种方式。您可以从我们关于Java 日志记录最佳实践的博客文章中阅读有关如何高效编写 Java 日志的更多提示。
包起来
在对 Java 应用程序进行故障排除时,日志记录是非常宝贵的。事实上,无论是 Java 应用程序、硬件交换机还是防火墙,日志记录对于一般的故障排除都是无价的。软件和硬件都让我们了解它们是如何以富含上下文信息的参数化日志的形式工作的。
在本文中,我们向您介绍了 Log4j 2.x,这是最先进的 Java 日志库,允许您为项目打开高效且高度可配置的日志记录。我们已经学习了如何包含它、如何打开它、配置日志事件格式和目标。
我希望本文能让您了解如何使用 Log4j 2.x 处理 Java 应用程序日志,以及为什么您应该立即开始使用该库(如果您还没有的话)。并且不要忘记,高效的应用程序日志记录的关键是使用一个好的日志管理工具