基于上下文使用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
相关产品推荐
相关产品推荐

