Spring Boot 3集成OpenTelemetry Logback Appender后TraceID丢失问题
问题:Spring Boot 3集成OpenTelemetry日志导出后traceID/spanID/requestId丢失
在Spring Boot 3 + Tracing环境中,通过自定义SdkLoggerProvider手动实现OpenTelemetry日志导出,添加官方推荐的Logback Appender后,日志中的traceID、spanID、requestId等字段全部为空。
日志对比
未添加Logback Appender时的日志
2023-12-05T22:36:06.055Z DEBUG [payment-service,traceID=30855c52a5c6025aa466bd16a2ec9791,7cf1b551d2c66da6,requestId=c6e2286e-bc42-4b84-8c63-f33604986ddb] 36480 --- [payment-service] [nio-8080-exec-2] [30855c52a5c6025aa466bd16a2ec9791-7cf1b551d2c66da6] o.s.web.servlet.DispatcherServlet : GET "/v1/payments?usernameFrom=admina", parameters={masked} 2023-12-05T22:36:06.061Z DEBUG [payment-service,traceID=30855c52a5c6025aa466bd16a2ec9791,7cf1b551d2c66da6,requestId=c6e2286e-bc42-4b84-8c63-f33604986ddb] 36480 --- [payment-service] [nio-8080-exec-2] [30855c52a5c6025aa466bd16a2ec9791-7cf1b551d2c66da6] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to com.example.springboot.SimpleRestApplication$PaymentController#getAllByUsernameFrom(String) 2023-12-05T22:36:06.079Z INFO [payment-service,traceID=30855c52a5c6025aa466bd16a2ec9791,7cf1b551d2c66da6,requestId=c6e2286e-bc42-4b84-8c63-f33604986ddb] 36480 --- [payment-service] [nio-8080-exec-2] [30855c52a5c6025aa466bd16a2ec9791-7cf1b551d2c66da6] c.e.springboot.SimpleRestApplication : Return list of payments 2023-12-05T22:36:06.095Z DEBUG [payment-service,traceID=30855c52a5c6025aa466bd16a2ec9791,7cf1b551d2c66da6,requestId=c6e2286e-bc42-4b84-8c63-f33604986ddb] 36480 --- [payment-service] [nio-8080-exec-2] [30855c52a5c6025aa466bd16a2ec9791-7cf1b551d2c66da6] org.hibernate.SQL : select u1_0.username,u1_0.active,u1_0.balance,u1_0.locale from tb_user u1_0 where u1_0.username=? Hibernate: select u1_0.username,u1_0.active,u1_0.balance,u1_0.locale from tb_user u1_0 where u1_0.username=?
添加Logback Appender后的日志
2023-12-05T22:41:02.781Z DEBUG [payment-service,traceID=,,requestId=] 37024 --- [payment-service] [nio-8080-exec-3] [ ] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to com.example.springboot.SimpleRestApplication$PaymentController#getAllByUsernameFrom(String) 2023-12-05T22:41:02.783Z INFO [payment-service,traceID=,,requestId=] 37024 --- [payment-service] [nio-8080-exec-3] [ ] c.e.springboot.SimpleRestApplication : Return list of payments 2023-12-05T22:41:02.792Z DEBUG [payment-service,traceID=,,requestId=] 37024 --- [payment-service] [nio-8080-exec-3] [ ] org.hibernate.SQL : select u1_0.username,u1_0.active,u1_0.balance,u1_0.locale from tb_user u1_0 where u1_0.username=? Hibernate: select u1_0.username,u1_0.active,u1_0.balance,u1_0.locale from tb_user u1_0 where u1_0.username=?
现有代码及配置
Spring Boot主类代码
public static void main(String[] args) { new SpringApplicationBuilder(SimpleRestApplication.class) .initializers((ApplicationContextInitializer<GenericApplicationContext>) applicationContext -> applicationContext.registerBean(SdkLoggerProvider.class, () -> { ConfigurableEnvironment environment = applicationContext.getEnvironment(); String endpoint = environment.getProperty("otel.exporter.logs.endpoint", "http://localhost:4317"); return SdkLoggerProvider.builder() .addLogRecordProcessor( BatchLogRecordProcessor.builder(OtlpGrpcLogRecordExporter.builder().setEndpoint(endpoint).build()).build()) .build(); })) .run(args); }
application.yml配置
spring: application: name: payment-service management: endpoints: web: exposure: include: health,info,prometheus tracing: propagation: produce: w3c, b3, b3_multi consume: b3, w3c, b3_multi baggage: remote-fields: requestId correlation: fields: requestId sampling: probability: 1.0 otlp: metrics: export: url: ${OTLP_URL:http://localhost:4318}/v1/metrics tracing: endpoint: ${OTLP_URL:http://localhost:4318}/v1/traces opentelemetry: resource-attributes: "service.name": ${spring.application.name} logging: pattern: level: "%5p [${spring.application.name:},traceID=%X{traceId:-},%X{spanId:-},requestId=%X{requestId:-}]"
logback-spring.xml配置
<appender name="OpenTelemetry" class="io.opentelemetry.instrumentation.logback.appender.v1_0.OpenTelemetryAppender"> </appender> <root level="info"> <appender-ref ref="OpenTelemetry"/> <appender-ref ref="CONSOLE" /> </root>
解决方案
问题原因
自定义的SdkLoggerProvider未关联Spring Boot自动配置的OpenTelemetry上下文,导致日志Appender无法获取当前线程的trace/span信息;同时Logback Appender默认自动获取全局SdkLoggerProvider,但自定义Bean的初始化顺序和关联逻辑存在问题。
具体修改步骤
1. 调整SdkLoggerProvider初始化逻辑,关联Spring的OpenTelemetry实例
修改主类代码,使用Spring容器中已配置好的OpenTelemetry实例来构建SdkLoggerProvider,确保上下文传播正确:
public static void main(String[] args) { new SpringApplicationBuilder(SimpleRestApplication.class) .initializers((ApplicationContextInitializer<GenericApplicationContext>) applicationContext -> { // 获取Spring自动配置的OpenTelemetry实例 OpenTelemetry openTelemetry = applicationContext.getBean(OpenTelemetry.class); ConfigurableEnvironment environment = applicationContext.getEnvironment(); String endpoint = environment.getProperty("otel.exporter.logs.endpoint", "http://localhost:4317"); SdkLoggerProvider loggerProvider = SdkLoggerProvider.builder() .setResource(openTelemetry.getSdkTracerProvider().getResource()) .addLogRecordProcessor( BatchLogRecordProcessor.builder(OtlpGrpcLogRecordExporter.builder().setEndpoint(endpoint).build()).build()) // 绑定上下文传播器,确保能获取trace/span信息 .setContextPropagator(openTelemetry.getPropagators().getTextMapPropagator()) .build(); applicationContext.registerBean(SdkLoggerProvider.class, () -> loggerProvider); }) .run(args); }
2. 配置Logback Appender绑定自定义SdkLoggerProvider
在logback-spring.xml中为OpenTelemetryAppender指定Spring容器中的Bean名称,确保Appender使用正确的日志提供者:
<appender name="OpenTelemetry" class="io.opentelemetry.instrumentation.logback.appender.v1_0.OpenTelemetryAppender"> <!-- 指定Spring容器中SdkLoggerProvider的Bean名称 --> <loggerProviderBeanName>sdkLoggerProvider</loggerProviderBeanName> </appender> <root level="info"> <appender-ref ref="OpenTelemetry"/> <appender-ref ref="CONSOLE" /> </root>
3. 自定义日志处理器注入requestId字段
添加自定义LogRecordProcessor,将MDC中的requestId字段注入到OpenTelemetry日志属性中,确保导出的日志包含该字段:
SdkLoggerProvider loggerProvider = SdkLoggerProvider.builder() .setResource(openTelemetry.getSdkTracerProvider().getResource()) .addLogRecordProcessor( BatchLogRecordProcessor.builder(OtlpGrpcLogRecordExporter.builder().setEndpoint(endpoint).build()).build()) .setContextPropagator(openTelemetry.getPropagators().getTextMapPropagator()) // 注入requestId到日志属性 .addLogRecordProcessor(new LogRecordProcessor() { @Override public void onEmit(LogRecord logRecord) { String requestId = MDC.get("requestId"); if (requestId != null) { logRecord.setAttribute("requestId", AttributeValue.stringValue(requestId)); } } @Override public void shutdown() {} @Override public void forceFlush() {} }) .build();
4. 验证效果
启动应用后,控制台日志中的traceID、spanID、requestId会恢复正常,同时日志会正确导出到指定的OTLP端点。
内容的提问来源于stack exchange,提问作者user2669657
相关产品推荐
相关产品推荐

