Contents

7 Days of OpenTelemetry: Day 2 - Understanding Distributed Tracing

   May 27, 2025     6 min read

Day 2: Understanding Distributed Tracing

Welcome to Day 2 of our “7 Days of OpenTelemetry” challenge! Yesterday, we introduced the concept of observability and provided an overview of OpenTelemetry. Today, we’ll dive deeper into distributed tracing, which forms the foundation of OpenTelemetry’s approach to observability.

What is Distributed Tracing?

Distributed tracing is a method for tracking and visualizing requests as they flow through distributed systems. Unlike traditional logging or metrics, distributed tracing provides end-to-end visibility into the entire request lifecycle, spanning multiple services, databases, and external dependencies.

Think of distributed tracing as a “breadcrumb trail” that follows a request from its entry point through all the services it touches until the final response is returned. This trail helps you understand:

  • The path a request takes through your system
  • How long each component takes to process the request
  • Where bottlenecks or failures occur
  • Dependencies between services

Why is Distributed Tracing Important?

In modern microservices architectures, a single user request might interact with dozens of services. When something goes wrong or performs poorly, traditional debugging approaches fall short:

  • Logs are scattered across different services and lack context about the entire request flow
  • Metrics show symptoms but not causes of problems
  • Service-level monitoring doesn’t reveal cross-service issues

Distributed tracing solves these problems by connecting the dots between services, providing a holistic view of the entire request journey.

Core Concepts in Distributed Tracing

To understand distributed tracing, you need to be familiar with several key concepts:

1. Traces

A trace represents the complete journey of a request through your system. It’s composed of one or more spans that represent operations within that request. Each trace has a unique trace ID that’s propagated across service boundaries.

2. Spans

A span represents a single operation within a trace. It could be an HTTP request, a database query, a function call, or any other unit of work. Each span contains:

  • A name describing the operation
  • A start and end time
  • A set of attributes (key-value pairs) providing additional context
  • Links to related spans
  • Events that occurred during the span’s lifetime
  • Status information (success, error, etc.)

Spans are the building blocks of traces and provide the detailed information needed to understand system behavior.

3. Parent-Child Relationships

Spans in a trace have parent-child relationships that represent the causal relationships between operations. For example:

  • A span for an HTTP handler might be the parent of spans for database queries
  • A span for a service call might be the parent of spans in the downstream service

These relationships create a hierarchical structure that can be visualized as a trace timeline or tree.

4. Context Propagation

For distributed tracing to work across service boundaries, trace context (trace ID, span ID, etc.) must be propagated from one service to another. This is typically done by passing context information in HTTP headers, message queues, or other inter-service communication mechanisms.

Context propagation is what connects the dots between spans in different services, allowing them to be assembled into a complete trace.

Distributed Tracing in Action

Let’s look at a simplified example of how distributed tracing works in a microservices architecture:

  1. A user makes a request to the API Gateway
  2. The API Gateway creates a root span and forwards the request to the Order Service
  3. The Order Service creates a child span and makes a call to the Inventory Service
  4. The Inventory Service creates another child span and queries a database
  5. Each service propagates the trace context to the next service
  6. All spans are collected and assembled into a complete trace

This process creates a comprehensive view of the request’s journey through the system, including timing information for each component.

OpenTelemetry’s Tracing Data Model

OpenTelemetry builds on these core concepts with a standardized tracing data model that includes:

Span Context

The span context contains the information that identifies a span in a trace:

  • Trace ID: A globally unique identifier for the trace
  • Span ID: A unique identifier for the span within the trace
  • Trace Flags: Bit flags that control tracing behavior (e.g., sampling)
  • Trace State: Additional vendor-specific trace information

Span Data

Beyond the span context, OpenTelemetry spans include:

  • Span Name: A descriptive name for the operation
  • Span Kind: The role of the span (client, server, producer, consumer, internal)
  • Start and End Timestamp: When the operation started and completed
  • Attributes: Key-value pairs providing additional context
  • Events: Time-stamped logs within the span
  • Links: References to related spans
  • Status: The outcome of the operation (OK, ERROR, UNSET)

Trace State

OpenTelemetry includes a mechanism for vendors to add custom information to traces without conflicting with the core specification. This allows for extensibility while maintaining interoperability.

A Simple Trace Example in Go

Let’s look at a conceptual example of what trace data might look like in a Go application. This isn’t actual instrumentation code (we’ll get to that on Day 4), but it illustrates the structure of a trace:

// Root span in API Gateway
rootSpan := {
    TraceID:    "abcdef0123456789",
    SpanID:     "0123456789abcdef",
    Name:       "GET /orders/123",
    StartTime:  "2023-05-20T10:00:00Z",
    EndTime:    "2023-05-20T10:00:01Z",
    Attributes: {
        "http.method": "GET",
        "http.url": "/orders/123",
        "service.name": "api-gateway"
    }
}

// Child span in Order Service
orderSpan := {
    TraceID:    "abcdef0123456789",  // Same trace ID
    SpanID:     "9876543210fedcba",
    ParentSpanID: "0123456789abcdef", // References the root span
    Name:       "GetOrder",
    StartTime:  "2023-05-20T10:00:00.1Z",
    EndTime:    "2023-05-20T10:00:00.8Z",
    Attributes: {
        "order.id": "123",
        "service.name": "order-service"
    }
}

// Child span in Inventory Service
inventorySpan := {
    TraceID:    "abcdef0123456789",  // Same trace ID
    SpanID:     "fedcba9876543210",
    ParentSpanID: "9876543210fedcba", // References the order span
    Name:       "CheckInventory",
    StartTime:  "2023-05-20T10:00:00.3Z",
    EndTime:    "2023-05-20T10:00:00.7Z",
    Attributes: {
        "product.id": "456",
        "service.name": "inventory-service"
    }
}

When visualized, these spans would show the flow of the request through the system, with timing information that helps identify where time is spent.

Comparison with Traditional Monitoring

To understand the value of distributed tracing, let’s compare it with traditional monitoring approaches:

AspectTraditional MonitoringDistributed Tracing
ScopeService-levelEnd-to-end request flow
GranularityAggregate metricsIndividual requests
ContextLimitedRich contextual information
CausalityInferredExplicitly captured
DebuggingRequires correlation across systemsBuilt-in correlation
ComplexitySimpler to implementMore complex but more powerful

While traditional monitoring still has its place, distributed tracing provides the detailed, contextual information needed to understand and troubleshoot complex distributed systems.

Preparing for OpenTelemetry Instrumentation

Now that we understand the concepts behind distributed tracing, we’re ready to start implementing it with OpenTelemetry. In tomorrow’s installment, we’ll set up the OpenTelemetry Collector, which will receive, process, and export our telemetry data.

The Collector will serve as the foundation for our observability pipeline, allowing us to see the results of our instrumentation immediately as we implement it in the following days.

Conclusion

Distributed tracing is a powerful technique for understanding the behavior of complex, distributed systems. By tracking requests as they flow through your services, you gain insights that would be impossible with traditional monitoring approaches.

OpenTelemetry builds on these concepts with a standardized, vendor-neutral approach to distributed tracing that works across languages, frameworks, and backends.

In Day 3, we’ll take our first practical step by setting up the OpenTelemetry Collector, which will form the foundation of our observability pipeline.

Stay tuned, and happy tracing!