Log4j2一些tips与最佳实践

2021-04-12 14:53:16 浏览数 (1)

Log4j2相对于log4j来说扩展了多种接口,并重新定义了日志记录流程,并且引入了一些框架例如Disruptor来加速。Log4j2无论在日志记录上,还有效率速率上都相对于log4j有很大的进步,下面我们来看一些比较有意思的使用和配置的最佳实践。

使用Tip

1.方法参数使用Java8 lambda表达式:

在java8之前,有时候log日志的参数是某个方法,可能很耗时,为了提高性能,我们先检查级别,再打日志:

代码语言:javascript复制
if (logger.isTraceEnabled()) {
    logger.trace("One parameter {}. Some long-running operation returned {}", var1, expensiveOperation());
}

我们可以用lambda表达式达到同样效果:

代码语言:javascript复制
logger.trace("One parameter {}. Some long-running operation returned {}", () -> var1, () -> expensiveOperation());

2.格式化字符串:

代码语言:javascript复制
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);

输出:

代码语言:javascript复制
2012-12-12 11:56:19,633 [main] DEBUG: User John Smith with birthday java.util.GregorianCalendar[time=?,areFieldsSet=false,areAllFieldsSet=false,lenient=true,zone=sun.util.calendar.ZoneInfo[id="America/New_York",offset=-18000000,dstSavings=3600000,useDaylight=true,transitions=235,lastRule=java.util.SimpleTimeZone[id=America/New_York,offset=-18000000,dstSavings=3600000,useDaylight=true,startYear=0,startMode=3,startMonth=2,startDay=8,startDayOfWeek=1,startTime=7200000,startTimeMode=0,endMode=3,endMonth=10,endDay=1,endDayOfWeek=1,endTime=7200000,endTimeMode=0]],firstDayOfWeek=1,minimalDaysInFirstWeek=1,ERA=?,YEAR=1995,MONTH=4,WEEK_OF_YEAR=?,WEEK_OF_MONTH=?,DAY_OF_MONTH=23,DAY_OF_YEAR=?,DAY_OF_WEEK=?,DAY_OF_WEEK_IN_MONTH=?,AM_PM=0,HOUR=0,HOUR_OF_DAY=0,MINUTE=0,SECOND=0,MILLISECOND=?,ZONE_OFFSET=?,DST_OFFSET=?]
2012-12-12 11:56:19,643 [main] DEBUG: User John Smith with birthday 05 23, 1995
2012-12-12 11:56:19,643 [main] DEBUG: Integer.MAX_VALUE = 2,147,483,647
2012-12-12 11:56:19,643 [main] DEBUG: Long.MAX_VALUE = 9,223,372,036,854,775,807

但是注意{}这种占位符和格式化占位符不能共用,如果想交替使用,可以考虑:

代码语言:javascript复制
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());

3.自动配置Logger名字

下面三种获取logger的方法是等价的

代码语言:javascript复制
package org.apache.test;

public class MyTest {
    private static final Logger logger = LogManager.getLogger(MyTest.class);
}
代码语言:javascript复制
package org.apache.test;

public class MyTest {
    private static final Logger logger = LogManager.getLogger(MyTest.class.getName());
}
代码语言:javascript复制
package org.apache.test;

public class MyTest {
    private static final Logger logger = LogManager.getLogger();
}

4.Spring Actuator提供接口动态修改日志级别

依赖:

代码语言:javascript复制
<dependency>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-actuatorartifactId>
dependency>

application.yml配置:

代码语言:javascript复制
##运行状态 actuator监控
endpoints:
  loggers:
    enabled: true
    sensitive: false
management:
  ##服务路径
  context-path: /manage
  ##服务端口
  port: 8081

查询日志级别:

请求路径:Get 示例返回 :

