log4j原来要这么用

大家好,今天我们一起聊聊Java开发中常用的日志框架log4j。


大纲

log4j原来要这么用

log4j2介绍

log4j2 是一个功能强大、性能卓越的优秀日志框架,它提供了强大的日志记录和管理功能,可以帮助开发者轻松记录应用程序中的各种事件和行为。和他对应的其他的日志框架例如logback; 之前的版本是log4j 1.x 这个是升级版。也可以搭配slf4j使用。

API分离

Log4j的API与实现分开,使应用程序开发人员可以清楚地了解可以使用哪些类和方法,同时确保向前兼容性。这使Log4j团队能够以安全且兼容的方式进行改进。

性能提升

Log4j2的性能得到了显著的提升。它包含了基于LMAX Disruptor库的下一代异步记录器。在多线程场景中,异步记录器的吞吐量比Log4j 1.x和Logback高出了18倍,并且延迟更低。这种性能提升使得Log4j2在处理大量并发日志记录时更加高效。

自动重新加载配置

与Logback类似,Log4j2可以在配置文件修改时自动重新加载其配置。但Log4j2在重新配置发生时不会丢失任何日志事件,这是它与Logback的一个主要区别。

高级过滤

Log4j2支持基于Log事件中的上下文数据、标记、正则表达式和其他组件进行过滤。此外,过滤器还可以与记录器关联。与Logback不同,Log4j2可以在任何这些情况下使用通用的Filter类,提供了更灵活和强大的过滤功能。

插件架构

Log4j2使用插件模式来配置组件。无需编写代码来创建和配置Appender,Layout,Pattern Converter等,一旦插件被正确配置,Log4j2就能自动识别并使用它们。这种插件架构使得Log4j2更加模块化和可扩展。

无垃圾机制

在稳态日志记录期间,Log4j2在独立应用程序中是无垃圾的,而在Web应用程序中则是低垃圾的。这有助于减少应用程序的内存消耗和垃圾收集开销,从而提高了应用程序的性能。

常用的日志框架

常见的日志框架有很多,如:JCL、SLF4J、Jboss-logging、JUL、log4j、log4j2、logback等。

log4j

Log4j是Apache的一个开源项目,通过使用Log4j,我们可以控制日志信息输送的目的地是控制台、文件、GUI组件,甚至是套接口服务器、NT的事件记录器、UNIX Syslog守护进程等;我们也可以控制每一条日志的输出格式;通过定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程。这些可以通过一个配置文件来灵活地进行配置,而不需要修改应用的代码。

log4j2

Apache Log4j 2 (Log4j – Apache Log4j 2)是对Log4j的升级,它比其前身Log4j 1.x提供了重大改进,并参考了Logback中优秀的设计,同时修复了Logback架构中的一些问题。被誉为是目前最优秀的Java日志框架;企业中通常使用SLF4j门面+Log4j2来记录日志。

Commons logging

Apache Commons Logging,又名JakartaCommons Logging (JCL),它是Apache提供的一个通用的日志接口,它的出现避免了和具体的日志方案直接耦合;在日常开发中,developer可以选择第三方日志组件进行搭配使用,例如log4j、logback等;

slf4j

slf4j的全称 Simple Logging Facade for Java(简单日志门面),这个组件的设计采用了Facade 设计模式,它为Java程序提供了一个统一的日志输出接口,但是这个组件不提供具体的日志实现方案,因此,如果准备让程序记录日志,还必须要引入具体的实现了日志记录功能的组件,如log4j或logback等。Slf4j在编译期间,静态绑定本地的log库,在通过查找类路径下org.slf4j.impl,static.staticLoggerBinder,然后在staticLoggerBinder中进行绑定。

logback

Logback是一个Java日志框架,是log4j项目的继承者,也是log4j创始人设计的另一个开源日志组件,性能比log4j要好。它旨在解决log4j存在的一些问题,并提供了更高效和更灵活的日志框架。一组日志组件的具体实现方案。

JUL

JUL(Java util logging),Java 原生日志框架,自java1.4以来的官方日志实现,不需要引入第三方依赖包,使用简单方便,一般在小型应用中使用,主流项目中现在很少使用了。

