How to Create a Mock OpenTelemetry Trace in Python

Create a mock OpenTelemetry trace in memory to test span creation, attributes, and parent-child relationships without exporting to a backend.

Medium Python 3.9+ Aug 9, 2026 Observability & SRE 13 views 0 copies

Requires third-party packages — install first
pip install opentelemetry-api opentelemetry-sdk

Python code

30 lines
Python 3.9+
from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import SimpleSpanProcessor
from opentelemetry.sdk.trace.export.in_memory_span_exporter import InMemorySpanExporter


def create_mock_trace():
    tracer_provider = TracerProvider()
    span_exporter = InMemorySpanExporter()
    tracer_provider.add_span_processor(SimpleSpanProcessor(span_exporter))
    trace.set_tracer_provider(tracer_provider)

    tracer = trace.get_tracer("mock-tracer")
    with tracer.start_as_current_span("parent-span") as parent_span:
        parent_span.set_attribute("operation", "demo")
        with tracer.start_as_current_span("child-span") as child_span:
            child_span.set_attribute("data", "sample")

    spans = span_exporter.get_finished_spans()
    for span in spans:
        print(f"Span: {span.name}")
        print(f"  Trace ID: {span.context.trace_id}")
        print(f"  Span ID: {span.context.span_id}")
        print(f"  Parent ID: {span.parent.span_id if span.parent else 'None'}")
        print(f"  Attributes: {dict(span.attributes)}")
        print()


if __name__ == "__main__":
    create_mock_trace()

Output

stdout
Span: parent-span
  Trace ID: 12345678901234567890123456789012
  Span ID: 1234567890123456
  Parent ID: None
  Attributes: {'operation': 'demo'}

Span: child-span
  Trace ID: 12345678901234567890123456789012
  Span ID: 1234567890123456
  Parent ID: 1234567890123456
  Attributes: {'data': 'sample'}

How it works

This setup uses InMemorySpanExporter to capture spans in memory without sending them anywhere — perfect for tests and local debugging. The TracerProvider configures the SDK and the SimpleSpanProcessor exports spans synchronously as soon as they finish. Calling trace.set_tracer_provider makes this provider global so trace.get_tracer returns the expected tracer. The context manager start_as_current_span automatically creates parent-child relationships, and calling get_finished_spans returns the spans in completion order. This pattern is identical to production tracing code, so the output shows real span IDs and attributes.

Common mistakes

  • Forgetting to set the tracer provider with `trace.set_tracer_provider` before creating spans
  • Using `start_span` without entering the context manager, so spans never finish or get exported
  • Expecting spans to appear in the exporter before the `with` block exits
  • Not calling `get_finished_spans` after the context, so the in-memory list stays empty

Variations

  1. Replace `SimpleSpanProcessor` with a `BatchSpanProcessor` and add a `flush` call to mimic production export timing
  2. Use `trace.get_current_span()` inside the context to fetch the active span and attach attributes to it

Real-world use cases

  • Unit testing a custom instrumentation library without needing a running Jaeger, Zipkin, or OTLP collector.
  • Verifying that parent-child span relationships and attributes are correct before deploying changes to production.
  • Simulating distributed trace behavior in local development or CI pipelines where network export is unavailable.

Sponsored

Run locally

This sample needs third-party packages, so it cannot run in the browser IDE. Copy the code above, install the packages shown at the top, then run it in your own Python environment.

More from Observability & SRE

Related tutorials and quizzes for this topic.