自定义日志级别

在代码中定义自定义日志级别

Log4J 2 支持自定义日志级别。自定义日志级别可以在代码或配置中定义。要在代码中定义自定义日志级别,请使用 Level.forName() 方法。此方法为指定名称创建一个新级别。定义日志级别后,您可以通过调用 Logger.log() 方法并传递自定义日志级别来记录此级别的消息

// This creates the "VERBOSE" level if it does not exist yet.
final Level VERBOSE = Level.forName("VERBOSE", 550);

final Logger logger = LogManager.getLogger();
logger.log(VERBOSE, "a verbose message"); // use the custom VERBOSE level

// Create and use a new custom level "DIAG".
logger.log(Level.forName("DIAG", 350), "a diagnostic message");

// Use (don't create) the "DIAG" custom level.
// Only do this *after* the custom level is created!
logger.log(Level.getLevel("DIAG"), "another diagnostic message");

// Using an undefined level results in an error: Level.getLevel() returns null,
// and logger.log(null, "message") throws an exception.
logger.log(Level.getLevel("FORGOT_TO_DEFINE"), "some message"); // throws exception!

定义自定义日志级别时,intLevel 参数(上面的示例中为 550 和 350)决定自定义级别相对于 Log4J 2 中内置的标准级别的位置。作为参考,下表显示了内置日志级别的 intLevel

Log4J 中内置的标准日志级别
标准级别 intLevel
OFF 0
FATAL 100
ERROR 200
WARN 300
INFO 400
DEBUG 500
TRACE 600
ALL Integer.MAX_VALUE

在配置中定义自定义日志级别

自定义日志级别也可以在配置中定义。这对于在记录器过滤器或附加器过滤器中使用自定义级别很方便。与在代码中定义日志级别类似,必须先定义自定义级别,然后才能使用它。如果记录器或附加器配置了未定义的级别,则该记录器或附加器将无效,并且不会处理任何日志事件。

CustomLevel 配置元素创建自定义级别。在内部,它调用上面讨论的相同的 Level.forName() 方法。

CustomLevel 参数
参数名称 类型 描述
name String 自定义级别的名称。请注意,级别名称区分大小写。约定是使用全大写名称。
intLevel integer 决定自定义级别相对于 Log4J 2 中内置的标准级别的位置(参见上表)。

以下示例显示了一个配置,该配置定义了一些自定义日志级别,并使用自定义日志级别来过滤发送到控制台的日志事件。

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
  <!-- Define custom levels before using them for filtering below. -->
  <CustomLevels>
    <CustomLevel name="DIAG" intLevel="350" />
    <CustomLevel name="NOTICE" intLevel="450" />
    <CustomLevel name="VERBOSE" intLevel="550" />
  </CustomLevels>

  <Appenders>
    <Console name="Console" target="SYSTEM_OUT">
      <PatternLayout pattern="%d %-7level %logger{36} - %msg%n"/>
    </Console>
    <File name="MyFile" fileName="logs/app.log">
      <PatternLayout pattern="%d %-7level %logger{36} - %msg%n"/>
    </File>
  </Appenders>
  <Loggers>
    <Root level="trace">
      <!-- Only events at DIAG level or more specific are sent to the console. -->
      <AppenderRef ref="Console" level="diag" />
      <AppenderRef ref="MyFile" level="trace" />
    </Root>
  </Loggers>
</Configuration>

内置日志级别的便捷方法

内置日志级别在 Logger 接口上有一组便捷方法,使它们更易于使用。例如,Logger 接口有 24 个 debug() 方法支持 DEBUG 级别

// convenience methods for the built-in DEBUG level
debug(Marker, Message)
debug(Marker, Message, Throwable)
debug(Marker, Object)
debug(Marker, Object, Throwable)
debug(Marker, String)
debug(Marker, String, Object...)
debug(Marker, String, Throwable)
debug(Message)
debug(Message, Throwable)
debug(Object)
debug(Object, Throwable)
debug(String)
debug(String, Object...)
debug(String, Throwable)
// lambda support methods added in 2.4
debug(Marker, MessageSupplier)
debug(Marker, MessageSupplier, Throwable)
debug(Marker, String, Supplier<?>...)
debug(Marker, Supplier<?>)
debug(Marker, Supplier<?>, Throwable)
debug(MessageSupplier)
debug(MessageSupplier, Throwable)
debug(String, Supplier<?>...)
debug(Supplier<?>)
debug(Supplier<?>, Throwable)

其他内置级别也存在类似的方法。相反,自定义级别需要将日志级别作为额外的参数传递。

// need to pass the custom level as a parameter
logger.log(VERBOSE, "a verbose message");
logger.log(Level.forName("DIAG", 350), "another message");

能够在自定义级别上拥有相同的易用性将很不错,这样在声明 VERBOSE/DIAG 自定义级别后,我们就可以使用如下代码