性能对比

异步日志记录对于处理事件突发情况非常有用。其工作原理是,应用程序线程进行最少量的工作以捕获日志事件所需的所有信息,然后将该日志事件放入队列中,由后台线程稍后进行处理。只要队列的大小足够大,应用程序线程就能够在日志记录调用上花费很少的时间,并迅速返回到业务逻辑中。

事实证明,队列的选择对于峰值吞吐量至关重要。Log4j 2的异步记录器使用无锁数据结构,而Logback、Log4j 1.2以及Log4j 2的异步追加器则使用ArrayBlockingQueue。在使用阻塞队列时,多线程应用程序在尝试将日志事件入队时通常会遇到锁争用的情况。

下面的图表展示了无锁数据结构在多线程场景下对吞吐量的影响。Log4j 2随着线程数量的增加能够更好地扩展:线程更多的应用程序能够记录更多的日志。其他日志库则受到锁争用的影响,当更多线程进行日志记录时,总吞吐量保持不变或下降。这意味着在使用其他日志库时,每个单独的线程能够记录的日志量会减少。

请注意,这是指峰值吞吐量:Log4j 2的异步记录器在达到某个点之前可以提供更好的吞吐量,但一旦队列满了,追加器线程就需要等待队列中出现可用的槽位,此时吞吐量最多只能降至底层追加器的最大持续吞吐量。

log4j原来要这么用

log4j2在全异步的模式下,在单线程和多线程的情况下,表现都很优异,特别是多线程的场景下。

下图为P99耗时,log4j2在全异步场景下,依然很优秀。

log4j原来要这么用

log4j2如何使用

log4j2引入

使用log4j2,需要两个依赖,log4j2-api和log4j2-core,其中log4j2-api为log4j2的接口定义,与slf4j的接口定义类似功能。log4j2-core是log4j2项目的核心,定义了log4j2日志的具体实现。

普通项目

手动添加jar包:log4j-api-2.5.jar、log4j-core-2.5.jar

Maven项目

项目pom.xml中添加

<dependencies>
  <dependency>
   <groupId>org.apache.logging.log4j</groupId>
   <artifactId>log4j-api</artifactId>
   <version>2.23.1</version>
  </dependency>
  <dependency>
   <groupId>org.apache.logging.log4j</groupId>
   <artifactId>log4j-core</artifactId>
   <version>2.23.1</version>
  </dependency>
 </dependencies>

log4j2配置文件

properties文件

配置文件名为log4j2.properties,放在项目的classpath路径下:

# 设置根记录器及其级别和输出
log4j.rootLogger = error,console,A1
# 设置自身项目记录器及其级别和输出
log4j.logger.com.XXX = info,console,A1
 # 控制台输出配置
