EAR中多WAR共享Core Jar,如何按调用WAR分离Log4J日志?
我之前处理过类似的多WAR共享核心Jar的日志隔离场景,结合你的需求(无法按包区分日志),这里有几个可行的方案,按推荐优先级排序:
方案一:升级到Log4j 2.x(最简洁,推荐)
Log4j 2.x原生支持Web应用的上下文隔离,每个WAR可以拥有独立的日志配置,即使共享的Core Jar也会自动使用调用它的WAR的日志上下文,完美解决你的问题。
步骤:
替换依赖:移除Log4j 1.x的
log4jJar,引入Log4j 2.x的核心依赖:log4j-api(API层,Core Jar需要依赖这个)log4j-core(核心实现)log4j-web(Web上下文支持,每个WAR都需要)
保留各WAR的日志配置:每个WAR继续使用自己的
log4j2.xml(或log4j2.properties),配置对应的输出文件(比如sales.log、intranet.log、lms.log)。Core Jar适配:确保Core Jar中的日志代码使用Log4j 2的API(
org.apache.logging.log4j.Logger和org.apache.logging.log4j.LogManager),而不是Log4j 1.x的API。如果Core Jar无法修改,可以引入log4j-1.2-api桥接Jar,让旧的Log4j 1.x代码自动适配Log4j 2的上下文。
这样配置后,当Sales WAR调用Core Jar时,Core的日志会自动输出到sales.log,内网WAR调用则输出到intranet.log,完全不需要额外的自定义代码。
方案二:Log4j 1.x下使用MDC + SiftingAppender(无需升级,适合遗留系统)
如果无法升级到Log4j 2,可以利用Log4j 1.x的**MDC(映射诊断上下文)**结合SiftingAppender(需要引入Log4j Extras扩展),通过标记请求来源的WAR,动态路由日志到对应文件。
步骤:
- 添加请求标识Filter:在每个WAR中实现一个Servlet Filter,在请求进入时设置MDC标记,标识当前WAR:
public class WarIdFilter implements Filter { private String warId; @Override public void init(FilterConfig config) throws ServletException { // 从web.xml的初始化参数获取当前WAR的唯一标识 warId = config.getInitParameter("warId"); } @Override public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { try { // 将WAR标识放入MDC MDC.put("warId", warId); chain.doFilter(req, res); } finally { // 请求结束后移除,避免内存泄漏 MDC.remove("warId"); } } @Override public void destroy() {} }
每个WAR的web.xml配置这个Filter,指定对应的warId:
<filter> <filter-name>WarIdFilter</filter-name> <filter-class>com.yourcompany.filter.WarIdFilter</filter-class> <init-param> <param-name>warId</param-name> <param-value>sales</param-value> <!-- 内网模块设为intranet,LMS设为lms --> </init-param> </filter> <filter-mapping> <filter-name>WarIdFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
- 配置SiftingAppender:在全局的
log4j.properties(或每个WAR的配置,建议全局统一)中配置SiftingAppender,根据MDC的warId动态选择日志文件:
# 引入Log4j Extras的SiftingAppender log4j.appender.sift=org.apache.log4j.sift.SiftingAppender # 指定用于区分的MDC键 log4j.appender.sift.key=warId # 无标识时默认输出到server.log log4j.appender.sift.default=server # 定义每个WAR对应的日志输出配置 log4j.appender.sift.appender=org.apache.log4j.RollingFileAppender log4j.appender.sift.appender.layout=org.apache.log4j.PatternLayout log4j.appender.sift.appender.layout.ConversionPattern=%d{ISO8601} %-5p [%c] %m%n # 使用MDC的warId动态生成日志文件名 log4j.appender.sift.appender.File=${catalina.base}/logs/%X{warId}.log log4j.appender.sift.appender.MaxFileSize=10MB log4j.appender.sift.appender.MaxBackupIndex=10 # 将Core Jar的包日志指向SiftingAppender,并关闭日志追加(避免重复输出) log4j.logger.com.yourcorepackage=INFO, sift log4j.additivity.com.yourcorepackage=false
这样,Core Jar的日志会根据调用它的WAR的MDC标识,自动输出到对应的日志文件中。
方案三:每个WAR部署独立的Core Jar副本(不推荐)
如果允许放弃EAR层的Core Jar共享,让每个WAR自己打包一份Core Jar,那么每个WAR的Core Jar会使用该WAR的Log4j配置,日志自然输出到对应文件。但这个方案会增加部署包体积,且可能引发类加载冲突(比如同一实体类被多个类加载器加载,导致类型转换异常),仅适合Core Jar无共享实体、无状态的场景。
内容的提问来源于stack exchange,提问作者Valerio Lopes

