Jaeger Trace MDC
1. æ¹æ¡æŠè¿°â
1.1 èæ¯äžç®æ â
åšååžåŒç³»ç»äžïŒè·šæå¡ç请æ±éŸè·¯è¿œèžªæ¯å®äœé®é¢çå ³é®ææ®µãæ¬æ¹æ¡æšåšå®ç° Jaeger ååžåŒè¿œèžªäž SLF4J MDCïŒMapped Diagnostic ContextïŒç深床éæïŒäœ¿åŸïŒ
- æ¥å¿èªåšæºåžŠ traceId/spanIdïŒäŸ¿äºæ¥å¿èåæ¥è¯¢
- æ¯æè·šæå¡ç trace äžäžæäŒ é
- åšçº¿çšæ± çåŒæ¥åºæ¯äžä¿æ trace äžäžæçåç¡®æ§
1.2 æ žå¿ä»·åŒâ
- é®é¢å®äœæçæåïŒéè¿ traceId å¿«éå ³èååžåŒç³»ç»äžçææçžå ³æ¥å¿
- é¶äŸµå ¥æ§ïŒäžå¡ä»£ç æ éæåšç®¡ç MDCïŒèªåšæ³šå ¥åæž ç
- 线çšå®å šïŒæ¯æåµå¥ Span åçº¿çšæ± å€çšåºæ¯
2. æ¶æè®Ÿè®¡â
2.1 æŽäœæ¶æåŸâ
âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
â Application Layer â
â ââââââââââââââââ ââââââââââââââââ ââââââââââââââââ â
â â Controller ââââ>â Service ââââ>â DAO â â
â â (HTTP Entry) â â (Business) â â (Database) â â
â ââââââââââââââââ ââââââââââââââââ ââââââââââââââââ â
â â â â â
â ââââââââââââââââââââââŽâââââââââââââââââââââ â
â â â
ââââââââââââââââââââââââââââââââŒâââââââââââââââââââââââââââââââââ€
â Tracing Layer ⌠â
â âââââââââââââââââââââââââââââââââââââââââââââââââ â
â â CustomMDCScopeManager (æ žå¿ç»ä»¶) â â
â â âââââââââââââââââââââââââââââââââââââââ â â
â â â ThreadLocal<CustomMDCScope> â â â
â â â - 管ç Scope çåœåšæ â â â
â â â - ç»Žæ€ Span æ â â â
â â âââââââââââââââââââââââââââââââââââââââ â â
â â â â
â â âââââââââââââââââââââââââââââââââââââââ â â
â â â CustomMDCScope (Scope å®ç°) â â â
â â â - MDC å¿«ç
§äžæ¢å€ â â â
â â â - æ¯æåµå¥ Span â â â
â â âââââââââââââââââââââââââââââââââââââââ â â
â âââââââââââââââââââââââââââââââââââââââââââââââââ â
â â â
ââââââââââââââââââââââââââââââââŒâââââââââââââââââââââââââââââââââ€
â Logging Layer ⌠â
â âââââââââââââââââââââââââââââââââââââââââââââââââ â
â â SLF4J MDC â â
â â âââââââââââââââââââââââââââââââââââââââ â â
â â â traceId: 04bf92f3577b34da... â â â
â â â spanId: 36bd32b7a5712a1a â â â
â â â parentId: 00f067aa0ba902b7 â â â
â â âââââââââââââââââââââââââââââââââââââââ â â
â âââââââââââââââââââââââââââââââââââââââââââââââââ â
â â â
ââââââââââââââââââââââââââââââââŒâââââââââââââââââââââââââââââââââ
âŒ
ââââââââââââââââââââââ
â Jaeger Collector â
â (Trace Storage) â
ââââââââââââââââââââââ
2.2 æ žå¿ç»ä»¶è¯Žæâ
2.2.1 CustomMDCScopeManagerâ
è莣ïŒå®ç° OpenTracing ç ScopeManager æ¥å£ïŒç®¡ç Span çæ¿æŽ»äžäŒ æ
å ³é®å®ç°ïŒ
private final ThreadLocal<CustomMDCScope> tlsScope = new ThreadLocal<>();
@Override
public Scope activate(Span span) {
return new CustomMDCScope(span);
}
@Override
public Span activeSpan() {
CustomMDCScope scope = tlsScope.get();
return scope == null ? null : scope.wrapped;
}
讟计èŠç¹ïŒ
- 䜿çš
ThreadLocalä¿è¯çº¿çšé犻 - æ¯æ Scope çåµå¥ïŒéè¿éŸè¡šç»æç»Žæ€ previousïŒ
- å®ç°ææ¿æŽ»ïŒä»
åšè°çš
activate()æ¶ææ³šå ¥ MDC
2.2.2 CustomMDCScopeâ
è莣ïŒå®ç° OpenTracing ç Scope æ¥å£ïŒç®¡çå䞪 Span ççåœåšæ
æ žå¿æºå¶ïŒå¿«ç §-æ³šå ¥-æ¢å€ïŒSnapshot-Inject-RestoreïŒ
CustomMDCScope(Span span) {
// 1. ä¿ååœå MDC å¿«ç
§
this.previousTraceId = MDC.get("traceId");
this.previousSpanId = MDC.get("spanId");
// 2. 建ç«éŸè¡šå
³ç³»ïŒæ¯æåµå¥ïŒ
this.previous = CustomMDCScopeManager.this.tlsScope.get();
CustomMDCScopeManager.this.tlsScope.set(this);
// 3. 泚å
¥æ°ç trace äžäžæ
MDC.put("traceId", span.context().toTraceId());
MDC.put("spanId", span.context().toSpanId());
}
@Override
public void close() {
// 4. æ¢å€å°äžäžå± Scope
CustomMDCScopeManager.this.tlsScope.set(previous);
// 5. æ¢å€ MDC å°å¿«ç
§ç¶æ
restoreMDC("traceId", previousTraceId);
restoreMDC("spanId", previousSpanId);
}
3. å ³é®æµçšè®Ÿè®¡â
3.1 æ¥æ¶è¿çš Trace äžäžæçæµçšâ
âââââââââââââââ
â HTTP Requestâ
â Headers â
â uber-trace-id: 4bf92f3577b...â
ââââââââ¬âââââââ
â
âŒ
ââââââââââââââââââââââââââââââââââââ
â 1. è§£æ HTTP Header â
â - æå traceId (128-bit) â
â - æå parentSpanId (64-bit) â
â - æå flags (éæ ·æ è®°) â
ââââââââ¬ââââââââââââââââââââââââââââ
â
âŒ
ââââââââââââââââââââââââââââââââââââ
â 2. æé JaegerSpanContext â
â long[] parts = splitTraceId() â
â new JaegerSpanContext( â
â traceIdHigh, â
â traceIdLow, â
â parentSpanId, â
â parentOfParentId, â
â flags â
â ) â
ââââââââ¬ââââââââââââââââââââââââââââ
â
âŒ
ââââââââââââââââââââââââââââââââââââ
â 3. å建å Span â
â tracer.buildSpan("child-span")â
â .asChildOf(parentContext)â
â .withTag(...) â
â .start() â
ââââââââ¬ââââââââââââââââââââââââââââ
â
âŒ
ââââââââââââââââââââââââââââââââââââ
â 4. æ¿æŽ» Span å°åœåçº¿çš â
â try (Scope scope = â
â tracer.scopeManager() â
â .activate(childSpan)) â
â { â
â // MDC èªåšæ³šå
¥ â
â // äžå¡é»èŸæ§è¡ â
â } // MDC èªåšæž
ç â
ââââââââââââââââââââââââââââââââââââ
3.2 åµå¥ Span ç MDC ç®¡çæµçšâ
æ¶éŽçº¿ ââââââââââââââââââââââââââââââââââââââââââââ>
ThreadLocal Stack:
âââââââââââââââââââââââââââââââââââââââââââââââââââ
â null â
âââââââââââââââââââââââââââââââââââââââââââââââââââ
activate(spanA)
âââââââââââââââââââââââââââââââââââââââââââââââââââ
â ScopeA: {traceId: xxx, spanId: A, previous: null}â
âââââââââââââââââââââââââââââââââââââââââââââââââââ
MDC: {traceId: xxx, spanId: A}
activate(spanB) // åµå¥è°çš
âââââââââââââââââââââââââââââââââââââââââââââââââââ
â ScopeB: {traceId: xxx, spanId: B, previous: ScopeA}â
âââââââââââââââââââââââââââââââââââââââââââââââââââ
MDC: {traceId: xxx, spanId: B} // spanId æŽæ°
// äžå¡ä»£ç æ§è¡
log.info("Processing...") // æ¥å¿æºåžŠ spanId=B
close(ScopeB)
âââââââââââââââââââââââââââââââââââââââââââââââââââ
â ScopeA: {traceId: xxx, spanId: A, previous: null}â
âââââââââââââââââââââââââââââââââââââââââââââââââââ
MDC: {traceId: xxx, spanId: A} // æ¢å€å° spanId=A
close(ScopeA)
âââââââââââââââââââââââââââââââââââââââââââââââââââ
â null â
âââââââââââââââââââââââââââââââââââââââââââââââââââ
MDC: {} // å®å
šæž
空
3.3 Trace ID åå²ç®æ³â
Jaeger æ¯æ 128-bit ç traceIdïŒäœ Java long ä»
䞺 64-bitïŒå æ€éèŠåå²ïŒ
/**
* å°åå
è¿å¶ traceId å笊䞲åå²äžºé«64äœåäœ64äœ
*
* 瀺äŸ:
* èŸå
¥: "04bf92f3577b34da63ce929d0e0e4736" (32䞪åå
è¿å¶å笊 = 128 bit)
* èŸåº: [0x04bf92f3577b34da, 0x63ce929d0e0e4736]
*/
public static long[] splitTraceId(String traceIdHex) {
if (traceIdHex.length() <= 16) {
// ä»
æäœ64äœïŒé«äœäžº0
return new long[]{0L, parseLong(traceIdHex)};
} else {
// åå²äžºé«64äœåäœ64äœ
String highHex = traceIdHex.substring(0, traceIdHex.length() - 16);
String lowHex = traceIdHex.substring(traceIdHex.length() - 16);
return new long[]{parseLong(highHex), parseLong(lowHex)};
}
}
4. é 眮äžéæâ
4.1 Tracer åå§åé 眮â
static Tracer tracer = new Configuration("order-service")
.withSampler(
new Configuration.SamplerConfiguration()
.withType("const")
.withParam(1) // éæ ·ç 100%
)
.withReporter(
new Configuration.ReporterConfiguration()
.withLogSpans(true) // åŒåç¯å¢å¯çšæ¥å¿èŸåº
.withSender(
new Configuration.SenderConfiguration()
.withAgentHost("localhost")
.withAgentPort(6831)
)
)
.getTracerBuilder()
.withScopeManager(new CustomMDCScopeManager()) // å
³é®ïŒæ³šå
¥èªå®ä¹ç®¡çåš
.build();
4.2 Logback é 眮â
<configuration>
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} [traceId=%X{traceId} spanId=%X{spanId}] - %msg%n</pattern>
</encoder>
</appender>
<appender name="JSON" class="ch.qos.logback.core.FileAppender">
<file>logs/app.json</file>
<encoder class="net.logstash.logback.encoder.LogstashEncoder">
<includeMdcKeyName>traceId</includeMdcKeyName>
<includeMdcKeyName>spanId</includeMdcKeyName>
<includeMdcKeyName>parentId</includeMdcKeyName>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="CONSOLE" />
<appender-ref ref="JSON" />
</root>
</configuration>
4.3 Spring Boot éæïŒå¯éïŒâ
@Configuration
public class TracingConfig {
@Bean
public Tracer jaegerTracer() {
return new Configuration(
env.getProperty("spring.application.name", "unknown-service")
)
.withSampler(samplerConfig())
.withReporter(reporterConfig())
.getTracerBuilder()
.withScopeManager(new CustomMDCScopeManager())
.build();
}
@Bean
public TracingFilter tracingFilter(Tracer tracer) {
return new TracingFilter(tracer);
}
}
5. 䜿çšåºæ¯äžæäœ³å®è·µâ
5.1 æ¥æ¶äžæžž Trace äžäžæâ
@RestController
public class OrderController {
@GetMapping("/order/{id}")
public Order getOrder(
@PathVariable String id,
@RequestHeader(value = "uber-trace-id", required = false) String uberTraceId
) {
JaegerSpanContext parentContext = parseUberTraceId(uberTraceId);
Span span = tracer.buildSpan("get-order")
.asChildOf(parentContext) // å
³é®ïŒéŸæ¥è¿çšç¶ Span
.withTag(Tags.SPAN_KIND, Tags.SPAN_KIND_SERVER)
.withTag("order.id", id)
.start();
try (Scope scope = tracer.scopeManager().activate(span)) {
log.info("Processing order request"); // èªåšæºåžŠ traceId/spanId
return orderService.getById(id);
} finally {
span.finish();
}
}
}
5.2 åäžæžžäŒ é Trace äžäžæâ
public class PaymentClient {
public void processPayment(String orderId) {
Span span = tracer.buildSpan("call-payment-service")
.withTag(Tags.SPAN_KIND, Tags.SPAN_KIND_CLIENT)
.start();
try (Scope scope = tracer.scopeManager().activate(span)) {
HttpHeaders headers = new HttpHeaders();
// 泚å
¥ trace äžäžæå° HTTP Header
tracer.inject(
span.context(),
Format.Builtin.HTTP_HEADERS,
new HttpHeadersCarrier(headers)
);
restTemplate.exchange(
"http://payment-service/pay",
HttpMethod.POST,
new HttpEntity<>(paymentRequest, headers),
PaymentResponse.class
);
} finally {
span.finish();
}
}
}
5.3 åŒæ¥åºæ¯å€çâ
@Service
public class AsyncOrderService {
@Autowired
private Tracer tracer;
@Autowired
private ExecutorService executorService;
public void processOrderAsync(String orderId) {
Span parentSpan = tracer.activeSpan(); // è·ååœå Span
executorService.submit(() -> {
// åšæ°çº¿çšäžéæ°æ¿æŽ»ç¶ Span
Span asyncSpan = tracer.buildSpan("async-process")
.asChildOf(parentSpan)
.start();
try (Scope scope = tracer.scopeManager().activate(asyncSpan)) {
log.info("Async processing order"); // MDC æ£ç¡®æ³šå
¥
// äžå¡é»èŸ
} finally {
asyncSpan.finish();
}
});
}
}
6. å ³é®è®Ÿè®¡å³çâ
6.1 䞺ä»ä¹äžçŽæ¥åšäžå¡ä»£ç äžæäœ MDCïŒâ
é®é¢ïŒæåšç®¡ç容æéæŒæž çïŒå¯ŒèŽçº¿çšæ± å€çšæ¶ traceId 污æ
è§£å³æ¹æ¡ïŒéè¿ ScopeManager ççåœåšæç®¡çïŒåš activate() æ¶æ³šå
¥ïŒåš close() æ¶èªåšæž
ç
6.2 䞺ä»ä¹éèŠä¿å MDC å¿«ç §ïŒâ
åºæ¯ïŒåµå¥ Span è°çšæ¶ïŒéèŠåšå Span ç»æåæ¢å€ç¶ Span ç MDC
å®ç°ïŒ
// è¿å
¥å Span æ¶
this.previousTraceId = MDC.get("traceId"); // ä¿åå¿«ç
§
MDC.put("traceId", childSpan.context().toTraceId()); // èŠç
// éåºå Span æ¶
restoreMDC("traceId", previousTraceId); // æ¢å€å¿«ç
§
6.3 䞺ä»ä¹äœ¿çš ThreadLocal èäžæ¯ InheritableThreadLocalïŒâ
èéïŒ
ThreadLocalïŒäž¥æ Œçº¿çšé犻ïŒéååæ¥åºæ¯InheritableThreadLocalïŒå线çšç»§æ¿ç¶çº¿çšåŒïŒäœåšçº¿çšæ± å€çšæ¶å®¹æåºç°äžäžææ³æŒ
æšèïŒåŒæ¥åºæ¯æŸåŒäŒ é SpanïŒèéäŸèµèªåšç»§æ¿
7. çæ§äžè°è¯â
7.1 æ¥å¿èŸåºç€ºäŸâ
21:45:32.123 [http-nio-8080-exec-1] INFO c.w.OrderService [traceId=04bf92f3577b34da63ce929d0e0e4736 spanId=36bd32b7a5712a1a] - Processing order ORD-001
21:45:32.234 [http-nio-8080-exec-1] INFO c.w.PaymentClient [traceId=04bf92f3577b34da63ce929d0e0e4736 spanId=7f3a28b9c4d5e6a1] - Calling payment service
21:45:32.456 [http-nio-8080-exec-1] INFO c.w.OrderService [traceId=04bf92f3577b34da63ce929d0e0e4736 spanId=36bd32b7a5712a1a] - Order processed successfully
7.2 Jaeger UI æ¥è¯¢â
åš Jaeger UI äžå¯ä»¥ïŒ
- éè¿ traceId
04bf92f3577b34da63ce929d0e0e4736æ¥ç宿Žè°çšéŸ - æ¥çæ¯äžª Span ç TagsïŒåŠ
db.statement,http.status_codeïŒ - æ¥ç Span ä¹éŽçç¶åå ³ç³»åèæ¶ååž
7.3 åžžè§é®é¢ææ¥â
| é®é¢ç°è±¡ | å¯èœåå | ææ¥æ¹æ³ |
|---|---|---|
| æ¥å¿äž traceId 䞺空 | æªæ¿æŽ» Span æ ScopeManager æªæ£ç¡®é 眮 | æ£æ¥ activate() è°çšå Tracer æå»º |
| traceId åšäžå请æ±éŽäž²å° | MDC æªæ£ç¡®æž çïŒçº¿çšæ± å€çšïŒ | ç¡®ä¿ finally åäžè°çš MDC.remove() |
| å Span æªéŸæ¥å°ç¶ Span | asChildOf() åæ°é误 | éªè¯ parentSpanId æ¯åŠæ£ç¡®è§£æ |
| Jaeger äžçäžå° Span | éæ ·ç讟眮䞺0æ Reporter é 眮é误 | æ£æ¥ withParam(1) åçœç»è¿éæ§ |
8. æ§èœèéâ
8.1 æ§èœåœ±ååæâ
| æäœ | èæ¶ | 圱å |
|---|---|---|
| MDC.put() | < 1ÎŒs | å¯å¿œç¥ |
| ThreadLocal.get() | < 1ÎŒs | å¯å¿œç¥ |
| Span.start() | ~10ÎŒs | äœ |
| Span.finish() + äžæ¥ | ~100ÎŒs | åŒæ¥äžæ¥ïŒå¯¹äž»æµçšåœ±åå° |
8.2 äŒå建议â
- åçæ§å¶éæ ·çïŒç产ç¯å¢å¯è®Ÿçœ®äžº 0.1ïŒ10%ïŒä»¥éäœååšææ¬
- æ¹éäžæ¥ïŒReporter é
眮
withFlushInterval(1000)æ¹éåé Span - é¿å è¿åºŠ SpanïŒäžèŠäžºæ¯äžªæ°æ®åºæ¥è¯¢éœå建 SpanïŒæ§å¶ç²åºŠ
9. æ©å±æ¹åâ
9.1 æ¯æååºåŒçŒçšïŒReactor/WebFluxïŒâ
public class ReactorScopeManager implements ScopeManager {
@Override
public Scope activate(Span span) {
return new ReactorScope(span);
}
static class ReactorScope implements Scope {
ReactorScope(Span span) {
// äœ¿çš Reactor Context èé ThreadLocal
Context.of("span", span);
}
}
}
9.2 éæ Spring Cloud Sleuthâ
Spring Cloud Sleuth æäŸäºåŒç®±å³çšçååžåŒè¿œèžªïŒå¯æ¿ä»£æ¬æ¹æ¡ïŒ
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
9.3 æ¯æ OpenTelemetryâ
OpenTelemetry æ¯æ°äžä»£å¯è§æµæ§æ åïŒå»ºè®®æªæ¥è¿ç§»ïŒ
OpenTelemetry openTelemetry = AutoConfiguredOpenTelemetrySdk
.initialize()
.getOpenTelemetrySdk();
10. æ»ç»â
æ¬æ¹æ¡éè¿èªå®ä¹ ScopeManager å®ç°äº Jaeger Trace äž SLF4J MDC çæ çŒéæïŒå
·å€ä»¥äžç¹ç¹ïŒ
â
èªåšåïŒScope çåœåšæç®¡çïŒæ éæåšæäœ MDC
â
线çšå®å
šïŒThreadLocal é犻 + å¿«ç
§æ¢å€æºå¶
â
åµå¥æ¯æïŒéŸè¡šç»æç»Žæ€å€å± Span å
³ç³»
â
ç产å¯çšïŒå·²èèçº¿çšæ± å€çšãåŒæ¥åºæ¯ç蟹çæ
åµ
è¯¥æ¹æ¡éçšäºéèŠç²Ÿç»æ§å¶ trace äžäžæäŒ æç埮æå¡æ¶æïŒç¹å«æ¯éèŠæ¥æ¶äžæžž trace ä¿¡æ¯çæå¡çœå ³ãBFF å±çåºæ¯ã