log4j2 不同用户/不同类别输出到不同目录
封面图是在今年11月底时去故宫拍的
概述
本文主要介绍 log4j2 使用 marker, routing, filter 来输出不同格式/不同目录/指定年月日分隔的日志, 基本上看下配置文件实操一下就明白怎么做了.
使用框架的是 Slf4j + log4j2 + lombok
源码地址 boom-java
来源于公司中记录 mqtt 日志的项目
内容
使用后得到的日志文件目录如下
maven 依赖
<!-- lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.10</version>
</dependency>
<!-- 日志 -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.12.1</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.12.1</version>
</dependency>
<dependency>
<groupId>com.lmax</groupId>
<artifactId>disruptor</artifactId>
<version>3.4.2</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.29</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
<version>2.12.1</version>
</dependency>
java 代码
import lombok.extern.slf4j.Slf4j;
import org.apache.logging.log4j.ThreadContext;
import org.slf4j.Marker;
import org.slf4j.MarkerFactory;
import java.util.concurrent.ThreadLocalRandom;
/**
* @author GiraffeTree
* @date 2019/12/5
*/
@Slf4j
public class Log4j2Test {
private final static Marker BOY = MarkerFactory.getMarker("boy");
private final static Marker GIRL = MarkerFactory.getMarker("girl");
private final static Marker OTHER = MarkerFactory.getMarker("other");
public static void main(String[] args) {
testRouting(1000, 3);
}
public static void addRandomLog() {
int num = ThreadLocalRandom.current().nextInt(0, 1000);
if (num < 950) {
ThreadContext.put("logFileName", "David");
log.info("current: {}", num);
} else {
log.error("current: {}", num);
}
}
public static void testRouting(int loopCount, int userSize) {
long l1 = System.currentTimeMillis();
int count = loopCount;
while (count > 0) {
writeMultipleUsersLog(userSize);
count--;
}
long l2 = System.currentTimeMillis();
System.out.println(String.format("size:%d loopCount:%d cost: %dms", userSize, loopCount, l2 - l1));
}
public static void writeMultipleUsersLog(int size) {
for (int i = 0; i < size; i++) {
String userName = "user" + i;
ThreadContext.put("logFileName", userName);
int num = ThreadLocalRandom.current().nextInt(0, 1200);
if (num < 500) {
log.info(BOY, "current: {}", num);
} else if (num < 1000) {
log.info(GIRL, "current: {}", num);
} else {
log.info(OTHER, "current: {}", num);
}
}
}
}
xml 配置文件
文件路径 resources/log4j2.xml
<?xml version="1.0" encoding="UTF-8"?>
<Configuration>
<Properties>
<!-- 日志输出级别 -->
<Property name="LOG_INFO_LEVEL" value="info"/>
<!-- error级别日志 -->
<Property name="LOG_ERROR_LEVEL" value="error"/>
<!-- 在当前目录下创建名为log目录做日志存放的目录 -->
<Property name="LOG_HOME" value="./log"/>
<!-- 档案日志存放目录 -->
<Property name="LOG_ARCHIVE_NAME" value="archive"/>
<!-- 模块名称, 影响日志配置名,日志文件名,根据自己项目进行配置 -->
<Property name="LOG_MODULE_NAME" value="boom-java"/>
<!-- 日志文件大小,超过这个大小将被压缩 -->
<Property name="LOG_MAX_SIZE" value="1 MB"/>
<!-- 保留多少天以内的日志 -->
<Property name="LOG_DAYS" value="15"/>
<!--输出日志的格式:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度, %msg:日志消息,%n是换行符 -->
<Property name="LOG_PATTERN" value="%d [%t] %-5level %logger{0} - %msg%n"/>
<!-- mqtt log -->
<Property name="MQTT_LOG_PATTERN" value="%msg%n"/>
<!--interval属性用来指定多久滚动一次-->
<Property name="TIME_BASED_INTERVAL" value="1"/>
</Properties>
<Appenders>
<!-- 控制台输出 -->
<Console name="STDOUT" target="SYSTEM_OUT">
<!--输出日志的格式-->
<PatternLayout pattern="${LOG_PATTERN}"/>
<!--控制台只输出level及其以上级别的信息(onMatch),其他的直接拒绝(onMismatch)-->
<ThresholdFilter level="${LOG_INFO_LEVEL}" onMatch="ACCEPT" onMismatch="DENY"/>
</Console>
<!-- 这个会打印出所有的info级别以上,error级别一下的日志,每次大小超过size或者满足TimeBasedTriggeringPolicy,则日志会自动存入按年月日建立的文件夹下面并进行压缩,作为存档-->
<!--异步日志会自动批量刷新,所以将immediateFlush属性设置为false-->
<Routing name="BoyRoutingAppender">
<Routes pattern="${ctx:logFileName}">
<Route>
<RollingRandomAccessFile name="RollingRandomAccessFileInfo2"
fileName="${LOG_HOME}/${date:yyyy}/${date:MM}/${date:dd}/boy/${ctx:logFileName}-info.log"
filePattern="${LOG_HOME}/${date:yyyy}/%d{MM}/%d{dd}/boy/${LOG_ARCHIVE_NAME}/${ctx:logFileName}-info-%i.log.gz"
immediateFlush="false">
<Filters>
<MarkerFilter marker="boy" onMatch="ACCEPT" onMismatch="DENY"/>
<!--如果是error级别拒绝,设置 onMismatch="NEUTRAL" 可以让日志经过后续的过滤器-->
<ThresholdFilter level="${LOG_ERROR_LEVEL}" onMatch="DENY" onMismatch="NEUTRAL"/>
<!--如果是info\warn输出-->
<ThresholdFilter level="${LOG_INFO_LEVEL}" onMatch="ACCEPT" onMismatch="NEUTRAL"/>
</Filters>
<PatternLayout pattern="${MQTT_LOG_PATTERN}"/>
<Policies>
<!--interval属性用来指定多久滚动一次,根据当前filePattern设置是1天滚动一次-->
<TimeBasedTriggeringPolicy interval="${TIME_BASED_INTERVAL}"/>
<SizeBasedTriggeringPolicy size="${LOG_MAX_SIZE}"/>
</Policies>
<!-- DefaultRolloverStrategy属性如不设置,则默认同一文件夹下最多保存7个文件-->
<DefaultRolloverStrategy max="${LOG_DAYS}"/>
</RollingRandomAccessFile>
</Route>
</Routes>
</Routing>
<Routing name="GirlRoutingAppender">
<Routes pattern="${ctx:logFileName}">
<Route>
<RollingRandomAccessFile name="RollingRandomAccessFileInfo2"
fileName="${LOG_HOME}/${date:yyyy}/${date:MM}/${date:dd}/girl/${ctx:logFileName}-info.log"
filePattern="${LOG_HOME}/${date:yyyy}/%d{MM}/%d{dd}/girl/${LOG_ARCHIVE_NAME}/${ctx:logFileName}-info-%i.log.gz"
immediateFlush="false">
<Filters>
<MarkerFilter marker="girl" onMatch="ACCEPT" onMismatch="DENY"/>
<!--如果是error级别拒绝,设置 onMismatch="NEUTRAL" 可以让日志经过后续的过滤器-->
<ThresholdFilter level="${LOG_ERROR_LEVEL}" onMatch="DENY" onMismatch="NEUTRAL"/>
<!--如果是info\warn输出-->
<ThresholdFilter level="${LOG_INFO_LEVEL}" onMatch="ACCEPT" onMismatch="NEUTRAL"/>
</Filters>
<PatternLayout pattern="${MQTT_LOG_PATTERN}"/>
<Policies>
<!--interval属性用来指定多久滚动一次,根据当前filePattern设置是1天滚动一次-->
<TimeBasedTriggeringPolicy interval="${TIME_BASED_INTERVAL}"/>
<SizeBasedTriggeringPolicy size="${LOG_MAX_SIZE}"/>
</Policies>
<!-- DefaultRolloverStrategy属性如不设置,则默认同一文件夹下最多保存7个文件-->
<DefaultRolloverStrategy max="${LOG_DAYS}"/>
</RollingRandomAccessFile>
</Route>
</Routes>
</Routing>
<Routing name="OtherRoutingAppender">
<Routes pattern="${ctx:logFileName}">
<Route>
<RollingRandomAccessFile name="RollingRandomAccessFileInfo2"
fileName="${LOG_HOME}/${date:yyyy}/${date:MM}/${date:dd}/other/${ctx:logFileName}-info.log"
filePattern="${LOG_HOME}/${date:yyyy}/%d{MM}/%d{dd}/other/${LOG_ARCHIVE_NAME}/${ctx:logFileName}-info-%i.log.gz"
immediateFlush="false">
<Filters>
<MarkerFilter marker="other" onMatch="ACCEPT" onMismatch="DENY"/>
<!--如果是error级别拒绝,设置 onMismatch="NEUTRAL" 可以让日志经过后续的过滤器-->
<ThresholdFilter level="${LOG_ERROR_LEVEL}" onMatch="DENY" onMismatch="NEUTRAL"/>
<!--如果是info\warn输出-->
<ThresholdFilter level="${LOG_INFO_LEVEL}" onMatch="ACCEPT" onMismatch="NEUTRAL"/>
</Filters>
<PatternLayout pattern="${MQTT_LOG_PATTERN}"/>
<Policies>
<!--interval属性用来指定多久滚动一次,根据当前filePattern设置是1天滚动一次-->
<TimeBasedTriggeringPolicy interval="${TIME_BASED_INTERVAL}"/>
<SizeBasedTriggeringPolicy size="${LOG_MAX_SIZE}"/>
</Policies>
<!-- DefaultRolloverStrategy属性如不设置,则默认同一文件夹下最多保存7个文件-->
<DefaultRolloverStrategy max="${LOG_DAYS}"/>
</RollingRandomAccessFile>
</Route>
</Routes>
</Routing>
<Routing name="RoutingAppender">
<Routes pattern="${ctx:logFileName}">
<Route>
<RollingRandomAccessFile name="RollingRandomAccessFileInfo"
fileName="${LOG_HOME}/${date:yyyy}/${date:MM}/${date:dd}/${ctx:logFileName}-info.log"
filePattern="${LOG_HOME}/${date:yyyy}/%d{MM}/%d{dd}/${LOG_ARCHIVE_NAME}/${ctx:logFileName}-info-%i.log.gz"
immediateFlush="false">
<Filters>
<MarkerFilter marker="other" onMatch="DENY" onMismatch="NEUTRAL"/>
<MarkerFilter marker="boy" onMatch="DENY" onMismatch="NEUTRAL"/>
<MarkerFilter marker="girl" onMatch="DENY" onMismatch="NEUTRAL"/>
<!--如果是error级别拒绝,设置 onMismatch="NEUTRAL" 可以让日志经过后续的过滤器-->
<ThresholdFilter level="${LOG_ERROR_LEVEL}" onMatch="DENY" onMismatch="NEUTRAL"/>
<!--如果是info\warn输出-->
<ThresholdFilter level="${LOG_INFO_LEVEL}" onMatch="ACCEPT" onMismatch="DENY"/>
</Filters>
<PatternLayout pattern="${LOG_PATTERN}"/>
<Policies>
<!--interval属性用来指定多久滚动一次,根据当前filePattern设置是1天滚动一次-->
<TimeBasedTriggeringPolicy interval="${TIME_BASED_INTERVAL}"/>
<SizeBasedTriggeringPolicy size="${LOG_MAX_SIZE}"/>
</Policies>
<!-- DefaultRolloverStrategy属性如不设置,则默认同一文件夹下最多保存7个文件-->
<DefaultRolloverStrategy max="${LOG_DAYS}"/>
</RollingRandomAccessFile>
</Route>
<Route ref="STDOUT" key="${ctx:logFileName}"/>
</Routes>
</Routing>
<!--只记录error级别以上的日志,与info级别的日志分不同的文件保存-->
<RollingRandomAccessFile name="RollingRandomAccessFileError"
fileName="${LOG_HOME}/${LOG_MODULE_NAME}-errorLog.log"
filePattern="${LOG_ARCHIVE}/${LOG_MODULE_NAME}-errorLog-%d{yyyy-MM-dd}-%i.log.gz"
immediateFlush="false">
<Filters>
<ThresholdFilter level="${LOG_ERROR_LEVEL}" onMatch="ACCEPT" onMismatch="DENY"/>
</Filters>
<PatternLayout pattern="${LOG_PATTERN}"/>
<Policies>
<TimeBasedTriggeringPolicy interval="${TIME_BASED_INTERVAL}"/>
<SizeBasedTriggeringPolicy size="${LOG_MAX_SIZE}"/>
</Policies>
<DefaultRolloverStrategy max="${LOG_DAYS}"/>
</RollingRandomAccessFile>
</Appenders>
<Loggers>
<!-- 开发环境使用 -->
<!--<Root level="${LOG_INFO_LEVEL}">
<AppenderRef ref="STDOUT"/>
</Root>-->
<!-- 生产环境使用 -->
<Root level="${LOG_INFO_LEVEL}" includeLocation="false">
<AppenderRef ref="RoutingAppender"/>
<AppenderRef ref="OtherRoutingAppender"/>
<AppenderRef ref="BoyRoutingAppender"/>
<AppenderRef ref="GirlRoutingAppender"/>
<AppenderRef ref="RollingRandomAccessFileError"/>
</Root>
</Loggers>
</Configuration>
常见问题
Multiple default routes. Route Route(type=dynamic - type=Route default) will be ignored
<Routes pattern="${ctx:logFileName}">
<Route>
...
</Route>
<Route>
...
</Route>
</Routes>
Routes 节点内有多个Route , 且 Route 的 key 没有指定, 导致 logj4j 认为有多个默认的 Route
应该写成
<Routes pattern="${ctx:logFileName}">
<Route key="specialFileName01">
...
</Route>
<Route>
...
</Route>
</Routes>
SLF4J: Failed to load class “org.slf4j.impl.StaticLoggerBinder / log4j:WARN No appenders could be found for logger
pom 中添加 log4j-slf4j-impl 依赖, 注意 scope
其他参考
- How to write different logs in different files with log4j2 (MDC in xml)?
- https://stackoverflow.com/questions/17827923/how-to-write-different-logs-in-different-files-with-log4j2-mdc-in-xml