常见问题解答

我看到这个错误“无法找到日志记录实现,使用 SimpleLogger”。问题出在哪里?

您的类路径中包含 log4j-api-2.x jar 文件,但您仍然需要将 log4j-core-2.x jar 添加到类路径中。(此外,您似乎正在使用旧版本的 Log4j 2。您可能需要升级。)

我需要哪些 JAR 文件?

您至少需要 log4j-api-2.x 和 log4j-core-2.x jar 文件。

如果您的应用程序调用了其他日志框架的 API,并且您希望将日志调用路由到 Log4j 2 实现,则需要其他 jar 文件。

Diagram showing which JARs correspond to which systems

当您的应用程序调用 Log4j 2 API,并且您希望将日志调用路由到 SLF4J 实现时,可以使用 log4j-to-slf4j 适配器 jar 文件。

Diagram showing the dependency flow to use Log4j 2 API with SLF4J

一些 Log4j 组件具有可选依赖项的功能。组件页面将提供更多详细信息。例如,log4j-core 组件页面概述了哪些 log4j-core 功能具有外部依赖项。

如何排除冲突的依赖项?

在多种情况下,您可能会遇到冲突的依赖项,尤其是传递包含的依赖项。下表显示了左侧的每个 Log4j 依赖项(隐式 groupId 为 org.apache.logging.log4j),右侧的以下依赖项可以安全地排除(以 groupId:artifactId 格式给出)。

Log4j 依赖项 要排除的依赖项
[log4j-1.2-api](log4j-1.2-api) log4j:log4j org.slf4j:log4j-over-slf4j
[log4j-core](log4j-core) log4j:log4j ch.qos.logback:logback-core org.apache.logging.log4j:log4j-to-slf4j
[log4j-jcl](log4j-jcl) org.slf4j:jcl-over-slf4j
[log4j-jul](log4j-jul) org.slf4j:jul-to-slf4j
[log4j-slf4j-impl](log4j-slf4j-impl) org.apache.logging.log4j:log4j-to-slf4j ch.qos.logback:logback-core

使用 Apache Maven,可以在您的项目中全局排除依赖项,如下所示

<dependencies>
  <dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
    <scope>provided</scope>
  </dependency>
</dependencies>

也可以为特定依赖项显式排除依赖项。例如,要使用具有 Log4j 2 的项目而不是 Log4j 1.x

<dependencies>
  <dependency>
    <groupId>com.example</groupId>
    <artifactId>example-project</artifactId>
    <version>1.0</version>
    <exclusions>
      <exclusion>
        <groupId>log4j</groupId>
        <artifactId>log4j</artifactId>
      </exclusion>
      <exclusion>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-log4j12</artifactId>
      </exclusion>
    </exclusions>
  </dependency>
  <dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-core</artifactId>
    <version>2.23.1</version>
  </dependency>
  <dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-slf4j-impl</artifactId>
    <version>2.23.1</version>
  </dependency>
  <dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-1.2-api</artifactId>
    <version>2.23.1</version>
  </dependency>
</dependencies>

可以在 Gradle 中全局排除依赖项,如下所示

configurations {
  all*.exclude group: 'log4j', module: 'log4j'
}

上述 Maven 排除的等效 Gradle 配置如下所示

dependencies {
  compile('com.example:example-project:1.0') {
    exclude group: 'log4j', module: 'log4j'
    exclude group: 'org.slf4j', module: 'slf4j-log4j12'
  }
  compile('org.apache.logging.log4j:log4j-core:2.23.1')
  compile('org.apache.logging.log4j:log4j-slf4j-impl:2.23.1')
  compile('org.apache.logging.log4j:log4j-1.2-api:2.23.1')
}

如何指定配置文件位置?

默认情况下,Log4j 在类路径中查找名为 log4j2.xml(而不是 log4j.xml)的配置文件。

您还可以使用此系统属性指定配置文件的完整路径:-Dlog4j.configurationFile=path/to/log4j2.xml

该属性也可以包含在名为 log4j2.component.properties 的类路径资源文件中。

Web 应用程序可以使用 servlet 上下文参数指定 Log4j 配置文件位置。请参阅在 Web 应用程序中使用 Log4j 2 手册页面的本节

如何在没有配置文件的情况下在代码中配置 log4j2?

从版本 2.4 开始,Log4j 2 提供了编程配置的 API 新的ConfigurationBuilder API 允许您通过构建组件定义在代码中创建配置,而无需您了解实际配置对象(如记录器和附加器)的内部结构。

如何在代码中使用特定配置文件重新配置 log4j2?

请参阅以下示例。请注意,此 LoggerContext 类不是公共 API 的一部分,因此您的代码可能会在任何次要版本中中断。

// import org.apache.logging.log4j.core.LoggerContext;

LoggerContext context = (org.apache.logging.log4j.core.LoggerContext) LogManager.getContext(false);
File file = new File("path/to/a/different/log4j2.xml");

// this will force a reconfiguration
context.setConfigLocation(file.toURI());

如何在代码中关闭 log4j2?

