charge_bank step, it should call the charge service (/charge) and produce spans that are correlated across both services so a tracing backend like Jaeger can display one end-to-end trace.
What you’ll learn
- How to configure a tracer provider for each service.
- How to instrument a Flask-based charge service (server).
- How to instrument the payment service (client) and inject trace context into HTTP headers.
- How to create client-side spans for outgoing requests.
- How to debug cases where two separate traces appear instead of a single correlated trace.
- Both services must establish a tracer provider (resource attributes:
service.name,service.version) and a span processor + exporter. - For local debugging use
ConsoleSpanExporter; for production collection use the OTLP exporter to a collector or backend. - Propagate context with
opentelemetry.propagate.injecton the client and extract it on the server (or rely on automatic instrumentation).
/charge endpoint and creates a server span that represents handling the incoming HTTP request. The server needs to receive the propagated trace context (via headers) and continue the same trace.
charge_service.py
opentelemetry.propagate.inject. The remote service will extract the context to continue the same trace.
payment_service.py


To correlate client and server spans into a single distributed trace you must propagate the trace context (for example, via HTTP headers). You can accomplish this manually using
opentelemetry.propagate.inject / opentelemetry.propagate.extract, or by enabling automatic instrumentation for your HTTP client and web framework, which will handle propagation for you.ConsoleSpanExporter prints spans to stdout. Use this to verify trace IDs and parent-child relationships:
- When context is correctly injected, the client span and the server span will share the same
trace_id. - When context is not injected, you will see different
trace_ids and separate traces.
| Cause | Symptom | Fix |
|---|---|---|
| No propagation | Client span and server span have different trace_ids | Inject context on client (propagate.inject) and extract on server (automatic in many instrumentations) |
| Different propagation formats | Headers not understood by receiving service | Use default W3C Trace Context headers or ensure both sides use the same propagator |
| Services export to different backends | Traces not visible together in one UI | Configure both services to use the same OTLP endpoint/collector |
| Missing instrumentation | Server does not extract context | Add server instrumentation or manual extraction logic |
- Use the default W3C Trace Context for cross-service interoperability.
- Avoid recording sensitive information (PII, full card numbers) as span attributes or events.
- Ensure both services export telemetry to the same collector/backend (
OTLPSpanExporterendpoint).
- Ensure both services use the same propagation format (W3C Trace Context recommended).
- Either:
- Manually inject the trace context into outgoing request headers (as shown above) and ensure the server extracts it (or uses automatic server instrumentation), or
- Use OpenTelemetry instrumentation libraries for your HTTP client and server framework to automatically propagate context and create appropriate spans.
- Confirm both services export to the same backend (same OTLP endpoint/collector) and that the tracer resource
service.nameis set appropriately for each service.
- OpenTelemetry Python Documentation
- Jaeger — Distributed Tracing
- OTLP Exporter Specification
- W3C Trace Context
When adding span attributes, never include sensitive information such as full credit card numbers, authentication tokens, or PII. Use truncated or hashed values if you must record identifying information for debugging.