Mastering Distributed Tracing with Spring Cloud Sleuth

Spring Cloud Sleuth serves as a distributed tracing mechanism integrated within the Spring Cloud ecosystem. Its primary objective is to simplify the process of debugging and monitoring requests across microservices by injecting unique identifiers into service communications.

In complex microservice architectures, an inbound request often traverses multiple application instances. Sleuth manages this flow by assigning a distinct Trace ID to every incoming external request. This identifier persists throughout the entire transaction lifecycle. Within individual services, specific segments of processing are recorded as Spans, linked via these IDs. This structure allows developers to reconstruct the full call graph for any given user request, regardless of how many hops it takes through the infrastructure.

Core tracking elements include:

  • Trace ID: A globally unique identifier marking the root request and all subsequent operations under it.
  • Span ID: Represents a specific segment of work performed within a single service.
  • Parent Span ID: Links a current span to its preceding operation in the hierarchy.
  • Baggage/Tags: Key-value pairs carrying custom context like User IDs or Transaction Types.
  • Events: Time-stamped markers for significant occurrences during execution.

Core Architectural Roles

Sleuth functions by embedding trace context into various transport layers (HTTP headers, RPC metadata). Its responsibilities extend beyond simple logging:

  1. Context Propagation: Automatically injects Trace ID and Span ID into HTTP request headers and propagates them to downstream services.
  2. Log Correlation: Integrates with SLF4J/Logback to append trace identifiers to log lines, making log aggregation trivial.
  3. Compatibility: Works seamlessly with Zipkin for visualization and can export metrics to ELK stacks.
  4. Non-Invasive Setup: Requires minimal configuration changes, typically just adding dependencies.
  5. Async Support: Handles context propagation in asynchronous environments such as Message Queues (RabbitMQ/Kafka) or @Async methods.

Integration Guide

To enable tracing in a Spring Boot application, include the starter dependency:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>

For Gradle users:

implementation 'org.springframework.cloud:spring-cloud-starter-sleuth'

The framework automatically configures itself when the main application class includes @SpringBootApplication.

Configuration Examples

You can customize sampling rates and baggage keys via configuration files.

application.yml:

spring:
  sleuth:
    sampler:
      probability: 0.1  # Sample only 10% of traffic
    baggage-keys: userId, transactionType

application.properties:

spring.sleuth.sampler.probability=0.5
spring.sleuth.baggage-keys=clientId

When ready to visualize data, add the Zipkin integration:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-sleuth-zipkin</artifactId>
</dependency>

Understanding Trace and Span Objects

A Trace represents the end-to-end logical unit of work, bounded by the entry point and the final response. It remains constant across all participating services using one shared Trace ID.

A Span defines the granularity within that trace. Every service interaction generates a new span. Spans contain metadata about duration, status codes, and operational names.

Example Log Output:

2023-10-27 09:30:00 INFO [user-service, 8a7f9b2c3d4e, 8a7f9b2c3d4e] Processing order...

Here, 8a7f9b2c3d4e acts as both Trace ID and Span ID for the root operation. Subsequent service calls will share the Trace ID but generate unique Span IDs.

Advanced Customization

Injecting Custom Tags

Developers can enrich spans with business-specific data using SpanCustomizer:

@Service
public class OrderService {
    
    private final SpanCustomizer customizer;

    public OrderService(SpanCustomizer customizer) {
        this.customizer = customizer;
    }

    public void processOrder(OrderRequest req) {
        // Add specific tag
        customizer.tag("order.id", req.getId());
        // Execute logic
    }
}

Recording Manual Events

Use the Tracer interface to annotate specific steps:

@Autowired
private Tracer tracer;

public void executeWorkflow() {
    Span span = tracer.nextSpan().name("complex-workflow").start();
    try (Tracer.SpanInScope scope = tracer.withSpan(span)) {
        tracer.annotate("validation-started");
        validateData();
        tracer.annotate("processing-complete");
    } finally {
        span.finish();
    }
}

Managing Sampling Strategies

High-volume systems require probabilistic sampling to prevent storage bloat. Configure the probability value between 0.0 and 1.0.

spring:
  sleuth:
    sampler:
      probability: 0.05 # Keep 5% of requests

Custom Samplers can also be implemented via the Sampler interface for logic based on request paths or headers.

Logging Framework Compatibility

Sleuth supports standard Java logging frameworks. To insure Trace IDs appear correctly in logs, define patterns:

Logback (logback-spring.xml):

<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %X{trace-id} %X{span-id} : %msg%n</pattern>

Log4j2 (log4j2.xml):

<patternLayout pattern="%d{yyyy-MM-dd HH:mm:ss} %t %-5p %logger{36} - %X{B3-TraceId} : %m%n"/>

Using placeholders like %X{trace-id} or %X{B3-SpanId} ensures Sleuth injects the necessary values at runtime.

Distributed Asynchronous Handling

Multi-threaded Context Propagation

Standard Java threads do not inherit ThreadLocal variables automatically. Sleuth addresses this using TraceableExecutorService:

@Bean
public ExecutorService taskExecutor() {
    return Executors.newFixedThreadPool(4);
}

@Bean
public AsyncConfig asyncConfig() {
    return new AsyncConfig(new ThreadPoolTaskExecutor(), taskExecutor());
}

Alternatively, wrap executors manually:

ExecutorService executorService = Executors.newCachedThreadPool();
ExecutorService wrappedExecutor = TraceAwareThreadPoolExecutor.wrap(executorService);

Messaging Systems

For RabbitMQ and Kafka, Sleuth intercepts message headers to carry trace context.

Configuration:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-stream-binder-rabbit</artifactId>
</dependency>

This ensures that message producers attach headers (like B3-*) and consumers extract them to link back to the originating trace.

Operational Considerations

Handling Server Connectivity Loss

If the backend tracking system (e.g., Zipkin) becomes unreachable:

  1. Logging Fallback: Local logs remain unaffected.
  2. Buffer Strategy: Clients may buffer spans temporarily before retrying.
  3. Timeout Config: Prevent infinite retries from impacting application performance.

Ignoring Health Checks

To reduce noise, exclude health check endpoints from tracing:

spring:
  sleuth:
    web:
      ignored-paths: /actuator/health,/actuator/info

This prevents unnecessary overhead on internal stability probes.

Performance Impact Mitigation

Tracking adds latency due to header manipulation and potential network I/O. To minimize impact:

  • Use asynchronous reporting to the collector.
  • Enable deterministic sampling for production environments.
  • Optimize downstream store configurations.

Span Lifecycle Management

Proper closure of spans is critical to avoid memory leaks.

  • Automatic Management: Web Controllers usually handle span creation/deletion transparently.
  • Manual Control: For background jobs, explicitly call span.finish() in a finally block.
try (Tracer.SpanInScope scope = tracer.withSpan(span)) {
    doWork();
} catch (Exception ex) {
    span.error(ex); // Mark span as failed
    throw ex;
}

Conclusion on Observability

Integrating Sleuth significantly enhances system observability. It provides visibility in to dependencies, isolates failure points, and aids in capacity planning. By adhering to consistent naming conventions and proper error handling within traces, teams can maintain high availability even in fragmented cloud-native architectures.

Tags: Spring Cloud Sleuth Distributed Tracing microservices java

Posted on Wed, 22 Jul 2026 17:05:47 +0000 by llangres