Log4j2.17.X异步Async Appender下自定义PatternConverter不生效问题
Log4j2 2.17.X 自定义PatternConverter在Async Appender下取值为空问题
问题表现
- 基于
PatternConverter插件实现自定义日志转换规则,通过占位符%s在日志中输出sessionId。 - 同步模式下,SMTP、控制台、文件Appender均可正常输出完整sessionId,日志内容符合预期。
- 将Appender包裹在
Async异步Appender中(以SMTP Appender为例),%s占位符对应位置输出为空,sessionId完全丢失。已尝试将Async Appender的includeLocation参数设为true,问题未解决。
转换规则配置
conversionPattern: %p: (%s) %c{1}: %m%n Time: %d{yyyy-MM-dd HH:mm:ss}
同步模式正常输出示例
INFO: (c5cwdac4-fw8e-12ec-cabf056af41fc) MyLoggerClass: Hello World. Time: 2022-06-24 00:25:34
Async异步模式异常输出示例
INFO: () MyLoggerClass: Hello World. Time: 2022-06-24 00:26:34
问题复现最小配置
<?xml version="1.0" ?> <Configuration name="DefaultLogger" status="INFO"> <Appenders> <SMTP name="SMTP" from="abc@xyz.com" to="acb@xyz.com" subject="Log4jLogger Test Message" smtpHost="mail" bufferSize="1"> <PatternLayout pattern="%p: (%s) %c{1}: %m%n Time: %d{yyyy-MM-dd HH:mm:ss}"/> <ThresholdFilter onMatch="ACCEPT" onMismatch="DENY" level="ERROR"/> </SMTP> <Async name="Async" includeLocation="true"> <AppenderRef ref="SMTP"/> </Async> </Appenders> <Loggers> <Logger name="com" level="INFO"/> <Root level="INFO"> <AppenderRef ref="Async"/> </Root> </Loggers> </Configuration>
根因分析
Async Appender基于生产者-消费者模型实现,日志事件由业务线程生成后,会被拷贝传递给独立的异步日志线程执行格式化、落盘/发送操作。
绝大多数自定义sessionId输出逻辑都是从业务线程的ThreadLocal中读取取值,当异步线程执行PatternConverter逻辑时,已经脱离原业务线程上下文,无法读取到原线程ThreadLocal中存储的sessionId,因此占位符输出为空。includeLocation参数仅控制日志事件是否携带调用类、方法、行号等位置信息,不会处理自定义ThreadLocal上下文的跨线程传递,因此修改该参数无法修复问题。
修复方案
- 实现自定义
ContextDataInjector,在日志事件生成阶段(业务线程内)就将ThreadLocal中存储的sessionId注入到LogEvent的上下文数据集合中,自定义PatternConverter直接从入参LogEvent携带的上下文数据中取值,禁止直接读取外部ThreadLocal。 - 若使用全局Async Logger而非Async Appender,可添加系统属性
log4j2.isThreadContextMapInheritable=true,通过InheritableThreadLocal传递上下文,但该方案对Async Appender的跨线程场景兼容性较差,优先选择上下文注入方案。 - 编写自定义PatternConverter时,所有取值逻辑必须完全依赖方法入参传入的
LogEvent对象自身携带的属性,不要依赖任何线程绑定的外部状态。
内容的提问来源于stack exchange,提问作者S V
相关产品推荐
相关产品推荐