通常不需要手动执行此操作。每个 LoggerContext 都注册了一个关闭挂钩,该挂钩负责在 JVM 退出时释放资源(除非系统属性 log4j.shutdownHookEnabled 设置为 false)。Web 应用程序应在其类路径中包含 log4j-web 模块,该模块会禁用关闭挂钩,而是会在 Web 应用程序停止时清理 log4j 资源。

但是,如果您需要手动关闭 Log4j,则可以按照以下示例进行操作。请注意,有一个可选参数用于指定要关闭的 LoggerContext

import org.apache.logging.log4j.LogManager;

// ...

LogManager.shutdown();

如何将不同级别的日志消息发送到不同的附加器?

您不需要声明单独的记录器来实现此目的。您可以在 AppenderRef 元素上设置日志级别。

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
  <Appenders>
    <File name="file" fileName="app.log">
      <PatternLayout>
        <Pattern>%d %p %c{1.} [%t] %m %ex%n</Pattern>
      </PatternLayout>
    </File>
    <Console name="STDOUT" target="SYSTEM_OUT">
      <PatternLayout pattern="%m%n"/>
    </Console>
  </Appenders>
  <Loggers>
    <Root level="trace">
      <AppenderRef ref="file" level="DEBUG"/>
      <AppenderRef ref="STDOUT" level="INFO"/>
    </Root>
  </Loggers>
</Configuration>

如何调试我的配置?

首先,确保您的类路径中包含正确的 jar 文件。您至少需要 log4j-api 和 log4j-core。

接下来,检查您的配置文件的名称。默认情况下,log4j2 将在类路径中查找名为 log4j2.xml 的配置文件。请注意文件名中的“2”!(请参阅配置手册页以获取更多详细信息。)

从 log4j-2.9 开始

从 log4j-2.9 开始,如果系统属性 log4j2.debug 被定义为空或其值等于 true(忽略大小写),则 log4j2 将将所有内部日志打印到控制台。

在 log4j-2.9 之前

在 log4j-2.9 之前,有两个地方可以控制内部日志记录

如果正确找到配置文件,则可以通过在配置文件中设置 <Configuration status="trace"> 来控制 log4j2 内部状态日志记录。这将在控制台上显示有关配置过程期间发生情况的详细 log4j2 内部日志语句。这可能有助于解决配置问题。默认情况下,状态记录器级别为 WARN,因此您只会在出现问题时看到通知。

如果未正确找到配置文件,您仍然可以通过设置系统属性 -Dorg.apache.logging.log4j.simplelog.StatusLogger.level=TRACE 来启用 log4j2 内部状态日志记录。

如何动态写入单独的日志文件?

查看RoutingAppender。您可以在配置中定义多个路由,并在 ThreadContext 映射中放置值,这些值决定了该线程中后续事件将记录到哪个日志文件。

您可以使用 ThreadContext 映射值来确定日志文件名。

<Routing name="Routing">
  <Routes pattern="$${ctx:ROUTINGKEY}">

    <!-- This route is chosen if ThreadContext has value 'special' for key ROUTINGKEY. -->
    <Route key="special">
      <RollingFile name="Rolling-${ctx:ROUTINGKEY}" fileName="logs/special-${ctx:ROUTINGKEY}.log"
	filePattern="./logs/${date:yyyy-MM}/${ctx:ROUTINGKEY}-special-%d{yyyy-MM-dd}-%i.log.gz">
	<PatternLayout>
	  <pattern>%d{ISO8601} [%t] %p %c{3} - %m%n</pattern>
	</PatternLayout>
	<Policies>
	  <TimeBasedTriggeringPolicy interval="6" modulate="true" />
          <SizeBasedTriggeringPolicy size="10 MB" />
	</Policies>
      </RollingFile>
    </Route>

    <!-- This route is chosen if ThreadContext has no value for key ROUTINGKEY. -->
    <Route key="$${ctx:ROUTINGKEY}">
      <RollingFile name="Rolling-default" fileName="logs/default.log"
	filePattern="./logs/${date:yyyy-MM}/default-%d{yyyy-MM-dd}-%i.log.gz">
        <PatternLayout>
	  <pattern>%d{ISO8601} [%t] %p %c{3} - %m%n</pattern>
        </PatternLayout>
        <Policies>
          <TimeBasedTriggeringPolicy interval="6" modulate="true" />
          <SizeBasedTriggeringPolicy size="10 MB" />
        </Policies>
      </RollingFile>
    </Route>

    <!-- This route is chosen if ThreadContext has a value for ROUTINGKEY
         (other than the value 'special' which had its own route above).
         The value dynamically determines the name of the log file. -->
    <Route>
      <RollingFile name="Rolling-${ctx:ROUTINGKEY}" fileName="logs/other-${ctx:ROUTINGKEY}.log"
	filePattern="./logs/${date:yyyy-MM}/${ctx:ROUTINGKEY}-other-%d{yyyy-MM-dd}-%i.log.gz">
	<PatternLayout>
	  <pattern>%d{ISO8601} [%t] %p %c{3} - %m%n</pattern>
	</PatternLayout>
	<Policies>
	  <TimeBasedTriggeringPolicy interval="6" modulate="true" />
	  <SizeBasedTriggeringPolicy size="10 MB" />
	</Policies>
      </RollingFile>
    </Route>
  </Routes>