log4j.appender.console = org.apache.log4j.ConsoleAppender
log4j.appender.console.layout = org.apache.log4j.PatternLayout
log4j.appender.console.layout.pattern = [%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %c{1} - %msg%n 
# 文件输出配置
log4j.appender.A1 = org.apache.log4j.DailyRollingFileAppender
log4j.appender.A1.Append = true
log4j.appender.A1.File = logs/app_%d{yyyyMMdd}.log
log4j.appender.A1.layout = org.apache.log4j.PatternLayout
log4j.appender.A1.layout.ConversionPattern = [%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %c{1} - %msg%n

xml文件

配置文件名为log4j2.xml,放在项目的classpath路径下:

<Configuration status="WARN">
    <Appenders>
       <Console name="console" target="SYSTEM_OUT">
         <PatternLayout pattern="[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS}[%t]%c{1}-[%m] %n"/>
         <ThresholdFilter level="trace" onMatch="ACCEPT" onMismatch="DENY" />
       </Console>
       <RollingRandomAccessFile name="logfile" fileName="logs/ucpproxy.log" append="true" filePattern="logs/app.log.%d{yyyy-MM-dd}">
          <PatternLayout pattern="[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS}[%t]%c{1}-[%m] %n"/>
          <ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/>
          <Policies>
              <TimeBasedTriggeringPolicy interval="1"/>
          </Policies>
        </RollingRandomAccessFile>
    </Appenders>
    <Loggers>
        <logger name="com.XXX" level="debug"></logger>
        <root level="error">
           <appender-ref ref="console"/>
           <appender-ref ref="logfile"/>
         </root>
    </Loggers>
</Configuration>

json文件

配置文件名为log4j2.json,放在项目的classpath路径下:

{
  "configuration": {
    "status""warn",
    "monitorInterval""30",
    "appenders": {
      "Console": {
        "name""console",
        "target""SYSTEM_OUT",
        "PatternLayout": {
          "pattern""[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS}[%t]%c{1}-[%m] %n"
        },
        "ThresholdFilter": {
          "level""trace",
          "onMatch""ACCEPT",
          "onMismatch""DENY"
        }
      },
      "RollingRandomAccessFile": {
        "name""logfile",
        "fileName""logs/app.log",
        "append""true",
        "filePattern""logs/app.log.%d{yyyy-MM-dd}",
        "PatternLayout": {
          "pattern""[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS}[%t]%c{1}-[%m] %n"
        },
        "ThresholdFilter": {
          "level""info",
          "onMatch""ACCEPT",
          "onMismatch""DENY"
        },
        "Policies": {
          "TimeBasedTriggeringPolicy": {
            "interval""1"  
           }
        }
      }
    },
    "loggers": {
      "logger": {
        "name""com.XXX",
        "level""debug"
      },
      "root": {
        "level""error",
        "appender-ref": {
          "ref""console"
        },
        "appender-ref": {
          "ref""logfile"
        }
      }
    }
  }
}

jaml文件

配置文件名为log4j2.yml,放在项目的classpath路径下:

Configuration: 
    status: warn
    monitorInterval: 30
    Appenders: 
      Console: 
        name: console
        target: SYSTEM_OUT
        PatternLayout: 
          pattern: "[%-5level] %dyyyy-MM-dd HH:mm:ss.SSS[%t]%c1-[%m] %n"
        ThresholdFilter: 
          level: trace
          onMatch: ACCEPT
          onMismatch: DENY   
      RollingRandomAccessFile: 
        name: logfile
        fileName: logs/app.log
        append: true
        filePattern: "logs/app.log.%dyyyy-MM-dd"
        PatternLayout: 
          pattern: "[%-5level] %dyyyy-MM-dd HH:mm:ss.SSS[%t]%c1-[%m] %n"     
        ThresholdFilter: 
          level: info
          onMatch: ACCEPT
          onMismatch: DENY    
        Policies: 
          TimeBasedTriggeringPolicy: 
            interval: 1  
    Loggers: 
      Logger: 
        name: com.XXX
        level: debug
      Root: 
        level: error
        Appender-ref: 
          ref: console
          ref: logfile

Configuration配置

status

记录到控制台的Log4j内部事件级别。该属性的有效值为“off”、“trace”、“debug”、“info”、“warn”、“error”、“fatal”和“all”。

Log4j会将初始化、滚动和其他内部操作的详细信息记录到状态记录器中。如果需要排查log4j的问题,设置status=”trace”是你可以使用的第一个工具之一。 (或者,设置系统属性log4j2.debug也会将内部Log4j2日志打印到控制台,包括在找到配置文件之前发生的内部日志记录。)

Appenders配置

配置单个appender时,可以使用特定appender插件的name属性,也可以使用带有type属性的appender元素,其中type属性包含appender插件的名称。此外,每个appender都必须具有一个name属性,其值在appender集合中必须是唯一的,这个名称将被loggers用于引用appender。

大多数appender还支持配置layout(这同样可以通过使用特定Layout插件的名称作为元素来指定,或者使用“layout”作为元素名称,并带有包含Layout插件名称的type属性来指定)。各种appender将包含其他必要的属性或元素,以确保它们能够正常工作。

FileAppender

FileAppender是一个OutputStreamAppender,它向fileName参数中指定的文件写入内容。FileAppender使用FileManager(继承自OutputStreamManager)来实际执行文件的输入/输出操作。虽然来自不同配置的FileAppenders不能共享,但FileManager可以共享,只要Manager是可访问的。例如,在servlet容器中,两个Web应用程序可以有它们自己的配置,并且可以安全地向同一个文件写入内容,只要Log4j位于这两个应用程序共同的ClassLoader中。实际应用中,不推荐使用,可使用功能更强大丰富的RollingFileAppender或RollingRandomAccessFileAppender。

RollingFileAppender

append

默认为true,即记录会追加在日志文件末尾。若设置为false,则新的记录在保存前,会先清楚原来保存的那些记录。不需另外配置。

bufferedIO

默认为true,即采用缓存的方式,缓存满了再一次性写入磁盘文件。设置false则会每条记录立刻写入磁盘文件。不需另外配置。

bufferSize

默认为8192个字节,即缓存的大小。当bufferedIO设置为true时生效。可根据需要进行调整。

filter

一个Filter,用于确定事件是否应该由这个appender处理。通过使用CompositeFilter,可以使用多个Filter。常用的Filter为ThresholdFilter。ThresholdFilter是临界值Filter,主要用于过滤掉低于指定临界值的日志。当日志级别等于或高于临界值时,过滤器会返回NEUTRAL,即接受该日志;而当日志级别低于临界值时,日志则会被拒绝。例如,可以配置ThresholdFilter来过滤掉所有低于INFO级别的日志,这样,只有INFO级别及以上的日志才会被处理。

ThresholdFilter的属性值level用于配置日志级别,即日志的临界值。属性值onMatch,即匹配到后的操作,值为ACCEPT为接受,即日志级别等于或高于level级别时,打印日志;值为DENY时,表示等于或高于level级别时,不打印日志。属性onMismatch,参数值与onMatch相同,值为ACCEPT,表示不匹配时打印日志;值为DENY时,表示不匹配时不打印日志。

fileName

要写入文件的名称。如果该文件或其任何父目录不存在,它们将被创建。

filePattern

归档日志文件的文件名模式取决于所使用的滚动策略(RolloverPolicy)。默认的滚动策略(DefaultRolloverPolicy)将接受与SimpleDateFormat兼容的日期/时间模式,或者一个%i,它代表一个整数计数器。该模式还支持在运行时进行插值,因此任何查找(例如DateLookup)都可以包含在模式中。

layout

用于格式化LogEvent的pattern。如果没有提供pattern,将使用默认的“%m%n”pattern。常用的PatternLayout,这是一个灵活的Layout,可以通过模式字符串进行配置。这个类的目的是格式化一个LogEvent并返回结果。结果的格式取决于转换模式。

PatternLayout参数pattern即格式的参数说明如下图:

log4j原来要这么用

policy

用于确定是否应该发生滚动的策略。日志记录中用于判断是否应该进行滚动(即创建新的日志文件或进行日志文件的备份)的策略。

在日志管理中,当日志文件达到某个大小限制、达到一定的时间周期或者满足其他特定条件时,就需要进行滚动操作,以避免日志文件过大或过期数据过多。这个“策略”就是用来定义这些条件和触发滚动的规则的。例如,可以设定一个策略,当日志文件大小超过1GB时自动滚动;或者设定一个策略,每天午夜时分自动滚动日志文件。这样的策略能够确保日志管理的有效性和高效性。

CronTriggeringPolicy:基于cron表达式触发滚动。该策略由定时器控制,与日志事件的处理是异步的,因此,前一个或下一个时间段的日志事件可能会出现在日志文件的开头或结尾。Appender的filePattern属性应该包含一个时间戳,否则每次滚动时目标文件都会被覆盖。参数schedule,遵循cron表达式。

OnStartup Triggering Policy:该策略会在日志文件比当前JVM启动时间还要老,并且文件大小达到或超过最小文件大小时触发滚动。

SizeBasedTriggeringPolicy:会在文件达到指定大小后触发滚动。大小可以以字节为单位指定,也可以使用后缀KB、MB、GB或TB,例如20MB。大小还可以包含小数值,如1.5 MB。大小评估使用Java的根区域设置,因此小数部分必须使用点号。当与时间触发策略结合使用时,文件模式必须包含%i,否则每次滚动时目标文件都会被覆盖,因为SizeBasedTriggeringPolicy不会更改文件名中的时间戳值。如果不使用时间触发策略,Size BasedTriggeringPolicy将导致时间戳值更改。

TimeBasedTriggeringPolicy:该策略会在当前活动的日志文件的日期/时间模式不再适用时触发滚动。该策略接受一个interval属性,它根据时间模式指示滚动的发生频率,以及一个modulate布尔属性。属性interval,基于日期模式中最具体的时间单位,滚动的发生频率应该是多少。例如,如果日期模式中最具体的是小时,并且interval属性为4,那么每4小时就会发生一次滚动。默认值为1。

RollingRandomAccessFileAppender

与标准的RollingFileAppender类似,但它总是进行缓冲(无法关闭)并且在内部使用ByteBuffer + RandomAccessFile而不是BufferedOutputStream。在我们的测试中,与RollingFileAppender(其中“bufferedIO=true”)相比,RollingRandomAccessFileAppender的性能提高了20-200%。

RollingRandomAccessFileAppender将日志写入fileName参数指定的文件中,并根据TriggeringPolicy和RolloverPolicy进行滚动。与RollingFileAppender类似,RollingRandomAccessFileAppender使用RollingRandomAccessFileManager来实际执行文件I/O和滚动操作。

虽然不同配置中的RollingRandomAccessFileAppender不能共享,但如果Manager可访问,则RollingRandomAccessFileManagers可以共享。例如,在servlet容器中的两个Web应用程序可以拥有各自的配置,并安全地将日志写入同一个文件中,只要Log4j处于它们共同的ClassLoader中。

RollingRandomAccessFileAppender需要一个TriggeringPolicy和一个RolloverStrategy。触发策略确定是否应执行滚动,而滚动策略则定义如何执行滚动。如果没有配置滚动策略,RollingRandomAccessFileAppender将使用默认的滚动策略(DefaultRolloverStrategy)。从log4j-2.5开始,可以在DefaultRolloverStrategy中配置自定义删除操作,以在滚动时执行。

ConsoleAppender

正如预期的那样,ConsoleAppender将其输出写入System.out或System.err,其中System.out是默认目标。必须提供一个Layout来格式化LogEvent。

filter

详见RollingFileAppender中的filter参数解释。

layout

详见RollingFileAppender中的layout参数解释。

target

值为”SYSTEM_OUT”  或 “SYSTEM_ERR”,默认为 “SYSTEM_OUT”,可根据需要调整。

Loggers配置

Loggers组件级别

五个级别的顺序,分别用来指定这条日志信息的重要程度:

DEBUG < INFO < WARN < ERROR < FATAL

Log4j规则:只输出级别不低于设定级别的日志信息,假如Loggers级别设定为INFO,则INFO、WARN、ERROR和FATAL级别的日志信息都会输出,而级别比INFO低的DEBUG则不会输出。

log4j原来要这么用

Loggers分类

log4j2中,分为同步日志,半异步日志,全异步日志

同步日志

所谓同步日志,即当输出日志时,必须等待日志输出语句执行完毕后,才能执行后面的业务逻辑语句。即使用<logger>和<root>的方式配置logger。

<Loggers>
    <logger name="com.XXX" level="debug"></logger>
    <root level="error">
        <appender-ref ref="console"/>
        <appender-ref ref="logfile"/>
    </root>
</Loggers>

半异步日志

Log4j-2.9及更高版本需要在类路径(classpath)中包含disruptor-3.3.4.jar或更高版本。在Log4j-2.9之前,则需要disruptor-3.0.0.jar或更高版本。无需将系统属性“Log4jContextSelector”设置为任何值。

同步和异步记录器可以在配置中组合使用。这为您提供了更大的灵活性,但会轻微损失一些性能(与将所有记录器都设置为异步相比)。使用配置元素来指定需要异步的记录器。一个配置中只能包含一个根记录器(要么是元素,要么是元素),但除此之外,异步和非异步记录器可以组合使用。例如,包含元素的配置文件也可以包含和元素,用于同步记录器。

默认情况下,异步记录器不会将位置信息传递给I/O线程。如果您的布局或自定义过滤器需要位置信息,您需要在所有相关记录器的配置中(包括根记录器)设置”includeLocation=true”。

<Loggers>
    <AsyncLogger name="com.XXX" level="debug"></AsyncLogger>
    <root level="error">
       <appender-ref ref="console"/>
       <appender-ref ref="logfile"/>
    </root>
</Loggers>

或者

<Loggers>
   <logger name="com.XXX" level="debug"></logger>
   <AsyncRoot level="error">
       <appender-ref ref="console"/>
       <appender-ref ref="logfile"/>
   </AsyncRoot>
</Loggers>

全异步日志

Log4j-2.9及更高版本需要在类路径(classpath)中包含disruptor-3.3.4.jar或更高版本。在Log4j-2.9之前,则需要disruptor-3.0.0.jar或更高版本。

这种配置最简单,且提供了最佳性能。要让所有的记录器(logger)变为异步的,需要将disruptor jar添加到类路径(classpath)中,并将系统属性log4j2.contextSelector设置为org.apache.logging.log4j.core.async.AsyncLoggerContextSelector或org.apache.logging.log4j.core.async.BasicAsyncLoggerContextSelector。

默认情况下,异步记录器不会将位置信息传递给I/O线程。如果您的布局或自定义过滤器需要位置信息,您需要在所有相关记录器(包括根记录器)的配置中设置“includeLocation=true”。

log4j2性能策略

日志模式优化

log4j2的日志模式性能由高到底为:全异步方式 > 半异步方式 > 同步方式。

详细见下图:

log4j原来要这么用

配置全局异步方式一

增加应用运行命令参数来实现:

-DLog4jContextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector。

配置全局异步方式二

classpath目录下增加配置文件log4j2.component.properties,内容如下:

# 设置异步日志系统属性
log4j2.contextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector

日志队列策略优化

全异步模式下,日志缓存队列满后的策略默认为等待,在极端情况下,缓存队列满,业务线程会被阻塞,导致系统性能下降。可以通过配置缓存队列满后的处理策略为Discard丢弃的方式,丢弃部分日志,换取业务线程的正常运行。此时需要另外配置丢弃的日志级别,默认情况下,丢弃的日志级别为info,建议调整为error。

优化操作如下: classpath目录下的配置文件log4j2.component.properties,内容如下:

log4j2.asyncQueueFullPolicy=Discard
log4j2.discardThreshold=ERROR

日志打印优化

一些layout可以显示记录调用在应用程序中发生时的类、方法和行号。在Log4j 2中,这样的layout选项的例子有HTML locationInfo,或者模式中的%C或$class,%F或%file,%l或%location,%L或%line,%M或%method。为了提供调用者位置信息,日志库会获取堆栈的快照,并遍历堆栈跟踪以找到位置信息。

下面的图表显示了从单个线程异步记录时捕获调用者位置信息对性能的影响。我们的测试显示,捕获调用者位置对所有日志库都有相似的影响,并使异步日志记录速度减慢约30至100倍。

log4j原来要这么用

可以看到多种日志打印框架在是否打印位置信息时,都存在不打印位置信息的吞吐量要远远高于打印位置信息。位置信息打印的配置,默认是打开的(异步日志默认是关闭的),在高并发的系统,建议关掉位置信息打印功能,系统的吞吐量会有很大的提升。以log4j2为例,取消位置信息打印的配置,只需要设置includeLocation参数为false,配置如下:

<Loggers>
    <AsyncLogger name="com.XXX" level="debug" includeLocation=false></AsyncLogger>
    <AsyncRoot level="error"  includeLocation=false>
        <appender-ref ref="console"/>
        <appender-ref ref="logfile"/>
    </AsyncRoot>
</Loggers>

日志代码优化

任何介绍都离不开惯常的“Hello, World”示例。下面就是我们的示例。首先,从LogManager中获取一个名为“HelloWorld”的Logger。接下来,使用此logger来写入“Hello, World!”消息,但只有当Logger配置为允许信息性消息时,该消息才会被写入。

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

public class HelloWorld {
    private static final Logger logger = LogManager.getLogger("HelloWorld");
    public static void main(String[] args) {
        logger.info("Hello, World!");
    }
}

调用logger.info()的输出将根据所使用的配置而有很大差异。

替换参数

日志记录的目的通常是为了提供有关系统中正在发生什么的信息,这要求包含有关正在操作的对象的信息。在Log4j 1.x中,可以通过以下方式实现:

if (logger.isDebugEnabled()) {
    logger.debug("Logging in user " + user.getName() + " with birthday " + user.getBirthdayCalendar());
}

反复这样做会使代码感觉更像是关于日志记录而不是当前的实际任务。此外,这还会导致日志级别被检查两次:一次是在调用isDebugEnabled时,另一次是在调用debug方法时。一个更好的替代方案是:

logger.debug("Logging in user {} with birthday {}", user.getName(), user.getBirthdayCalendar());

使用上面的代码,日志级别将只被检查一次,字符串构造也只有在启用调试日志时才会发生。

此外,你还可以将Throwable作为最后一个参数传递。在这种情况下,无需添加额外的替换参数,因为日志API会自动处理它。例如:

logger.debug("Logging in user {} with birthday {}", user.getName(), user.getBirthdayCalendar(), exception);

格式化参数

如果toString()方法不是您想要的,格式化器记录器会将格式化工作留给您。为了方便格式化,您可以使用与Java的Formatter相同的格式字符串。例如:

public static Logger logger = LogManager.getFormatterLogger("Foo");
 
logger.debug("Logging in user %s with birthday %s", user.getName(), user.getBirthdayCalendar());
logger.debug("Logging in user %1$s with birthday %2$tm %2$te,%2$tY", user.getName(), user.getBirthdayCalendar());
logger.debug("Integer.MAX_VALUE = %,d", Integer.MAX_VALUE);
logger.debug("Long.MAX_VALUE = %,d", Long.MAX_VALUE);

将Loggers与格式化loggers混合使用 格式化Loggers提供了对输出格式的精细控制,但缺点是必须指定正确的类型(例如,为%d格式参数传递除十进制整数以外的任何内容都会引发异常)。

如果您的主要用法是使用{}样式的参数,但偶尔需要对输出格式进行精细控制,您可以使用printf方法:

public static Logger logger = LogManager.getLogger("Foo");
 
logger.debug("Opening connection to {}...", someDataSource);
logger.printf(Level.INFO, "Logging in user %1$s with birthday %2$tm %2$te,%2$tY", user.getName(), user.getBirthdayCalendar());

Java 8 lambda对延迟日志记录的支持 在2.4版本中,Logger接口增加了对lambda表达式的支持。这使得客户端代码能够延迟记录消息,而无需显式检查请求的日志级别是否已启用。例如,以前会写:

// pre-Java 8 style optimization: explicitly check the log level
// to make sure the expensiveOperation() method is only called if necessary
if (logger.isTraceEnabled()) {
    logger.trace("Some long-running operation returned {}", expensiveOperation());
}

使用Java 8,可以使用lambda表达式达到相同的效果。不再需要显式检查日志级别:

// Java-8 style optimization: no need to explicitly check the log level:
// the lambda expression is not evaluated if the TRACE level is not enabled
logger.trace("Some long-running operation returned {}", () -> expensiveOperation());

总结

log4j2 是一个功能强大、性能卓越的优秀日志框架,它可以帮助开发者轻松记录应用程序中的各种事件和行为。在当前的日志框架中,log4j2在吞吐量和耗时上,占有明显的优势。

log4j2的日志输出有三种模式:同步方式,半异步方式,全异步方式,其中全异步方式的效果最佳,推荐使用log4j2的全异步方式。

log4j2在异步模式下,默认队列满后,采用等待的策略,会导致主业务线程等待,应适当采取Discard的策略,同时应对丢弃的日志级别设置为error以上级别,以最大限度的减少日志满而引起的系统性能下降。

log4j2在打印位置信息时,日志库会获取堆栈的快照,并遍历堆栈跟踪以找到位置信息。因此会严重影响吞吐量,实际应用场景中,高并发应用应关闭位置信息打印,以提高吞吐量。

在开发的过程中,应考虑级别低的日志的处理方式,使用函数式接口,降低日志在内存及cpu上不必要的资源使用。

点击这里给我留言吧

原文始发于微信公众号(扬哥手记):log4j原来要这么用

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/241142.html

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!