Propagasi Konteks dan Instrumentasi Span
Propagasikan konteks pelacakan lintas thread dan layanan menggunakan Micrometer Observation dan OpenTelemetry.
Propagasi Konteks dan Instrumentasi Span adalah pelajaran Spring Boot 4 Complete Guide gratis di CoddyKit. Ini adalah pelajaran 1 dari 4. Kamu bisa membaca pelajaran lengkapnya di bawah secara gratis — lalu praktikkan langsung di browser dengan editor kode bawaan dan tutor AI 24/7. Ini adalah bagian dari jalur belajar Spring Boot 4 Complete Guide, dan progresmu tersinkronisasi di web dan aplikasi CoddyKit. Kursus Spring Boot 4 Complete Guide mencakup 4 pelajaran total.
Bagian dari pelajaran ini belum diterjemahkan dan ditampilkan dalam bahasa Inggris.
Why Context Propagation Matters
In a distributed system, a single user request hops across threads, queues, and services. Distributed tracing stitches these hops together using a shared trace context (a trace ID plus the current span ID).
- If the context is lost, your trace breaks into disconnected fragments.
- Spring Boot 4 uses Micrometer Observation as the unified API and OpenTelemetry (or Brave) as the tracing bridge.
- The challenge: keep the active span attached as work crosses thread and process boundaries.
This lesson shows how to instrument spans and propagate context both in-process (across threads) and across services (over HTTP/messaging headers).
The Observation API
Micrometer's ObservationRegistry is the entry point. An Observation abstracts both metrics and a tracing span, so one instrumentation point feeds both pillars.
Observation.createNotStarted(name, registry)builds it.observe(Runnable)opens a scope, runs the code, and closes it — making the span the active one for the current thread.
While the scope is open, any nested instrumentation (HTTP client, JDBC, logging MDC) automatically becomes a child span.
import io.micrometer.observation.Observation;
import io.micrometer.observation.ObservationRegistry;
@Service
public class OrderService {
private final ObservationRegistry registry;
public OrderService(ObservationRegistry registry) {
this.registry = registry;
}
public Order place(Long id) {
return Observation.createNotStarted("order.place", registry)
.lowCardinalityKeyValue("order.type", "standard")
.observe(() -> doPlace(id));
}
private Order doPlace(Long id) {
// child spans created here join the same trace
return new Order(id);
}
}High vs Low Cardinality Tags
Observation key-values become both metric tags and span attributes. Choosing the right cardinality is a key decision:
- lowCardinalityKeyValue: bounded values (HTTP method, status, region). Safe for metric dimensions.
- highCardinalityKeyValue: unbounded values (user ID, order ID). Attached only to the span, never to metrics, to avoid a tag explosion.
Putting a user ID into a low-cardinality tag would create one time series per user and overwhelm your metrics backend.
Observation.createNotStarted("order.place", registry)
.lowCardinalityKeyValue("order.type", order.getType()) // bounded -> metric + span
.highCardinalityKeyValue("order.id", order.getId().toString()) // span only
.observe(() -> process(order));The Active Span and Thread-Locals
OpenTelemetry stores the current Context in a thread-local. Micrometer mirrors this with an Observation scope that is also thread-bound.
- Inside
observe(...),Span.current()returns the live span. - The moment you hand work to another thread (executor,
CompletableFuture, reactive scheduler), the thread-local is empty there — the span does not follow automatically.
This is the root cause of broken traces in async code. The next scenes fix it.
import io.opentelemetry.api.trace.Span;
public void log() {
Span span = Span.current();
System.out.println("traceId=" + span.getSpanContext().getTraceId());
}Propagating Across Threads with ContextSnapshot
Micrometer's context-propagation library captures all registered thread-local values into a ContextSnapshot and restores them in another thread.
- Capture on the producing thread with
ContextSnapshotFactory.captureAll(). - Restore inside the worker by wrapping the task with
snapshot.wrap(runnable).
This carries the OpenTelemetry context and Observation scope so the worker's spans join the original trace.
import io.micrometer.context.ContextSnapshot;
import io.micrometer.context.ContextSnapshotFactory;
import java.util.concurrent.ExecutorService;
public void runAsync(ExecutorService pool) {
ContextSnapshot snapshot = ContextSnapshotFactory.builder().build().captureAll();
pool.submit(snapshot.wrap(() -> {
// active span here is the same as on the calling thread
doWork();
}));
}Auto-Propagating Executors
Wrapping every task by hand is error-prone. Instead, decorate the executor once so every submitted task captures and restores context automatically.
ContextExecutorService.wrap(delegate, () -> ContextSnapshot.captureAll())from context-propagation, or- Spring's
ContextPropagatingTaskDecoratoron aThreadPoolTaskExecutor.
With Spring Boot 4, registering the task decorator means @Async methods keep the trace context with no per-call code.
import org.springframework.core.task.support.ContextPropagatingTaskDecorator;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
@Bean
public ThreadPoolTaskExecutor applicationTaskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(8);
executor.setTaskDecorator(new ContextPropagatingTaskDecorator());
executor.initialize();
return executor;
}Creating Custom Spans Manually
Sometimes you need a span around a specific block without a full Observation. Inject the Micrometer Tracer and manage the span explicitly.
tracer.nextSpan().name("...").start()creates a child of the current span.- Open a
SpanInScopewithtracer.withSpan(span)so nested code sees it as current. - Always
end()in a finally block, and record errors withspan.error(ex).
import io.micrometer.tracing.Span;
import io.micrometer.tracing.Tracer;
public void doImport(Tracer tracer) {
Span span = tracer.nextSpan().name("file.import").start();
try (Tracer.SpanInScope ws = tracer.withSpan(span)) {
parseAndStore();
} catch (RuntimeException ex) {
span.error(ex);
throw ex;
} finally {
span.end();
}
}Cross-Service Propagation over HTTP
Across processes, context travels in HTTP headers. The default format is W3C Trace Context: the traceparent header carries trace ID, parent span ID, and flags.
- Auto-instrumented
RestClient/WebClientbeans inject the header outbound. - The receiving Spring Boot app extracts it and continues the same trace.
Use the framework-provided builders (not new RestClient.Builder()) so the tracing interceptor is attached.
@Service
public class InventoryClient {
private final RestClient restClient;
// inject the auto-configured, instrumented builder
public InventoryClient(RestClient.Builder builder) {
this.restClient = builder.baseUrl("http://inventory").build();
}
public Stock check(String sku) {
// traceparent header is injected automatically
return restClient.get().uri("/stock/{sku}", sku)
.retrieve().body(Stock.class);
}
}The W3C traceparent Header
Understanding the wire format helps when debugging broken traces. A traceparent looks like:
00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01- 00 = version, then 16-byte trace-id, 8-byte parent-id, and 01 = sampled flag.
If a downstream span shows a new trace ID, the header was dropped — often because a non-instrumented client or a manual header copy stripped it. A companion tracestate header carries vendor-specific data.
Propagating Through Messaging
For Kafka or RabbitMQ, context rides in message headers. With instrumented Spring Messaging, the producer injects traceparent into the record headers and the consumer extracts it, creating a span linked to the producer.
- Producer span kind =
PRODUCER; consumer span kind =CONSUMER. - Across a queue the relationship is often modeled as a span link rather than a strict parent/child, since many consumers may process batches.
Keep using the auto-configured KafkaTemplate/listeners so propagation works without manual header handling.
@Component
public class OrderEvents {
private final KafkaTemplate<String, OrderEvent> template;
public OrderEvents(KafkaTemplate<String, OrderEvent> template) {
this.template = template;
}
public void publish(OrderEvent event) {
// traceparent header added to the Kafka record automatically
template.send("orders", event.id(), event);
}
@KafkaListener(topics = "orders")
public void consume(OrderEvent event) {
// this span links back to the producing trace
process(event);
}
}Correlating Logs and Baggage
Two finishing touches make traces usable:
- Log correlation: Micrometer pushes
traceIdandspanIdinto the SLF4J MDC, so each log line carries the IDs. Spring Boot's default log pattern prints them. - Baggage: arbitrary key-values that propagate across services alongside trace context. Configure
management.tracing.baggage.correlation.fieldsandremote-fieldsto forward and expose them in MDC.
Use baggage sparingly (e.g. a userId or tenantId) — every value is copied onto every downstream hop.
management:
tracing:
sampling:
probability: 1.0
baggage:
remote-fields: tenantId
correlation:
fields: tenantIdQuick Check
Test your understanding of in-process context propagation.
Recap
You learned how trace context flows through a Spring Boot 4 system:
- Micrometer Observation unifies metrics and spans;
observe(...)opens a thread-bound scope. - Choose low-cardinality tags for metrics and high-cardinality attributes for spans only.
- Context lives in a thread-local, so async handoffs need ContextSnapshot or a ContextPropagatingTaskDecorator to keep the trace intact.
- Create explicit spans with the
Tracerwhen an Observation is overkill, always ending them in a finally block. - Across services, the W3C traceparent header (HTTP) or message headers (Kafka/Rabbit) carry the trace; use auto-instrumented clients.
- MDC correlation ties logs to traces, and baggage forwards small key-values downstream.
Pertanyaan yang Sering Diajukan
Apakah pelajaran “Propagasi Konteks dan Instrumentasi Span” gratis?
Ya — teks lengkap “Propagasi Konteks dan Instrumentasi Span” gratis dibaca di sini di web. Untuk praktiknya secara interaktif (editor kode bawaan dan tutor AI 24/7) dan buka sisa kursus Spring Boot 4 Complete Guide, upgrade ke CoddyKit PRO. Kursus Spring Boot 4 Complete Guide mencakup 4 pelajaran total.
Apa yang akan aku pelajari di “Propagasi Konteks dan Instrumentasi Span”?
Propagasikan konteks pelacakan lintas thread dan layanan menggunakan Micrometer Observation dan OpenTelemetry. Kamu berlatih Spring Boot 4 Complete Guide dengan kode praktik yang langsung kamu jalankan di browser, dan tutor AI 24/7 menjawab pertanyaanmu saat kamu mengerjakan pelajaran ini.
Apakah aku perlu pengalaman untuk memulai Spring Boot 4 Complete Guide?
Tidak diperlukan pengalaman sebelumnya. Spring Boot 4 Complete Guide di CoddyKit dirancang untuk pemula hingga pelajar tingkat lanjut, jadi kamu bisa memulai di sini atau dari awal dan belajar sesuai kecepatan kamu sendiri. Ini adalah pelajaran 1 dari 4.
Berapa lama pelajaran “Propagasi Konteks dan Instrumentasi Span” memakan waktu?
Sebagian besar pelajaran CoddyKit memakan waktu sekitar 5–10 menit. Setiap pelajaran ringkas dan interaktif, jadi kamu membuat kemajuan stabil dan melanjutkan dari tempat kamu tinggalkan di web dan aplikasi.
Bisakah aku menulis dan menjalankan kode dalam pelajaran Spring Boot 4 Complete Guide ini?
Ya. Setiap pelajaran Spring Boot 4 Complete Guide menyertakan editor kode bawaan, jadi kamu menulis dan menjalankan kode nyata langsung di browser dan mendapatkan umpan balik AI instan — tidak diperlukan penyiapan lokal.
Semua pelajaran dalam kursus ini
- Propagasi Konteks dan Instrumentasi Span
- Circuit Breaker dan Isolasi Bulkhead
- Pembatas Laju, Percobaan Ulang, dan Pembatas Waktu
- Menghubungkan Log, Metrik, dan Pelacakan