You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何无需执行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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.04 22:30:32