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

基于上下文使用Log4j将日志输出至不同目录的实现方案

解决方案

一、利用MDC(映射诊断上下文)实现按上下文分目录

这是logback中处理上下文日志的原生方案,无需创建大量Logger,同上下文的线程可共享MDC标识来统一日志目录。

1. 修改XML配置

在RollingFile Appender的路径中引入MDC变量,为无上下文标识的日志设置默认目录:

<Configuration>
    <Appenders>    
        <RollingFile name="RollingFile" fileName="${sys:logfiledir}/${mdc:contextDir:-default}/out.log" filePattern="${sys:logfiledir}/${mdc:contextDir:-default}/out-%d{yyyy-MM-dd}-%i.log">
            <PatternLayout>
                <pattern>%-5p %d{yyyy-MM-dd HH:mm:ss.SSS}{GMT+0} UTC %C:%L - %msg%n</pattern>
            </PatternLayout>
            <Policies>
                <SizeBasedTriggeringPolicy size="10 MB" />
            </Policies>
            <DefaultRolloverStrategy>
                <Delete basePath="${sys:logfiledir}/${mdc:contextDir:-default}" >
                    <IfLastModified age="10d" />
                </Delete>
            </DefaultRolloverStrategy>
        </RollingFile>
        <Console name="Console">
            <PatternLayout>
                <pattern>%-5p %d{yyyy-MM-dd HH:mm:ss.SSS}{GMT+0} UTC %C:%L - %msg%n</pattern>
            </PatternLayout>
        </Console>
    </Appenders>
    
    <Loggers>
        <Logger name="de" level="${sys:loglevel}" additivity="false">
            <appender-ref ref="RollingFile"/>
            <appender-ref ref="Console"/>
        </Logger>
    </Loggers>
</Configuration>

2. 上下文初始化时设置MDC值

在上下文启动阶段为所属线程注入标识,线程池场景需手动传递MDC上下文:

// 上下文启动时设置标识
MDC.put("contextDir", "context_a");

// 线程池任务提交时复制MDC上下文
Map<String, String> contextMap = MDC.getCopyOfContextMap();
executorService.submit(() -> {
    if (contextMap != null) {
        MDC.setContextMap(contextMap);
    }
    // 任务内的日志会自动输出到context_a目录
    logger.info("Log entry for context_a");
    MDC.clear(); // 任务结束后清理上下文
});

二、编程式创建Logger和Appender(备选方案)

若MDC方案不适用,可动态为每个上下文创建独立的Logger和Appender:

核心实现代码

import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.encoder.PatternLayoutEncoder;
import ch.qos.logback.core.rolling.RollingFileAppender;
import ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy;
import ch.qos.logback.core.util.FileSize;
import org.slf4j.LoggerFactory;

public class ContextLoggerFactory {

    public static org.slf4j.Logger getLogger(String contextId) {
        LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
        
        // 检查上下文Logger是否已存在,避免重复创建
        String loggerName = "de." + contextId;
        Logger existingLogger = (Logger) loggerContext.exists(loggerName);
        if (existingLogger != null) {
            return existingLogger;
        }
        
        // 创建日志格式编码器
        PatternLayoutEncoder encoder = new PatternLayoutEncoder();
        encoder.setContext(loggerContext);
        encoder.setPattern("%-5p %d{yyyy-MM-dd HH:mm:ss.SSS}{GMT+0} UTC %C:%L - %msg%n");
        encoder.start();
        
        // 创建滚动文件Appender
        RollingFileAppender rollingFileAppender = new RollingFileAppender();
        rollingFileAppender.setContext(loggerContext);
        rollingFileAppender.setName("RollingFile-" + contextId);
        String logDir = System.getProperty("logfiledir") + "/" + contextId;
        rollingFileAppender.setFile(logDir + "/out.log");
        
        // 配置滚动策略(按尺寸+时间滚动,保留10天日志)
        SizeAndTimeBasedRollingPolicy rollingPolicy = new SizeAndTimeBasedRollingPolicy();
        rollingPolicy.setContext(loggerContext);
        rollingPolicy.setParent(rollingFileAppender);
        rollingPolicy.setFileNamePattern(logDir + "/out-%d{yyyy-MM-dd}-%i.log");
        rollingPolicy.setMaxFileSize(FileSize.valueOf("10MB"));
        rollingPolicy.setMaxHistory(10);
        rollingPolicy.start();
        
        rollingFileAppender.setRollingPolicy(rollingPolicy);
        rollingFileAppender.setEncoder(encoder);
        rollingFileAppender.start();
        
        // 创建并配置Logger
        Logger logger = (Logger) LoggerFactory.getLogger(loggerName);
        logger.addAppender(rollingFileAppender);
        logger.setLevel(ch.qos.logback.classic.Level.toLevel(System.getProperty("loglevel")));
        logger.setAdditive(false); // 避免日志重复输出到根Logger
        
        return logger;
    }
}

使用方式

org.slf4j.Logger logger = ContextLoggerFactory.getLogger("context_a");
logger.info("Log entry for context_a");

注意事项

  • MDC方案中,线程池线程需手动复制上下文,否则子线程无法继承父线程的MDC标识。
  • 编程式创建时,需保证Logger命名唯一,避免与现有Logger冲突,同时确保Appender资源正确启动和关闭。

内容的提问来源于stack exchange,提问作者Matthias

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.06 13:25:25