代码语言:javascript复制
{

      "levels" : [
         "OFF",
         "FATAL",
         "ERROR",
         "WARN",
         "INFO",
         "DEBUG",
         "TRACE" 
    ],

      "loggers" : {

            "ROOT" : {

                  "configuredLevel" : "INFO",

                  "effectiveLevel" : "INFO"

        },

            "RocketmqClient" : {

                  "configuredLevel" : "ERROR",

                  "effectiveLevel" : "ERROR"

        },

            "com.mybatis" : {

                  "configuredLevel" : "DEBUG",

                  "effectiveLevel" : "DEBUG"

        },

            "java.sql" : {

                  "configuredLevel" : "INFO",

                  "effectiveLevel" : "INFO"

        },

            "java.sql.Connection" : {

                  "configuredLevel" : "DEBUG",

                  "effectiveLevel" : "DEBUG"

        },

            "java.sql.PreparedStatement" : {

                  "configuredLevel" : "ERROR",

                  "effectiveLevel" : "ERROR"

        },

            "java.sql.Statement" : {

                  "configuredLevel" : "ERROR",

                  "effectiveLevel" : "ERROR"

        },

            "org.mybatis" : {

                  "configuredLevel" : "INFO",

                  "effectiveLevel" : "INFO"

        }

    }

} 

修改日志级别

请求路径:POST /manage/loggers/{elephant}

Body:

代码语言:javascript复制
{
    "configuredLevel": "debug"
}

例如修改java.sql.Statement的日志级别,路径就是/manage/loggers/java.sql.Statement

日志生产最佳配置

同步日志

代码语言:javascript复制
<configuration>
    <Properties>
        <Property name="springAppName">service-realSportsGameProperty>
        <Property name="LOG_ROOT">logProperty>
        <Property name="LOG_EXCEPTION_CONVERSION_WORD">%xwExProperty>
        <Property name="LOG_LEVEL_PATTERN">%5pProperty>
        <Property name="logFormat">
           %d{yyyy-MM-dd HH:mm:ss.SSS} ${LOG_LEVEL_PATTERN} [%X{X-B3-TraceId},%X{X-B3-SpanId}] [${sys:PID}] [%t] %logger{40}: %m%n${sys:LOG_EXCEPTION_CONVERSION_WORD}
        Property>
    Properties>


    <appenders>
        <Console name="console" target="SYSTEM_OUT">
            
            <ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/>
            <PatternLayout pattern="${logFormat}"/>
        Console>
        
        <RollingFile name="file"
                     fileName="${LOG_ROOT}/${springAppName}/${springAppName}.log" append="true"
                     filePattern="${LOG_ROOT}/${springAppName}/${springAppName}.%d{yyyy-MM-dd}.gz">
            <PatternLayout pattern="${logFormat}" />
            <Policies>
                <TimeBasedTriggeringPolicy interval="1"
                                           modulate="true" />
            Policies>
            <DefaultRolloverStrategy>
                
                <Delete basePath="${LOG_ROOT}/${springAppName}" maxDepth="1">
                    <IfFileName glob="*${springAppName}.log.*">
                        <IfAny>
                            <IfAccumulatedFileCount exceeds="6" />
                        IfAny>
                    IfFileName>
                Delete>
            DefaultRolloverStrategy>
        RollingFile>

    appenders>

    <loggers>
        <Logger name="RocketmqClient" level="error" additivity="false">
            <appender-ref ref="console"/>
            <appender-ref ref="file"/>
        Logger>

        <Logger name="org.mybatis" level="INFO"/>
        <Logger name="java.sql" level="INFO"/>
        <Logger name="com.alibaba.druid" level="WARN"/>
        <root level="info">
            <AppenderRef  ref="file"/>
            <AppenderRef  ref="console"/>
        root>
    loggers>
configuration>

异步日志

如果为了性能,不考虑日志丢失,那么可以使用异步日志。Log4J2有两种方式配置异步日志:

1.配置全局日志为异步,设置系统变量:

代码语言:javascript复制
System.setProperty("log4j2.contextSelector", "org.apache.logging.log4j.core.async.AsyncLoggerContextSelector");

同时添加disruptor依赖:

代码语言:javascript复制
<dependency>
    <groupId>com.lmaxgroupId>
    <artifactId>disruptorartifactId>
    <version>3.3.4version>
dependency>

