Micronaut 3.1.0+Log4j2实现X-RequestId请求头全链路日志注入
解决方案
问题根因说明
- 空指针异常是因为
Log4jLogEventFactory的实现类是Log4j2框架自行实例化的,不属于Micronaut容器管理的Bean,@Inject注解不会生效,因此requestScopedBean始终为null,该方案从实现逻辑上不可行。 - 原生
ThreadContext(MDC)无法跨线程是因为没有开启Micronaut的上下文传播配置,默认MDC仅绑定当前线程,不会传递给子线程或异步执行线程。
实现步骤
1. 开启Micronaut MDC上下文传播配置
在application.yml中添加以下配置,支持多线程、异步、反应式场景下的MDC自动传递:
micronaut: context: propagation: threads: true # 开启线程池场景MDC传播 reactive: true # 开启反应式场景MDC传播 server: thread-selection: IO logging: mdc: enabled: true # 全局开启MDC支持
2. 重构服务端过滤器,移除自定义LogEventFactory
删除LogFactoryConfig类中LogEventFactory的实现逻辑,仅保留服务端过滤器功能直接操作MDC,不需要额外定义RequestScopedBean:
import io.micronaut.core.order.Ordered; import io.micronaut.core.order.OrderUtil; import io.micronaut.http.HttpRequest; import io.micronaut.http.MutableHttpResponse; import io.micronaut.http.filter.HttpServerFilter; import io.micronaut.http.filter.ServerFilterChain; import org.apache.logging.log4j.ThreadContext; import org.reactivestreams.Publisher; import java.util.Optional; import java.util.UUID; @Filter(Filter.MATCH_ALL_PATTERN) public class RequestIdFilter implements HttpServerFilter, Ordered { private static final String REQUEST_ID_HEADER = "X-RequestId"; @Override public int getOrder() { // 设置最高优先级,保证请求最先进入该过滤器,最后离开 return OrderUtil.HIGHEST_PRECEDENCE; } @Override public Publisher<MutableHttpResponse<?>> doFilter(HttpRequest<?> request, ServerFilterChain chain) { // 从请求头获取RequestId,不存在可自行生成 String requestId = Optional.ofNullable(request.getHeaders().get(REQUEST_ID_HEADER)) .orElseGet(() -> UUID.randomUUID().toString().replace("-", "")); // 放入MDC ThreadContext.put(REQUEST_ID_HEADER, requestId); return chain.proceed(request) // 请求处理完成后清理MDC,避免线程复用导致的脏数据 .doFinally(signalType -> ThreadContext.remove(REQUEST_ID_HEADER)); } }
3. 修改Log4j2日志格式
修改log4j2.xml的PatternLayout,直接引用MDC中的X-RequestId变量,为空时自动输出空字符,适配无请求场景:
<PatternLayout> <pattern> %date{MM-dd HH:mm:ss,SSS} %-5level %logger{36} - %replace{%X{X-RequestId:-}}{.+}{**$0** }%msg%n </pattern> </PatternLayout>
上述配置会在有RequestId时自动输出**REQUEST_ID** 前缀,无RequestId时不输出任何多余字符,完全符合你的日志格式要求。
4. 跨微服务传递实现
新增HttpClient过滤器,发送请求时自动将当前MDC中的X-RequestId注入到请求头,实现全链路传递:
import io.micronaut.http.MutableHttpRequest; import io.micronaut.http.filter.ClientFilterChain; import io.micronaut.http.filter.HttpClientFilter; import org.apache.logging.log4j.ThreadContext; import org.reactivestreams.Publisher; @Filter(Filter.MATCH_ALL_PATTERN) public class RequestIdClientFilter implements HttpClientFilter { private static final String REQUEST_ID_HEADER = "X-RequestId"; @Override public Publisher<? extends HttpResponse<?>> doFilter(MutableHttpRequest<?> request, ClientFilterChain chain) { String requestId = ThreadContext.get(REQUEST_ID_HEADER); if (requestId != null) { request.header(REQUEST_ID_HEADER, requestId); } return chain.proceed(request); } }
5. 自定义线程场景适配
如果业务代码中存在自行创建线程的场景,直接注入Micronaut提供的InstrumentedExecutorService即可,该线程池会自动携带MDC上下文,无需手动传递:
@Inject ExecutorService executorService;
效果验证
- 无请求场景(服务启动等):MDC中无
X-RequestId,日志输出和原有格式完全一致 - 有请求场景:所有该请求关联的同步、异步、跨服务调用的日志都会自动携带
**REQUEST_ID**前缀,符合预期格式
内容的提问来源于stack exchange,提问作者svrdoljak
相关产品推荐
相关产品推荐