// nice to have: descriptive methods and no need to pass the level as a parameter
logger.verbose("a verbose message");
logger.diag("another message");
logger.diag("java 8 lambda expression: {}", () -> someMethod());

标准 Logger 接口无法为自定义级别提供便捷方法,但接下来的几节将介绍一个代码生成工具,用于创建旨在使自定义级别与内置级别一样易于使用的记录器。

添加或替换日志级别

我们假设大多数用户希望添加自定义级别方法到 Logger 接口,除了现有的 trace()、debug()、info()、... 方法用于内置日志级别。

还有另一个用例,领域特定语言记录器,我们希望替换现有的 trace()、debug()、info()、... 方法,使用全自定义方法。

例如,对于医疗设备,我们可能只有 critical()warning()advisory() 方法。另一个例子可能是只有 defcon1()defcon2()defcon3() 级别 的游戏。

如果可以隐藏现有的日志级别,用户可以自定义 Logger 接口以匹配他们的需求。例如,有些人可能不想使用 FATAL 或 TRACE 级别。他们希望能够创建一个只有 debug()、info()、warn() 和 error() 方法的自定义记录器。

为自定义记录器包装器生成源代码

常见的 Log4J 用法是从 LogManager 获取 Logger 接口的实例,并调用此接口上的方法。但是,自定义日志级别事先未知,因此 Log4J 无法提供具有这些自定义日志级别的便捷方法的接口。

为了解决这个问题,Log4J 附带了一个工具,用于生成记录器包装器的源代码。生成的包装器类为每个自定义日志级别都有便捷方法,使自定义级别与内置级别一样易于使用。

包装器有两种类型:一种扩展 Logger API(向内置级别添加方法),另一种自定义 Logger API(替换内置方法)。

生成包装器类的源代码时,需要指定

  • 要生成的类的完全限定名
  • 要支持的自定义级别列表及其 intLevel 相对强度
  • 是扩展 Logger(并保留现有的内置方法)还是只有自定义日志级别的方法

然后,您将生成的源代码包含在要使用自定义日志级别的项目中。

生成记录器包装器的示例用法

以下是如何使用带有 DIAG、NOTICE 和 VERBOSE 自定义级别的生成记录器包装器的示例

// ExtLogger is a generated logger wrapper
import com.mycompany.myproject.ExtLogger;

public class MyService {
    // instead of Logger logger = LogManager.getLogger(MyService.class):
    private static final ExtLogger logger = ExtLogger.create(MyService.class);

    public void demoExtendedLogger() {
        // ...
        logger.trace("the built-in TRACE level");
        logger.verbose("a custom level: a VERBOSE message");
        logger.debug("the built-in DEBUG level");
        logger.notice("a custom level: a NOTICE message");
        logger.info("the built-in INFO level");
        logger.diag("a custom level: a DIAG message");
        logger.warn("the built-in WARN level");
        logger.error("the built-in ERROR level");
        logger.fatal("the built-in FATAL level");
        logger.notice("java 8 lambda expression only executed if NOTICE is enabled: {}", () -> someMethod());
        // ...
    }
    ...
}

生成扩展记录器

使用以下命令生成一个向内置级别添加方法的记录器包装器

java -cp log4j-core-2.23.1.jar org.apache.logging.log4j.core.tools.ExtendedLoggerGenerator \
        com.mycomp.ExtLogger DIAG=350 NOTICE=450 VERBOSE=550 > com/mycomp/ExtLogger.java

这将生成记录器包装器的源代码,该包装器具有内置级别的便捷方法以及指定的自定义级别。该工具将生成的源代码打印到控制台。通过追加 " > filename",输出可以重定向到文件。

注意:在 log4j-2.9 之前,此工具是内部类 Generate$ExtendedLogger
在 Unix/Mac/Linux 上的 bash shell 中,美元符号 $ 需要转义,因此类名应位于单引号中 'org.apache.logging.log4j.core.tools.Generate$ExtendedLogger’。

生成自定义记录器

使用以下命令生成一个隐藏内置级别并且只有自定义级别的记录器包装器

java -cp log4j-core-2.23.1.jar org.apache.logging.log4j.core.tools.CustomLoggerGenerator \
        com.mycomp.MyLogger DEFCON1=350 DEFCON2=450 DEFCON3=550 > com/mycomp/MyLogger.java

这将生成记录器包装器的源代码,该包装器具有指定自定义级别的便捷方法,没有内置级别的便捷方法。该工具将生成的源代码打印到控制台。通过追加 " > filename",输出可以重定向到文件。

注意:在 log4j-2.9 之前,此工具是内部类 Generate$ExtendedLogger
在 Unix/Mac/Linux 上的 bash shell 中,美元符号 $ 需要转义,因此类名应该用单引号括起来 'org.apache.logging.log4j.core.tools.Generate$CustomLogger'。