2.可以针对某些logger配置成异步,将Logger标签替换成AsyncLogger,如果是root则替换成Asyncroot,同时加上includeLocation="true"保证%l可以正常输出:

代码语言:javascript复制
<configuration>
    <Properties>
        <Property name="springAppName">service-realSportsGameProperty>
        <Property name="LOG_ROOT">logProperty>
        <Property name="LOG_EXCEPTION_CONVERSION_WORD">%xwExProperty>
        <Property name="LOG_LEVEL_PATTERN">%5pProperty>
        <Property name="logFormat">
           %d{yyyy-MM-dd HH:mm:ss.SSS} ${LOG_LEVEL_PATTERN} [%X{X-B3-TraceId},%X{X-B3-SpanId}] [${sys:PID}] [%t] %logger{40}: %m%n${sys:LOG_EXCEPTION_CONVERSION_WORD}
        Property>
    Properties>


    <appenders>
        <Console name="console" target="SYSTEM_OUT">
            
            <ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/>
            <PatternLayout pattern="${logFormat}"/>
        Console>
        
        <RollingFile name="file"
                     fileName="${LOG_ROOT}/${springAppName}/${springAppName}.log" append="true"
                     filePattern="${LOG_ROOT}/${springAppName}/${springAppName}.%d{yyyy-MM-dd}.gz">
            <PatternLayout pattern="${logFormat}" />
            <Policies>
                <TimeBasedTriggeringPolicy interval="1"
                                           modulate="true" />
            Policies>
            <DefaultRolloverStrategy>
                
                <Delete basePath="${LOG_ROOT}/${springAppName}" maxDepth="1">
                    <IfFileName glob="*${springAppName}.log.*">
                        <IfAny>
                            <IfAccumulatedFileCount exceeds="6" />
                        IfAny>
                    IfFileName>
                Delete>
            DefaultRolloverStrategy>
        RollingFile>

    appenders>

    <loggers>
        <AsyncLogger name="RocketmqClient" level="error" additivity="false" includeLocation="true">
            <appender-ref ref="console"/>
            <appender-ref ref="file"/>
        Logger>

        <Logger name="org.mybatis" level="INFO"/>
        <Logger name="java.sql" level="INFO"/>
        <Logger name="com.alibaba.druid" level="WARN"/>
        <Asyncroot level="info" includeLocation="true">
            <AppenderRef  ref="file"/>
            <AppenderRef  ref="console"/>
        root>
    loggers>
configuration>

异步日志特殊配置

有些进阶调优配置参数可以配置,在classpath根目录添加log4j2.component.properties:

代码语言:javascript复制
#填写实现com.lmax.disruptor.ExceptionHandler接口的类,可以自己实现异常处理
#log4j2.asyncLoggerExceptionHandler=default handler

#disruptor环形buffer的大小,最小值为128,相当于一个缓冲,程序启动就会初始化好,要足够大但也不要太大导致内存溢出
log4j2.asyncLoggerRingBufferSize=256 * 1024

#Disruptor内部的waitStrategy:
#Block是比较常见的挂起cpu的等待策略,这种消耗cpu最少,但是性能最差而且可能会丢失最多的日志。
#Timeout其实就是另一种Block,但是会定时主动苏醒检查是否就绪,没有就绪就继续采用Block的方式wait
#Yield最耗CPU,就是一直保持spin(CPU空转),这样日志写入延迟最小,但是吞吐量可能不是最大的
#Sleep是一种混合方式,先开始spin(CPU空转),之后没有就绪就 Thread.yield(),在之后就会block,这个很好协调CPU资源和性能,经测试吞吐量最高
log4j2.asyncLoggerWaitStrategy=Sleep

#是否缓存线程名称,默认为了提高性能是缓存的,但是如果你的程序会修改线程名称,就配置成UNCACHED
log4j2.asyncLoggerThreadNameStrategy=CACHED

#默认日志时间采用System.currentTimeMillis(),可以替换成实现org.apache.logging.log4j.core.util.Clock的类
#log4j2.clock=

0 人点赞