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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.25 01:24:05