如何无需执行log4j2 reconfigure()实现基于MDC的动态日志文件名
问题
需求:根据MDC中fileName的值,将日志写入对应名称的文件。
现有代码示例:
public class A { void test() { // 期望日志写入A.log MDC.put("fileName", "A"); // LoggerContext ctx = (LoggerContext)LoggerContext.getContext(false); // ctx.reconfigure(); log.info("aaa"); C c = new C(); c.test(); } } public class B { void test() { // 期望日志写入B.log MDC.put("fileName", "B"); // LoggerContext ctx = (LoggerContext)LoggerContext.getContext(false); // ctx.reconfigure(); log.info("bbb"); C c = new C(); c.test(); } } public class C { void test() { log.info("cccc"); } }
当前log4j2.xml配置:
<RollingFile name="rollingFile" fileName="/home/logs/${ctx:key}.log" filePattern="/home/logs/application-${ctx:key}-i.log"> <PatternLayout pattern="[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} %m%n"/> <Policies> <SizeBasedTriggeringPolicy size="2MB"/> </Policies> <DefaultRolloverStrategy max="10"/> </RollingFile>
目前取消注释reconfigure()代码后可实现需求:执行A.test()时,"aaa"、"cccc"写入A.log;执行B.test()时,"bbb"、"cccc"写入B.log。但高并发场景下,频繁调用reconfigure()会带来IO资源占用、性能下降等问题。已知log4j2可通过%X{key}实时获取MDC值,想知道是否无需每次变更MDC都执行reconfigure(),或者有其他更优的实现方式?
解决方案
完全不需要每次变更MDC都调用reconfigure(),这种方式不仅低效,还会在高并发下引发资源竞争问题。正确的做法是使用Log4j2的RoutingAppender结合MDC动态路由日志,它可以根据MDC值自动将日志分发到对应的文件,无需重新配置上下文。
步骤1:修改log4j2.xml配置
使用RoutingAppender替代原来的RollingFile,配置如下:
<Appenders> <!-- 定义通用的RollingFile模板 --> <RollingFile name="RollingFileTemplate" fileName="/home/logs/%X{fileName}.log" filePattern="/home/logs/application-%X{fileName}-%i.log"> <PatternLayout pattern="[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} %m%n"/> <Policies> <SizeBasedTriggeringPolicy size="2MB"/> </Policies> <DefaultRolloverStrategy max="10"/> </RollingFile> <!-- 路由Appender,根据MDC的fileName值动态选择目标文件 --> <Routing name="Routing"> <Routes pattern="$${ctx:fileName}"> <!-- 当MDC中存在fileName值时,使用上面定义的RollingFile模板 --> <Route ref="RollingFileTemplate"/> <!-- 兜底路由:如果MDC中没有fileName值,写入默认日志文件 --> <Route key="$${ctx:fileName}" ref="DefaultRollingFile"/> </Routes> </Routing> <!-- 兜底的默认日志Appender --> <RollingFile name="DefaultRollingFile" fileName="/home/logs/default.log" filePattern="/home/logs/application-default-%i.log"> <PatternLayout pattern="[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} %m%n"/> <Policies> <SizeBasedTriggeringPolicy size="2MB"/> </Policies> <DefaultRolloverStrategy max="10"/> </RollingFile> </Appenders> <Loggers> <Root level="info"> <AppenderRef ref="Routing"/> </Root> </Loggers>
步骤2:修改业务代码
移除所有reconfigure()调用,只保留MDC的put操作即可,注意线程池场景要清理MDC值:
public class A { void test() { MDC.put("fileName", "A"); log.info("aaa"); C c = new C(); c.test(); // 线程复用场景(如线程池)必须移除,避免污染后续请求 MDC.remove("fileName"); } } public class B { void test() { MDC.put("fileName", "B"); log.info("bbb"); C c = new C(); c.test(); MDC.remove("fileName"); } }
关键说明
- RoutingAppender会实时读取MDC中的
fileName值,自动将日志路由到对应的文件,无需重新配置LoggerContext,完全适配高并发场景。 - 配置中的
%X{fileName}是直接读取MDC值的语法,结合Routes的pattern实现动态路由逻辑。 - 线程池场景下必须在方法结束时调用
MDC.remove("fileName"),因为MDC是线程绑定的,线程复用会导致MDC值残留,引发日志写入错误文件的问题。
内容的提问来源于stack exchange,提问作者roov
相关产品推荐
相关产品推荐

