之前对一个项目进行调优,将原先项目中的日志配置logBack,修改为log4j2,网上查找各种资料,官方文档,遇到很多坑,最终实现了修改,至此,特记录一下。
1.导入依赖,pom文件配置
SpringBoot官方文档:
一个干净的项目如果要使用log4j2,可以采用下面的方式引入依赖:
<!-- 排除 Spring-boot-starter 默认的日志配置 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- 引入log4j2依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>
因为在老项目中,日志依赖混杂,在项目中引入依赖情况:
<!-- slf4j的实现:log4j2,用来取代logback -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
<version>1.5.9.RELEASE</version>
</dependency>
<!-- log4j转 slf4j 输出-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>log4j-over-slf4j</artifactId>
<version>1.7.25</version>
</dependency>
<!--异步日志依赖-->
<dependency>
<groupId>com.lmax</groupId>
<artifactId>disruptor</artifactId>
<version>3.3.4</version>
</dependency>
总结:排干净项目中的Logback依赖,否则会导致日志在控制台打印却不能输出到文件中。
2.log4j2配置文件
参考(官方)文档:
在项目resource目录下,新建log4j2.xml 或者 log4j2-spring.xm l。建议使用log4j2-spring.xml,否则还需在application.properties或者.yml中配置好logging.config=你的配置文件名。
网上找来的xml模版:
<?xml version="1.0" encoding="UTF-8"?>
<!-- Configuration后面的status,这个用于设置log4j2自身内部的信息输出,可以不设置,
当设置成trace时,可以看到log4j2内部各种详细输出
-->
<!-- monitorInterval:Log4j能够自动检测修改配置 文件和重新配置本身,设置间隔秒数 -->
<configuration monitorInterval="5">
<!-- 日志级别以及优先级排序: OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL -->
<!-- 变量配置 -->
<Properties>
<!--
格式化输出:
%d表示日期,
%thread表示线程名,
%-5level:级别从左显示5个字符宽度
%msg:日志消息,%n是换行符
%logger{36} 表示 Logger 名字最长36个字符
-->
<property name="LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} %highlight{%-5level}[%thread] %style{%logger{36}}{cyan} : %msg%n" />
<!-- 定义日志存储的路径,不要配置相对路径 -->
<property name="FILE_PATH" value="此处更换为你的日志文件存储路径" />
<property name="FILE_NAME" value="此处更换为你的项目名称" />
</Properties>
<appenders>
<console name="Console" target="SYSTEM_OUT">
<!--输出日志的格式-->
<PatternLayout pattern="${LOG_PATTERN}" disableAnsi="false" noConsoleNoAnsi="false"/>
<!--控制台只输出level及其以上级别的信息(onMatch),其他的直接拒绝(onMismatch)-->
<ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/>
</console>
<!--
这个会打印出所有的info及以下级别的信息,每次大小超过size,
则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,作为存档
-->
<RollingFile name="RollingFileInfo" fileName="${FILE_PATH}/info.log" filePattern="${FILE_PATH}/${FILE_NAME}-INFO-%d{yyyy-MM-dd}_%i.log.gz">
<!--控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch)-->
<ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/>
<PatternLayout pattern="${LOG_PATTERN}"/>
<Policies>
<!--interval属性用来指定多久滚动一次,默认是1 hour-->
<TimeBasedTriggeringPolicy interval="1"/>
<SizeBasedTriggeringPolicy size="20MB"/>
</Policies>
<!-- DefaultRolloverStrategy属性如不设置,则默认为最多同一文件夹下7个文件开始覆盖 -->
<DefaultRolloverStrategy max="15"/>
</RollingFile>
<!-- 这个会打印出所有的warn及以下级别的信息,每次大小超过size,则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,作为存档-->
<RollingFile name="RollingFileWarn" fileName="${FILE_PATH}/warn.log" filePattern="${FILE_PATH}/${FILE_NAME}-WARN-%d{yyyy-MM-dd}_%i.log.gz">
<!--控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch)-->
<ThresholdFilter level="warn" onMatch="ACCEPT" onMismatch="DENY"/>
<PatternLayout pattern="${LOG_PATTERN}"/>
<Policies>
<!--interval属性用来指定多久滚动一次,默认是1 hour-->
<TimeBasedTriggeringPolicy interval="1"/>
<SizeBasedTriggeringPolicy size="20MB"/>
</Policies>
<!-- DefaultRolloverStrategy属性如不设置,则默认为最多同一文件夹下7个文件开始覆盖-->
<DefaultRolloverStrategy max="15"/>
</RollingFile>
<!-- 这个会打印出所有的error及以下级别的信息,每次大小超过size,则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,作为存档-->
<RollingFile name="RollingFileError" fileName="${FILE_PATH}/error.log" filePattern="${FILE_PATH}/${FILE_NAME}-ERROR-%d{yyyy-MM-dd}_%i.log.gz">
<!--控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch)-->
<ThresholdFilter level="error" onMatch="ACCEPT" onMismatch="DENY"/>
<PatternLayout pattern="${LOG_PATTERN}"/>
<Policies>
<!--interval属性用来指定多久滚动一次,默认是1 hour-->
<TimeBasedTriggeringPolicy interval="1"/>
<SizeBasedTriggeringPolicy size="20MB"/>
</Policies>
<!-- DefaultRolloverStrategy属性如不设置,则默认为最多同一文件夹下7个文件开始覆盖-->
<DefaultRolloverStrategy max="15"/>
</RollingFile>
</appenders>
<!--Logger节点用来单独指定日志的形式,比如要为指定包下的class指定不同的日志级别等。-->
<!--然后定义loggers,只有定义了logger并引入的appender,appender才会生效-->
<loggers>
<!--过滤掉spring和mybatis的一些无用的DEBUG信息-->
<logger name="org.mybatis" level="info" additivity="false">
<AppenderRef ref="Console"/>
</logger>
<!--监控系统信息-->
<!--若是additivity设为false,则 子Logger 只会在自己的appender里输出,而不会在 父Logger 的appender里输出。-->
<Logger name="org.springframework" level="info" additivity="false">
<AppenderRef ref="Console"/>
</Logger>
<root level="info">
<appender-ref ref="Console"/>
<appender-ref ref="RollingFileInfo"/>
<appender-ref ref="RollingFileWarn"/>
<appender-ref ref="RollingFileError"/>
</root>
</loggers>
</configuration>
3.配置文件解读
3.1 日志级别
在log4j2中, 共有8个级别,按照从低到高为:ALL < TRACE < DEBUG < INFO < WARN < ERROR < FATAL < OFF。
- All:最低等级的,用于打开所有日志记录;
- Trace:是追踪,就是程序推进一下;
- Debug:指出细粒度信息事件对调试应用程序是非常有帮助的;
- Info:消息在粗粒度级别上突出强调应用程序的运行过程;
- Warn:输出警告及warn以下级别的日志;
- Error:输出错误信息日志;
- Fatal:输出每个严重的错误事件将会导致应用程序的退出的日志;
- OFF:最高等级的,用于关闭所有日志记录。
Event Level | LoggerConfig Level | ||||||
---|---|---|---|---|---|---|---|
TRACE | DEBUG | INFO | WARN | ERROR | FATAL | OFF | |
ALL | YES | YES | YES | YES | YES | YES | NO |
TRACE | YES | NO | NO | NO | NO | NO | NO |
DEBUG | YES | YES | NO | NO | NO | NO | NO |
INFO | YES | YES | YES | NO | NO | NO | NO |
WARN | YES | YES | YES | YES | NO | NO | NO |
ERROR | YES | YES | YES | YES | YES | NO | NO |
FATAL | YES | YES | YES | YES | YES | YES | NO |
OFF | NO | NO | NO | NO | NO | NO | NO |
3.2 xml配置解读
根节点Configuration
有两个属性:
status:指定log4j2本身的打印日志的级别.
monitorinterval:指定log4j自动重新配置的监测间隔时间,单位是s,最小是5s.
有两个子节点:
Appenders
Loggers
Appenders节点
常见的有三种子节点:Console、RollingFile、File
Console节点用来定义输出到控制台的Appender.
name:指定Appender的名字.
target:SYSTEM_OUT 或 SYSTEM_ERR,一般只设置默认:SYSTEM_OUT.
PatternLayout:输出格式,不设置默认为:%m%n.
File节点用来定义输出到指定位置的文件的Appender.
name:指定Appender的名字.
fileName:指定输出日志的目的文件带全路径的文件名.
PatternLayout:输出格式,不设置默认为:%m%n.
RollingFile节点用来定义超过指定条件自动删除旧的创建新的Appender.
name:指定Appender的名字.
fileName:指定输出日志的目的文件带全路径的文件名.
PatternLayout:输出格式,不设置默认为:%m%n.
filePattern : 指定当发生Rolling时,文件的转移和重命名规则.
Policies:指定滚动日志的策略,就是什么时候进行新建日志文件输出日志.
TimeBasedTriggeringPolicy:Policies子节点,基于时间的滚动策略,interval属性用来指定多久滚动一次,默认是1 hour。modulate=true用来调整时间:比如现在是早上3am,interval是4,那么第一次滚动是在4am,接着是8am,12am...而不是7am.
SizeBasedTriggeringPolicy:Policies子节点,基于指定文件大小的滚动策略,size属性用来定义每个日志文件的大小.
DefaultRolloverStrategy:用来指定同一个文件夹下最多有几个日志文件时开始删除最旧的,创建新的(通过max属性)。
Loggers节点
Root节点用来指定项目的根日志,如果没有单独指定Logger,那么就会默认使用该Root日志输出
level:日志输出级别
AppenderRef:Root的子节点,用来指定该日志输出到哪个Appender
Logger节点用来单独指定日志的形式,比如要为指定包下的class指定不同的日志级别等。
level:日志输出级别
name:用来指定该Logger所适用的类或者类所在的包全路径,继承自Root节点.
AppenderRef:Logger的子节点,用来指定该日志输出到哪个Appender,如果没有指定,就会默认继承自Root.如果指定了,那么会在指定的这个Appender和Root的Appender中都会输出,此时我们可以设置Logger的additivity="false"只在自定义的Appender中进行输出。
4.log4j2-spring.xml读取.properties中的属性值
Spring Environment property: logging.config.并不能为log4j2-spring提供属性,我们需要通过MDC为其获取.properties中的属性。
4.1 写一个监听类
import org.slf4j.MDC;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent;
import org.springframework.boot.context.event.ApplicationFailedEvent;
import org.springframework.boot.context.event.ApplicationPreparedEvent;
import org.springframework.boot.context.event.ApplicationStartingEvent;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.event.ContextClosedEvent;
import org.springframework.context.event.GenericApplicationListener;
import org.springframework.core.Ordered;
import org.springframework.core.ResolvableType;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.PropertySource;
public class ApplicationStartedEventListener implements GenericApplicationListener {
public static final int DEFAULT_ORDER = Ordered.HIGHEST_PRECEDENCE + 10;
private static Class<?>[] EVENT_TYPES = { ApplicationStartingEvent.class,
ApplicationEnvironmentPreparedEvent.class, ApplicationPreparedEvent.class,
ContextClosedEvent.class, ApplicationFailedEvent.class };
private static Class<?>[] SOURCE_TYPES = { SpringApplication.class,
ApplicationContext.class };
@Override
public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof ApplicationEnvironmentPreparedEvent) {
ConfigurableEnvironment envi = ((ApplicationEnvironmentPreparedEvent) event).getEnvironment();
PropertySource<?> propertySource = envi.getPropertySources().get("读取到的配置文件,比如applicationConfig: [classpath:/application.properties]");
//你自己的逻辑,切记MDC.put(key,value)
...
MDC.put("LOG_PATH", loggingPath);
}
}
/*
* (non-Javadoc)
*
* @see org.springframework.core.Ordered#getOrder()
*/
@Override
public int getOrder() {
// TODO Auto-generated method stub
return DEFAULT_ORDER;
}
/*
* (non-Javadoc)
*
* @see org.springframework.context.event.GenericApplicationListener#
* supportsEventType(org.springframework.core.ResolvableType)
*/
@Override
public boolean supportsEventType(ResolvableType resolvableType) {
return isAssignableFrom(resolvableType.getRawClass(), EVENT_TYPES);
}
@Override
public boolean supportsSourceType(Class<?> sourceType) {
return isAssignableFrom(sourceType, SOURCE_TYPES);
}
private boolean isAssignableFrom(Class<?> type, Class<?>... supportedTypes) {
if (type != null) {
for (Class<?> supportedType : supportedTypes) {
if (supportedType.isAssignableFrom(type)) {
return true;
}
}
}
return false;
}
}
4.2 在Application启动类中添加自定义的启动监听ApplicationStartedEventListener
SpringApplication app = new SpringApplication(AppMain.class);
ApplicationStartedEventListener asel = new ApplicationStartedEventListener();
app.addListeners(asel);
app.run(args);
4.3 在log4j2.xml中使用MDC定义的属性
使用方式 ${ctx:key}
5. 异步日志
参考文档;logging.apache.org/log4j/2.x/m…
5.1 让所有的日志异步输出
jvm启动参数增加:
-Dlog4j2.contextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector
log4j2配置文件不需要变动
5.2 同步和异步混合使用
不需要增加5.1中的jvm启动参数。
官网的配置文件
<?xml version="1.0" encoding="UTF-8"?>
<!-- No need to set system property "log4j2.contextSelector" to any value
when using <asyncLogger> or <asyncRoot>. -->
<Configuration status="WARN">
<Appenders>
<!-- Async Loggers will auto-flush in batches, so switch off immediateFlush. -->
<RandomAccessFile name="RandomAccessFile" fileName="asyncWithLocation.log"
immediateFlush="false" append="false">
<PatternLayout>
<Pattern>%d %p %class{1.} [%t] %location %m %ex%n</Pattern>
</PatternLayout>
</RandomAccessFile>
</Appenders>
<Loggers>
<!-- pattern layout actually uses location, so we need to include it -->
<AsyncLogger name="com.foo.Bar" level="trace" includeLocation="true">
<AppenderRef ref="RandomAccessFile"/>
</AsyncLogger>
<Root level="info" includeLocation="true">
<AppenderRef ref="RandomAccessFile"/>
</Root>
</Loggers>
</Configuration>
6.混合使用同异步日志,异步不输出至控制台和文件的原因
之前配置log4j2.xml的时候,没有设置AsyncLogger、root的 level。这时,设置了AsyncLogger的类(name="类/包")不打印到控制台,也不输出到文件里,经过排查注释掉AsyncLogger,恢复了正常,于是暂时采用了完全异步模式输出日志。
建立一个新module只针对日志进行测试,移植以前的配置,发现在注释掉AsyncLogger后,控制台竟只输出ERROR级别的日志(当时只设置了INFO、WARN、ERROR三种级别),随后发现配置文件中缺少了logging.level.root=info(此处再给自己挖个坑,如果log4j2-spring.xml无法直接读取*.properties文件中的配置,那么root的日志级别是怎么设置的,AsyncLogger的日志级别是否也可以通过此方式进行配置),增加root的level,配置为info,此时控制台能够全部输出,增加AsyncLogger并设置level="info",解决问题。
总结:缺少设置AsyncLogger的level(目前测试默认是ERROR)。additivity="true"(此时控制台也可以输出)。
附上填坑过程代码;
package com.xyz.study.log;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
/**
* @author gem.xu
*/
@Component
public class LogTest {
private Logger log = LoggerFactory.getLogger(LogTest.class);
@PostConstruct
public void init(){
log.info("加载中 这是info信息");
log.warn("加载中 这是warn信息");
log.error("加载中 这是error信息");
//以为是被最后一个覆盖了,结果还是输出了error
log.info("加载中 这是info信息");
}
}
<?xml version="1.0" encoding="UTF-8"?>
<configuration status="OFF">
<Properties>
<property name="CONSOLE_LOG_PATTERN"
value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %highlight{%-5level} %logger{36} - %msg%n"/>
<property name="LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
<property name="FILE_PATH" value="${ctx:LOG_PATH}"/>
<property name="FILE_NAME" value="logtest"/>
</Properties>
<appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout>
<Pattern>${CONSOLE_LOG_PATTERN}</Pattern>
</PatternLayout>
</Console>
<!-- 日志输出配置 file文件方式 immediateFlush="true"-->
<RollingFile name="RollingFileInfo" fileName="${FILE_PATH}/${FILE_NAME}.log"
filePattern="${FILE_PATH}/%d{yyyy-MM-dd}/${FILE_NAME}-%d{yyyy-MM-dd}-%i.log.gz"
immediateFlush="false">
<PatternLayout>
<Pattern>${LOG_PATTERN}</Pattern>
</PatternLayout>
<!-- 记录日志级别-->
<ThresholdFilter level="INFO" onMatch="ACCEPT" onMismatch="DENY"/>
<Policies>
<!--interval属性用来指定多久滚动一次,默认是1 hour-->
<TimeBasedTriggeringPolicy interval="24"/>
<!-- 日志文件达到y阈值开始滚动 -->
<SizeBasedTriggeringPolicy size="500MB"/>
</Policies>
<!-- 同一文件夹最多多少 开始覆盖 默认7 这这里设置30-->
<DefaultRolloverStrategy max="30"/>
</RollingFile>
<!-- 日志输出配置 file文件方式-->
<RollingFile name="ERROR" fileName="${FILE_PATH}/${FILE_NAME}_error.log"
filePattern="${FILE_PATH}/%d{yyyy-MM-dd}/${FILE_NAME}-%d{yyyy-MM-dd}-%i.log.gz"
>
<PatternLayout>
<Pattern>${LOG_PATTERN}</Pattern>
</PatternLayout>
<!-- 记录日志级别-->
<ThresholdFilter level="ERROR" onMatch="ACCEPT" onMismatch="DENY"/>
<Policies>
<!--interval属性用来指定多久滚动一次,默认是24 hour-->
<TimeBasedTriggeringPolicy interval="24"/>
<!-- 日志文件达到y阈值开始滚动 -->
<SizeBasedTriggeringPolicy size="500MB"/>
</Policies>
<!-- 同一文件夹最多多少 开始覆盖 默认7 这这里设置30-->
<DefaultRolloverStrategy max="30"/>
</RollingFile>
</appenders>
<loggers>
<!-- 异步日志 -->
<AsyncLogger level="info" name="com.xyz.study.log.LogTest" includeLocation="true" additivity="true" >
<appender-ref ref="RollingFileInfo"/>
<appender-ref ref="ERROR"/>
</AsyncLogger>
<root >
<appender-ref level="info" ref="Console"/>
<!-- <appender-ref ref="RollingFileInfo"/>-->
<!-- <appender-ref ref="ERROR"/>-->
</root>
</loggers>
</configuration>