Skip to main content
This guide shows how to connect two services so that an outgoing HTTP call from the Payment service to the Charge service is represented as a single correlated distributed trace. The objective: when the payment service executes its 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.
Quick overview
  • 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.inject on the client and extract it on the server (or rely on automatic instrumentation).
Configure tracing (shared pattern) Each service reuses a common tracer configuration module. This ensures consistent resource attributes and exporter configuration across services. tracing_config.py
Charge service (Flask) The charge service exposes a /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
Call site (Payment service — client) The payment service validates the payment details and then calls the charge service. For the outgoing HTTP call, create a CLIENT span, attach useful attributes, and inject the current trace context into the HTTP headers with opentelemetry.propagate.inject. The remote service will extract the context to continue the same trace. payment_service.py
Why context propagation matters If you run both services but do not inject the trace context into the outgoing HTTP request, the charge service will start a new trace (different trace ID). That results in two separate traces in Jaeger rather than one correlated trace spanning both services. Screenshots below illustrate the common symptom: a trace for the payment service (with a CLIENT span) and a separate trace for the charge service (server span) that has a different trace id.
The image shows a Jaeger UI with a trace view for a "payment service," including details like trace duration, depth, total spans, and a breakdown of service operations with timing information.
You can also inspect the charge service traces in Jaeger and observe a different trace id for the server span:
The image shows the Jaeger UI interface with search parameters for tracing a "Charge service" with two resulting traces displayed, including their durations and timestamps.
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.
Debugging locally with ConsoleSpanExporter Switching both services to use 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.
Common causes for separate traces and quick fixes
CauseSymptomFix
No propagationClient span and server span have different trace_idsInject context on client (propagate.inject) and extract on server (automatic in many instrumentations)
Different propagation formatsHeaders not understood by receiving serviceUse default W3C Trace Context headers or ensure both sides use the same propagator
Services export to different backendsTraces not visible together in one UIConfigure both services to use the same OTLP endpoint/collector
Missing instrumentationServer does not extract contextAdd server instrumentation or manual extraction logic
Best practices and security notes
  • 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 (OTLPSpanExporter endpoint).
Next steps (what to change if you see separate traces)
  • 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.name is set appropriately for each service.
References and further reading
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.

Watch Video