Adetayo Akinsanya unkletayo.dev

Distributed Tracing & Observability: Trace Context Propagation, OpenTelemetry, and W3C Headers

Understanding Trace IDs, Span IDs, baggage propagation, OpenTelemetry collectors, and W3C traceparent headers

Part 18 in Series — Catch up on the previous article: Multi-Region Distributed Databases: Active-Active vs Active-Passive Cross-Data-Center Replication (Part 17) before diving into this post.

Why You Need This in Real Life

A user submits an order on an e-commerce platform. The HTTP request travels through an API Gateway, an Auth Service, an Order Service, a Payment Gateway, an Inventory Service, and a Database.

The request takes 8,400 milliseconds to complete.

When the engineering team inspects application logs, each microservice has isolated log files. Searching for the user’s ID across 50 log files produces 10,000 unorganized log lines without timing context. It takes three hours to figure out which microservice introduced the latency bottleneck.

Distributed Tracing solves this diagnostic nightmare by assigning a globally unique Trace ID to the initial user request and propagating that ID across every downstream HTTP, gRPC, and message queue call.

To debug microservice performance in production, you must master Span Trees, W3C Trace Context Headers, Baggage Propagation, and OpenTelemetry.


Part 1: Anatomic Breakdown of Distributed Tracing

Distributed tracing models an execution flow as a directed acyclic graph (DAG) of Spans grouped under a single Trace ID:

[ Root Span: API Gateway ] (Duration: 8400ms, Trace ID: 4bf92f3577b34da6a3ce929d0e0e4736)
  |-- [ Span 1: Auth Service ] (Duration: 50ms, Span ID: 00f067aa0ba902b7)
  |-- [ Span 2: Order Service ] (Duration: 8300ms, Span ID: 5a15634d35343d80)
        |-- [ Span 3: Payment Service ] (Duration: 8200ms, Span ID: b725b838a11955b9) <-- BOTTLENECK!

Core Data Structures

  • Trace ID: A 128-bit globally unique identifier representing the entire request journey across microservices.
  • Span ID: A 64-bit unique identifier representing a single unit of work within a specific microservice (e.g., executing a SQL query or calling a downstream REST endpoint).
  • Parent Span ID: Links child spans back to their caller in the DAG hierarchy.
  • Baggage: Key-value pairs (e.g., tenant_id=corp_42) propagated alongside the trace context across all downstream services.

Part 2: Context Propagation & W3C traceparent Header

To track requests across network boundaries, microservices propagate trace context inside HTTP headers using the W3C Trace Context Specification:

traceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01

Deconstructing the traceparent Header Format

00 - 4bf92f3577b34da6a3ce929d0e0e4736 - 00f067aa0ba902b7 - 01
|    |                                  |                |
|    |-- 128-bit Trace ID (Hex)         |-- 64-bit Span  |-- Trace Flags
|                                           ID (Hex)        (01 = Sampled)
v
Version (00)
  1. Version (00): Current W3C specification version.
  2. Trace ID (4bf92f...): Shared across all microservices processing this request.
  3. Parent Span ID (00f067...): The ID of the caller’s span.
  4. Trace Flags (01): Indicates whether the trace is sampled (01 = recorded; 00 = ignored to save storage).

Part 3: OpenTelemetry Architecture

OpenTelemetry (OTel) is the CNCF industry standard framework for collecting metrics, logs, and traces without vendor lock-in.

Microservice A (OTel SDK) --\
Microservice B (OTel SDK) ----> [ OTel Collector ] ---> Export to Jaeger / Zipkin / Datadog
Microservice C (OTel SDK) --/   (Batch, Filter, Tail Sample)

The OpenTelemetry Collector

The OTel Collector sits between application SDKs and backend tracing engines:

  • Receivers: Accept traces in OpenTelemetry Protocol (OTLP), Jaeger, or Zipkin formats.
  • Processors: Batch spans, filter sensitive fields, and perform Tail Sampling (e.g., sample 100% of failed or slow requests while dropping 99% of fast successful requests).
  • Exporters: Send telemetry data to storage backends (Jaeger, Prometheus, Datadog, Grafana Tempo).

Part 4: Runnable Java Trace Context Propagation Interceptor

package com.example.tracing;

import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.UUID;

public class TracingHttpClient {

    private final HttpClient httpClient = HttpClient.newHttpClient();

    public void callDownstreamService(String targetUrl, String currentTraceId, String parentSpanId) throws Exception {
        // Generate new Span ID for this outgoing HTTP request
        String newSpanId = generate64BitHexId();

        // Construct W3C traceparent header: 00-{traceId}-{spanId}-01
        String traceparentHeader = String.format("00-%s-%s-01", currentTraceId, newSpanId);

        System.out.println("[TRACING INTERCEPTOR] Injecting W3C Header: " + traceparentHeader);

        HttpRequest request = HttpRequest.newBuilder()
                .uri(URI.create(targetUrl))
                .header("traceparent", traceparentHeader) // Propagate Trace Context!
                .GET()
                .build();

        HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
        System.out.println("[TRACING INTERCEPTOR] Response Status: " + response.statusCode());
    }

    private String generate64BitHexId() {
        return UUID.randomUUID().toString().replace("-", "").substring(0, 16);
    }
}

Next Steps

Now that we understand distributed tracing and observability, we will explore the Master System Design Framework in Part 19: dissecting a 4-step methodology for Senior and Staff System Design interviews.

References & Further Reading

  1. Apple Developer Documentation. Apple Push Notification service (APNs) Provider API Specification. Apple Docs.
  2. Google Developers. Firebase Cloud Messaging (FCM) HTTP v1 API Specification. Google Docs.
  3. Hohpe, G., & Woolf, B. (2003). Enterprise Integration Patterns. Addison-Wesley.

Up Next in Series →

Part 19: The Master System Design Framework: 4-Step Methodology for Senior & Staff Architect Interviews

Continue to Part 19 →