Back to Microservice Course

Microservice Course

Distributed Tracing — Spring Cloud Sleuth / Micrometer Tracing

Important: Spring Cloud Sleuth has been discontinued as a standalone project. As of Spring Boot 3.0 / Spring Cloud 2022.0 (Kilburn), distributed tracing is now handled by Micrometer Tracing (the Micrometer observation API with tracing bridges). If you are on Spring Boot 3.x, use Micrometer Tracing with Zipkin or Jaeger. The concepts below — trace ID, span ID, context propagation — are identical; only the dependency names and auto-configuration differ.


Why Distributed Tracing?

In a microservices architecture, a single user-facing request often fans out across many services:

A → B → C
A → B
B → C

When something goes wrong (slow response, error, timeout), you need to be able to answer: which service was slow? Where did the error originate? How long did each hop take? Without distributed tracing, you'd have to correlate logs from each service manually by timestamp — nearly impossible at scale.

Distributed tracing solves this by assigning a single trace ID to the entire end-to-end request as it enters the system, and a span ID to each individual service hop within that request. These IDs are injected into log output automatically and propagated across service boundaries via HTTP headers (W3C traceparent, or B3 headers used by Zipkin).

Concepts

  • Trace ID: A globally unique ID assigned to the entire end-to-end request flow across all services. All log lines from all services handling the same incoming request share the same trace ID — this is what lets you pull the full picture from a log aggregator.
  • Span ID: A unique ID for a single unit of work within one service (or one hop). A trace is a tree of spans — the root span covers the whole request entry point, and each downstream call creates a child span.
  • Export: Whether the trace data is exported to a trace backend (Zipkin, Jaeger). Shown as true/false in Sleuth log output — false means the span was sampled locally but not exported.

A Sleuth-instrumented log line looks like:

INFO [service-name,traceId,spanId,exported] - your log message here

Example:

{ "service": "billing-service", "traceId": "95d9962199891f52", "spanId": "95d9962199891f52", "message": "Processing order 1232" }

When traceId and spanId are the same, it means this is the root span (the request originated here, not forwarded from another service).


Spring Boot 2.x — Spring Cloud Sleuth (Legacy)

<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-sleuth</artifactId> </dependency> <!-- Optional: export traces to Zipkin --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-sleuth-zipkin</artifactId> </dependency>

Sleuth auto-instruments RestTemplate, WebClient, @FeignClient, Kafka consumers/producers, RabbitMQ channels, and @Async / ExecutorService — trace context is propagated automatically with no changes to business code.

@Service @Slf4j public class BillingService { public String billingOrder(String orderId) throws Exception { int orderIdNum = Integer.parseInt(orderId); if (orderIdNum <= 0) { log.info("Invalid order id: {}", orderId); // trace/span IDs auto-added throw new IllegalArgumentException("Invalid Order ID: " + orderId); } else if (orderIdNum < 500) { log.info("Payment failed for order: {}", orderId); throw new Exception("Payment failed for Order ID " + orderId); } else { log.info("Order processed successfully: {}", orderId); return "Successfully placed the order " + orderId; } } }

Spring Boot 3.x — Micrometer Tracing (Current)

<dependency> <groupId>io.micrometer</groupId> <artifactId>micrometer-tracing-bridge-brave</artifactId> </dependency> <dependency> <groupId>io.zipkin.reporter2</groupId> <artifactId>zipkin-reporter-brave</artifactId> </dependency>
management: tracing: sampling: probability: 1.0 # 1.0 = 100% sampling (use lower values in production) zipkin: tracing: endpoint: http://localhost:9411/api/v2/spans

The programming model is identical — log statements automatically include trace and span IDs. The difference is under the hood: Micrometer Tracing is the unified observation API that also handles metrics, and the Brave bridge is what connects it to Zipkin's wire format.


Viewing Traces — Zipkin

Zipkin (or Jaeger) provides a UI to visualize the full trace: which services were involved, how long each span took, where errors occurred, and the dependency graph between services. Run Zipkin locally with Docker:

docker run -d -p 9411:9411 openzipkin/zipkin

Open http://localhost:9411 to search traces by trace ID, service name, operation name, or time range, and to view the waterfall timeline of spans within a trace.