</Routing>

如何以编程方式设置记录器的级别?

您可以使用 Log4j Core 中的类 Configurator 设置记录器的级别。请注意,Configurator 类不是公共 API 的一部分。

// org.apache.logging.log4j.core.config.Configurator;

Configurator.setLevel("com.example.Foo", Level.DEBUG);

// You can also set the root logger:
Configurator.setRootLevel(Level.DEBUG);

如何设置我的日志存档保留策略?如何删除旧的日志存档?

滚动文件追加器(以及滚动随机访问文件追加器)的DefaultRolloverStrategy支持Delete元素。

从指定的基目录开始,您可以删除满足某些条件的所有文件,例如,所有与给定文件名模式匹配且早于指定天数的文件。更复杂的条件也是可能的,如果内置条件不足,用户可以通过创建插件条件或编写脚本条件来提供自定义条件。

使用 Log4j 2 API 与 SLF4J API 的权衡是什么?

Log4j 2 API 和 SLF4J 有很多共同点。它们都共享干净地将日志记录 API 与实现分离的目标。我们认为 Log4j 2 API 可以帮助您的应用程序提高性能,同时提供更多功能和灵活性。

可能存在使用 Log4j 2 API 会将您的应用程序紧密耦合到 Log4j 2 的担忧。事实并非如此:使用 Log4j 2 API 编写的应用程序始终可以选择使用任何与 SLF4J 兼容的库作为其日志记录实现,并使用 log4j-to-slf4j 适配器。有关详细信息,请参阅哪些 jar 常见问题解答条目。

使用 Log4j 2 API 有几个优点

  • SLF4J 强制您的应用程序记录字符串。Log4j 2 API 支持记录任何 CharSequence(如果您想记录文本),但也支持按原样记录任何对象。日志记录实现负责处理此对象,我们认为将应用程序限制为记录字符串是一个设计错误。
  • Log4j 2 API 提供对记录消息对象的支持。消息允许支持有趣的和复杂的结构通过日志记录系统传递并有效地操作。用户可以自由地创建自己的消息类型并编写自定义布局、过滤器和查找来操作它们。
  • Log4j 2 API 支持 Java 8 lambda 表达式
  • Log4j 2 API 对无垃圾日志记录有更好的支持:它避免创建可变参数数组,并在记录 CharSequence 对象时避免创建字符串。

当我使用 SLF4J API 时,Log4j 2 仍然是无垃圾的吗?

是的,log4j-slf4j-impl 绑定(以及 log4j-core)实现了org.slf4j.Logger方法以实现无 GC。但是,请记住,有一些限制

SLF4J API 仅为参数化消息提供最多两个参数。超过此数量将使用可变参数,这将为参数数组创建一个临时对象。Log4j 2.6 API 具有最多十个展开参数的方法。

另一个考虑因素是 SLF4J API 强制您的应用程序记录字符串。Log4j 2 API 允许您记录任何 java.lang.CharSequence,甚至任何对象。Log4j 可以记录实现java.lang.CharSequenceorg.apache.logging.log4j.util.StringBuilderFormattable的任何对象,而不会创建垃圾。

org.slf4j.spi.LocationAwareLogger::log方法尚未在 log4j-slf4j-impl 绑定中以无垃圾的方式实现。它为每次调用创建一个新的消息对象。

如何在我的域对象中记录日志,而不会创建垃圾?

一种选择是让域对象实现 java.lang.CharSequence。但是,对于许多域对象来说,在不分配临时对象的情况下实现这一点可能并不容易。

另一种方法是实现org.apache.logging.log4j.util.StringBuilderFormattable接口。如果记录的对象实现了此接口,则会调用其formatTo方法,而不是toString()

package org.apache.logging.log4j.util;
public interface StringBuilderFormattable {
    /**
     * Writes a text representation of this object into the specified {@code StringBuilder},
     * ideally without allocating temporary objects.
     *
     * @param buffer the StringBuilder to write into
     */
     void formatTo(StringBuilder buffer);
}

如何创建显示正确类、方法和行号的自定义日志记录器包装器?

Log4j 会记住日志记录器的完全限定类名 (FQCN),并在配置为打印位置时使用它来遍历每个日志事件的堆栈跟踪。(请注意,使用位置进行日志记录很慢,可能会影响应用程序的性能。)

自定义日志记录器包装器的问题是它们具有与实际日志记录器不同的 FQCN,因此 Log4j 无法找到调用自定义日志记录器的位置。

解决方案是提供正确的 FQCN。最简单的方法是让 Log4j 为您生成日志记录器包装器。Log4j 带有一个日志记录器包装器生成器工具。该工具最初旨在支持自定义日志级别,并在此处进行了说明。

生成的日志记录器代码将处理 FQCN。

启用 ProGuard 缩减时,我需要添加哪些规则?

当您在启用 ProGuard/R8 的情况下使用 Log4j 时,您需要在配置文件中添加以下规则

-keep,allowoptimization class org.apache.logging.log4j.** { *; }