如何为OpenTelemetry Java Agent配置X-CorrelationToken头同步至New Relic APM
背景
前端会给所有后端API请求发送X-CorrelationToken请求头,该Token会写入每条日志,方便出错时通过ELK检索日志。需要将这个请求头的值作为属性同步到New Relic APM,实现日志与服务链路的关联。
此前实现方式(New Relic Java Agent)
通过newrelic.yml配置即可实现需求:
transaction_events: custom_request_headers: - header_name: "X-CorrelationToken"
当前问题(OpenTelemetry Java Agent)
使用官方OpenTelemetry Java Agent时,尝试了以下配置参数,但均无效——服务的New Relic链路或Span中未出现X-CorrelationToken属性:
otel.instrumentation.http.client.capture-request-headers=X-CorrelationToken otel.instrumentation.http.capture-headers.client.request=X-CorrelationToken otel.instrumentation.common.experimental.capture-http-headers.client.request=X-CorrelationToken otel.instrumentation.common.experimental.capture-http-headers.server.request=X-CorrelationToken otel.instrumentation.http.capture-headers.server.request=X-CorrelationToken
期望实现的效果与New Relic Java Agent一致,即X-CorrelationToken属性显示在类似下图的位置:
解决方案
1. 正确配置OpenTelemetry HTTP头捕获参数
OpenTelemetry Java Agent的HTTP头捕获参数需区分服务端和客户端场景,且不同版本参数可能存在差异:
- 若为服务端接收前端请求,使用服务端头捕获参数:
otel.instrumentation.http.server.capture-headers=X-CorrelationToken - 若为后端作为客户端调用其他服务,使用客户端头捕获参数:
otel.instrumentation.http.client.capture-headers=X-CorrelationToken
注:确保使用较新的稳定版OpenTelemetry Agent,旧版本可能不支持上述参数,建议升级至最新版。
2. 自定义Span Processor手动添加属性
若配置参数无效,可通过自定义Span Processor提取请求头并添加到Span属性:
- 创建自定义Span Processor类:
import io.opentelemetry.api.trace.Span; import io.opentelemetry.context.Context; import io.opentelemetry.sdk.trace.ReadWriteSpan; import io.opentelemetry.sdk.trace.ReadableSpan; import io.opentelemetry.sdk.trace.SpanProcessor; import jakarta.servlet.http.HttpServletRequest; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; public class CorrelationTokenSpanProcessor implements SpanProcessor { @Override public void onStart(Context parentContext, ReadWriteSpan span) { ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); if (attributes != null) { HttpServletRequest request = attributes.getRequest(); String correlationToken = request.getHeader("X-CorrelationToken"); if (correlationToken != null && !correlationToken.isEmpty()) { span.setAttribute("X-CorrelationToken", correlationToken); } } } @Override public boolean isStartRequired() { return true; } @Override public void onEnd(ReadableSpan span) {} @Override public boolean isEndRequired() { return false; } }
- 将自定义Span Processor注册到OpenTelemetry SDK:
import io.opentelemetry.sdk.OpenTelemetrySdk; import io.opentelemetry.sdk.trace.SdkTracerProvider; import io.opentelemetry.sdk.trace.export.SimpleSpanProcessor; import io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporter; public class OpenTelemetryConfig { public static OpenTelemetrySdk createOpenTelemetry() { OtlpGrpcSpanExporter exporter = OtlpGrpcSpanExporter.builder() .setEndpoint("https://otlp.nr-data.net:4317") .addHeader("api-key", "YOUR_NEW_RELIC_LICENSE_KEY") .build(); SdkTracerProvider tracerProvider = SdkTracerProvider.builder() .addSpanProcessor(SimpleSpanProcessor.create(exporter)) .addSpanProcessor(new CorrelationTokenSpanProcessor()) .build(); return OpenTelemetrySdk.builder() .setTracerProvider(tracerProvider) .buildAndRegisterGlobal(); } }
注:Spring Boot项目可通过
@Configuration类注册该配置,确保自定义处理器被加载。
3. 配置日志与链路关联
为让日志中的X-CorrelationToken与New Relic Span关联,需配置日志框架(如Logback、Log4j2)的OpenTelemetry Appender,将Span属性同步到日志:
以Logback为例,添加OpenTelemetry Appender:
<appender name="OTEL" class="io.opentelemetry.instrumentation.logback.appender.v1_0.OpenTelemetryAppender"> <encoder> <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} %X{X-CorrelationToken} - %msg %ex%n</pattern> </encoder> </appender>
同时通过请求拦截器将Token写入MDC:
import org.slf4j.MDC; import org.springframework.web.servlet.HandlerInterceptor; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; public class CorrelationTokenInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String correlationToken = request.getHeader("X-CorrelationToken"); if (correlationToken != null) { MDC.put("X-CorrelationToken", correlationToken); } return true; } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { MDC.remove("X-CorrelationToken"); } }
内容的提问来源于stack exchange,提问作者John Little

