Log4j2相对于log4j来说扩展了多种接口,并重新定义了日志记录流程,并且引入了一些框架例如Disruptor来加速。Log4j2无论在日志记录上,还有效率速率上都相对于log4j有很大的进步,下面我们来看一些比较有意思的使用和配置的最佳实践。
在java8之前,有时候log日志的参数是某个方法,可能很耗时,为了提高性能,我们先检查级别,再打日志:
if (logger.isTraceEnabled()) {
logger.trace("One parameter {}. Some long-running operation returned {}", var1, expensiveOperation());
}
我们可以用lambda表达式达到同样效果:
logger.trace("One parameter {}. Some long-running operation returned {}", () -> var1, () -> expensiveOperation());
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);
输出:
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
但是注意{}这种占位符和格式化占位符不能共用,如果想交替使用,可以考虑:
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());
下面三种获取logger的方法是等价的
package org.apache.test;
public class MyTest {
private static final Logger logger = LogManager.getLogger(MyTest.class);
}
package org.apache.test;
public class MyTest {
private static final Logger logger = LogManager.getLogger(MyTest.class.getName());
}
package org.apache.test;
public class MyTest {
private static final Logger logger = LogManager.getLogger();
}
依赖:
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
application.yml配置:
##运行状态 actuator监控
endpoints:
loggers:
enabled: true
sensitive: false
management:
##服务路径
context-path: /manage
##服务端口
port: 8081
查询日志级别:
请求路径:Get
示例返回 :
{
"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:
{
"configuredLevel": "debug"
}
例如修改java.sql.Statement
的日志级别,路径就是/manage/loggers/java.sql.Statement
<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.配置全局日志为异步,设置系统变量:
System.setProperty("log4j2.contextSelector", "org.apache.logging.log4j.core.async.AsyncLoggerContextSelector");
同时添加disruptor依赖:
<dependency>
<groupId>com.lmaxgroupId>
<artifactId>disruptorartifactId>
<version>3.3.4version>
dependency>
2.可以针对某些logger配置成异步,将Logger标签替换成AsyncLogger,如果是root则替换成Asyncroot,同时加上includeLocation="true"
保证%l可以正常输出:
<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
:
#填写实现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=