What Is Context Propagation in OpenTelemetry?
Context propagation is how OpenTelemetry carries the identity of a request from one part of your system to the next, so that work spread across many services can be stitched into a single trace. This guide explains it from zero: what context is, how it travels, what breaks it, and how to keep it intact.
Think of a relay race. Four runners each cover one part of the track, and the team wins or loses together. But a race only works if the baton is passed from one runner to the next. If a runner drops the baton, or hands it to a stranger from another team, the race stops making sense. A modern application is a relay race too. A single click on a website may be handled by a web server, a login service, a payment service, and a database, each running separately. For the whole journey to be understood as one story, something must be passed from service to service like a baton. In OpenTelemetry, that baton is called context, and passing it along is called context propagation.
1The Big Picture: Why a Trace Needs a Thread
Tracing only works if every step of a request knows which request it belongs to.
Years ago, most applications were a single program. When something went wrong, you looked at that one program’s logs and found the cause. Today, applications are usually built from many small services that call each other over the network. A single user action can touch ten or twenty services, each with its own logs, its own timing, and its own failures. When a customer says “checkout is slow”, the cause might be in any one of them.
Distributed tracing was invented to solve this. A trace is the complete story of one request, made up of spans, each span being one measured step of work. A good trace shows you the whole journey on one timeline, so you can see at a glance that the payment step took four seconds while everything else was fast. But there is a hidden difficulty. Each service creates its own spans independently, on its own machine, with no shared memory. How does the payment service know that its work belongs to the same story as the work done by the web server a moment earlier?
Which request is this?
Each service needs to know the identity of the overall request it is helping to serve.
Who called me?
Each step needs to know which earlier step triggered it, so the steps form a tree.
Should I record this?
Every service should make the same keep-or-drop decision, so traces are not left with missing pieces.
What else should I know?
Sometimes business details, such as a customer tier, must travel with the request.
The answer to all four questions is context propagation. It is the mechanism that carries a small package of identifying information along with the request, from the first service to the last.
Imagine a hospital patient moving between departments: reception, X-ray, the laboratory, the doctor. Each department keeps its own notes. But the patient wears a wristband with an identification number, and each department writes that number on its notes. Later, anyone can gather every note about that patient from every department. Context propagation is the wristband that follows the request everywhere it goes.
Context propagation is what turns a pile of separate spans into a single connected trace. Without it, you simply have many unrelated fragments.
2First, What Is Span Context?
The thing that actually travels is a small, fixed package called the span context.
Before we talk about moving context around, we need to know what is inside it. In OpenTelemetry, the most important part of the context that travels between services is the span context. It is a compact set of facts that identifies a particular span and the trace it belongs to. It is small on purpose, because it must be attached to every request that crosses a service boundary.
Trace Identifier
A long random number shared by every span in the same trace. It is the wristband number for the whole request.
Span Identifier
A shorter random number identifying one particular span. The next service uses it to know who its parent is.
Trace Flags
A few on-or-off switches. The most important one says whether this trace is being sampled, meaning recorded.
Trace State
Optional vendor-specific notes that tracing systems can use to pass along extra information of their own.
Remote Marker
A flag set on the receiving side saying this context arrived from another process rather than being created locally.
flowchart TD
T["Trace: one whole request"] --> A["Span A in service 1"]
A --> B["Span B in service 2"]
B --> C["Span C in service 3"]
A --> D["Span D in service 1"]
Why Two Identifiers?
The trace identifier answers “which request?”, and it never changes as the request moves along. The span identifier answers “which step?”, and it changes at every step. When service one calls service two, it sends its own span identifier. Service two creates a new span, gives it a new span identifier, and records service one’s span as its parent. That single link is what lets a tool draw the tree of calls.
The trace identifier is the same everywhere in a trace. The span identifier is unique to each step. Together they say which request this is and exactly where in the request we are.
3What Is Context Propagation?
Context propagation is the act of passing the context from one place to the next so a request keeps its identity.
Context propagation is the mechanism by which OpenTelemetry carries context, mainly the span context and optionally extra key-value information called baggage, along the path of a request. It makes sure that every piece of work done on behalf of a request can discover which trace it belongs to and which step triggered it.
There are really two separate problems hidden inside this one idea, and it helps to keep them apart. The first is passing context inside a single program, from one function to another, even across threads. The second is passing context between different programs, over the network, from one service to another. OpenTelemetry solves each with a different technique, and both are needed for complete traces.
Inside a single office, a colleague simply hands you a folder across the desk. That is in-process passing. Between two offices in different cities, the folder must be put in an envelope with a label and sent by courier. That is cross-process passing. Both are about the same folder reaching the right person, but each needs its own method.
The Good News: It Is Mostly Automatic
For most applications, you do not write propagation code. Automatic instrumentation libraries for popular web frameworks, HTTP clients, database drivers, and messaging clients already handle it. When your service receives a request, the library reads the incoming context. When your service makes an outgoing call, the library attaches the context. You only need to understand the idea so that you can recognize when something goes wrong, and so that you can handle the unusual places where automation cannot reach.
The idea is not limited to tracing. The same mechanism lets logs carry trace identifiers and lets metrics link to example traces, because they can all read the current context.
4Inside One Program: The Context API
Before context can cross the network, it has to travel correctly inside a single program.
Inside a single program, many functions run one after another, calling each other. When your code handles a request and starts a span, the program needs a way for deeper functions to discover that span without passing it as an argument through every single call. If every function needed an extra parameter just for tracing, code would become cluttered and fragile.
OpenTelemetry solves this with a context object. You can think of it as a small, read-only notebook that holds a few entries, such as the currently active span. Whenever new information is added, a new notebook is created with the extra entry, while the old one is left untouched. The program keeps track of which notebook is current for the code that is running right now, and any code can ask what the current context is.
Immutable
A context never changes after creation. Adding an entry makes a new context, so one part of the code cannot accidentally disturb another.
Current Context
The program tracks which context applies to the code running at this moment, so deep functions can find the active span.
Attach and Detach
Making a context current is called attaching it. When the work is done, it is detached and the previous context returns.
Language-Specific Storage
Each programming language uses its own natural mechanism to remember the current context for the running code.
Picture a theater stage with a spotlight. The spotlight shows who is currently speaking. When an actor begins a speech, the spotlight moves to them. When they finish, it returns to the previous actor. The current context is the spotlight, and the active span is the actor in it. Any stagehand can look up and see who is lit right now.
How Child Spans Find Their Parents
This is how parent-child relationships form automatically. When your code starts a new span, the library looks at the current context, finds the span that is active, and records it as the new span’s parent. Then the new span becomes active, so any deeper work becomes its child. When the new span ends, the previous span becomes active again. You never name the parent yourself; the context does the bookkeeping.
When you add your own spans by hand, make sure the new span is made active while the work runs. Otherwise, any work inside it, including calls to other services, will not be linked beneath it.
5Between Services: Carriers, Inject, and Extract
Crossing the network needs a way to write context into a message and read it back out.
When one service calls another over the network, the context must be written into the message so it can survive the trip, then read out again on the other side. OpenTelemetry describes this with a small set of simple ideas.
Carrier
The thing that physically travels with the request, such as the headers of a web request or the properties of a message.
Propagator
The component that knows how to write context into a carrier and read it back, following one specific format.
Inject
The sending side’s action: take the current context and write it into the outgoing carrier.
Extract
The receiving side’s action: read the incoming carrier and rebuild the context from it.
sequenceDiagram
participant A as Service A
participant N as Network
participant B as Service B
A->>A: Start span, make it current
A->>A: Inject context into outgoing headers
A->>N: Send request with headers
N->>B: Deliver request with headers
B->>B: Extract context from incoming headers
B->>B: Start child span under the extracted parent
B-->>A: Send response
Step by Step
Service A Is Doing Work
It has an active span, so there is a current context holding the trace identifier and the span identifier.
Inject Before Calling
Just before sending a request, the propagator writes the context into the request’s headers.
The Request Travels
The headers go along with the request over the network, passing through any gateways or proxies in between.
Extract on Arrival
Service B’s library reads the headers and rebuilds the context, marking it as having come from a remote process.
Create the Child Span
Service B starts its own span using the extracted context as the parent, so its work joins the same trace.
Inject and extract are mirror images. If one service writes context in one format and the next service looks for a different format, the link is silently lost. Both ends must agree on the format.
6The Standard Format: W3C Trace Context
A shared standard lets services written in different languages and by different vendors understand each other.
For propagation to work across a whole company, or across companies, everyone needs to agree on how the context is written. In the past, each tracing system invented its own header names and formats, which made mixing tools painful. To end this, the World Wide Web Consortium published a standard called W3C Trace Context. It is now the default propagation format in OpenTelemetry and is widely supported across the industry.
The Two Headers
The standard defines two headers. The first, called traceparent, carries the essential information. The second, called tracestate, carries optional extra notes for individual vendors. Most of the time, only the first matters to you.
| Part of the traceparent header | What it holds | Why it matters |
|---|---|---|
| Version | A short number saying which version of the format is used | Lets the format evolve safely over time |
| Trace identifier | A long random value shared by the whole trace | Groups every span of a request together |
| Parent identifier | The identifier of the span that made the call | Lets the receiver link its span beneath the caller’s |
| Trace flags | Small switches, including whether the trace is sampled | Makes every service follow the same keep-or-drop decision |
The four parts are written as a single compact line of text, separated by dashes. Humans do not need to read it, but it is worth knowing that the whole thing is short, usually well under a hundred characters, which keeps the cost of attaching it to every request very small.
The Tracestate Header
The second header is optional. It is a list of named entries that individual tracing vendors can use to carry their own information alongside the standard one. For example, a vendor might record how it made a sampling decision. Services that do not understand a vendor’s entries simply pass them along unchanged. This allows different tools to coexist without breaking each other.
Think of the standard postal address format. Because every country agrees on roughly where the name, street, and postal code go, a letter can cross many borders and still be understood. Trace context is the agreed address format for requests, and any tool that reads it can deliver the message.
OpenTelemetry libraries use W3C Trace Context together with baggage by default, so in a standard setup you usually need no configuration at all.
7Baggage: Carrying Your Own Business Context
Sometimes you need to send your own information along with the request, not just identifiers.
The span context carries identity: which trace and which step. But sometimes a downstream service needs a business fact that only an earlier service knows. For example, the web server may know the customer’s plan tier from the login, while a service four steps later wants to treat premium customers differently. Passing that fact as an argument through every intermediate service would be clumsy. Baggage solves this.
Baggage is a set of named values that travels along with the request, separate from the span context. Any service along the path can read the baggage and add to it. It is also defined by a W3C standard and travels in its own header, so any compatible tool can carry it.
| Question | Span context | Baggage |
|---|---|---|
| What does it carry? | Identity: trace and span identifiers, flags | Your own named values, such as a customer tier |
| Who defines the contents? | The standard; the format is fixed | You; the names and values are yours to choose |
| Is it attached to spans automatically? | It is the basis of how spans are linked | No; you must copy chosen values onto spans yourself |
| Main risk | Very low | Leaking information and adding overhead to every request |
An Important Point About Baggage
Beginners often assume that baggage values automatically show up as attributes on spans. They do not. Baggage is simply carried along; it is not recorded anywhere unless you take extra steps. If you want a baggage value to be searchable on spans, you must deliberately copy it onto the spans, usually with a small piece of configuration or a processor that reads the baggage and sets matching attributes.
Baggage is like a sticky note attached to a parcel that says “handle with care, priority customer”. Every depot the parcel passes can read the note and act on it. But the note is not automatically copied into each depot’s records; a depot clerk must choose to write it down.
Size and Cost
Baggage is sent with every outgoing request, so the more you put in it, the more every call costs in bandwidth and processing. The standards also limit how many entries and how many bytes baggage may contain, and libraries may drop entries beyond those limits. Keep baggage small and put only genuinely useful values in it, such as short, stable categories.
Baggage travels to every service your request calls, including services outside your organization. Never put secrets or personal data in baggage. Anything in it can be read by every recipient.
8Other Formats and Combining Propagators
Older systems use other formats, and OpenTelemetry can speak several at once.
W3C Trace Context is the modern default, but it is not the only format in the world. Before it existed, several tracing systems defined their own headers, and many of those systems are still running. If part of your infrastructure uses an older format, the services must still be able to understand each other, or your traces will break at the boundary.
W3C Trace Context
The modern industry standard and the OpenTelemetry default. Best choice for new systems.
B3
Originated in the Zipkin tracing system. Comes in a single-header and a multiple-header style.
Jaeger
The native format of the Jaeger tracing system, still found in many existing deployments.
AWS X-Ray
Used by Amazon’s tracing service and by some Amazon-managed components.
W3C Baggage
The standard format for baggage, carried in its own header alongside the trace context.
Composite Propagators
A composite propagator combines several formats at once. When sending, it writes context in all the chosen formats. When receiving, it tries each format in turn until one succeeds. This is extremely helpful during migrations. A service in the middle of an upgrade can accept both the old and the new format and emit both, so neighbors on either side still work. Once everything has moved to the standard, you can remove the extras.
Choosing Which Propagators to Use
Propagators are normally chosen through simple configuration, often a single setting that lists the formats by name, without any code changes. The key rule is consistency: every service that takes part in a trace must understand at least one format that its neighbors emit. A mixture of services each using a different single format, with no overlap, will produce broken traces.
| Situation | Suggested choice |
|---|---|
| All services are new or use OpenTelemetry | The default: W3C Trace Context plus baggage |
| Some services still use Zipkin-style headers | Add B3 alongside the default during migration |
| Some services still use Jaeger-style headers | Add the Jaeger format alongside the default |
| You call Amazon-managed services that emit X-Ray context | Add the X-Ray format where needed |
Configuring different propagators in different services without overlap. Service A writes one format, service B reads another, and the trace silently splits in two at that point, with no error message.
9Sampling Decisions Travel Too
Propagation also makes sure every service agrees on whether a trace is being recorded.
Recording every trace in a busy system is often too expensive, so systems typically record only a fraction. This is called sampling. But consider what would happen if each service made its own random choice. The first service might record its part, the second might skip it, and the third might record again. The resulting trace would be full of holes, and nobody could use it.
Propagation solves this by carrying the decision. The first service in a request, called the root, makes the sampling decision, and records it in the trace flags that travel with the context. Every later service reads that flag. A common setting, called parent-based sampling, tells each service to follow the decision of its parent: if the parent was sampled, record; if not, skip. As a result, a trace is either complete or entirely absent, never half there.
flowchart LR
A["Service A: makes the decision"] -->|"flag: sampled"| B["Service B: follows the flag"]
B -->|"flag: sampled"| C["Service C: follows the flag"]
A2["Service A: decides not to record"] -->|"flag: not sampled"| B2["Service B: skips"]
B2 -->|"flag: not sampled"| C2["Service C: skips"]
Think of a school trip where the teacher decides at the start whether the whole class will take photographs. Everyone follows that decision. If each student chose independently, the album would contain a few random photos with no complete story. A single early decision gives a complete album, or none at all.
Not Sampled Does Not Mean Not Propagated
Even when a trace is not being recorded, the context must still be propagated. The identifiers and the not-sampled flag still need to travel, so downstream services know to skip as well, and so that logs written along the way still carry consistent identifiers. A service that stops propagating when a trace is unsampled would break the chain for everyone after it.
Some systems use tail sampling in the Collector, deciding after the whole trace has been seen. That approach still depends on every span carrying the same trace identifier, which is exactly what propagation provides.
10Propagation Across Different Kinds of Connections
The idea is the same everywhere; only the carrier changes.
Requests do not only travel as web calls. Systems also talk through remote procedure calls, message queues, scheduled jobs, and file-based handoffs. The principle is always the same: write the context into whatever the carrier is on the sending side, and read it back out on the receiving side. What changes is where the context is stored.
| Connection type | Where context travels | Notes |
|---|---|---|
| Web calls over HTTP | Request headers | The most common case; usually fully automatic |
| Remote procedure calls such as gRPC | Call metadata | Handled by the instrumentation for the framework |
| Message queues and streams | Message headers or properties | Producer injects, consumer extracts; some systems lack header support |
| Scheduled or background jobs | A field stored with the job record | You may need to save and restore the context yourself |
| Browsers calling servers | Request headers from browser code | Cross-origin rules may block the headers unless allowed |
Messaging: Parent or Link?
Message queues add an interesting question. When a producer sends a message and a consumer processes it later, should the consumer’s span be a child of the producer’s span? Often yes, for a single message handled right away. But consider a consumer that picks up a batch of fifty messages and processes them together. That one piece of work relates to fifty different traces, and a span can have only one parent. For this case OpenTelemetry offers links: references from a span to other spans that are related but are not the parent. The batch span links back to the context of each message it handled.
A parent is like a person’s one mother. A link is like a bibliography entry: a work can cite many sources without being a child of any of them. A batch job is a work that cites many earlier requests.
Background Jobs and Long Delays
Sometimes a request creates work that will run much later, such as an email scheduled for tomorrow. The context can be saved along with the job, in a field in the database or the queue entry, and restored when the job runs. Whether the later work should be a child in the same trace or a new trace linked to the original is a design choice. Long delays often make a link more sensible than a child, because a trace that spans a whole day is awkward to view.
When using a messaging system, check whether your instrumentation library supports message headers for that system. If it does not, you may need to store the context inside the message itself.
11Async Boundaries: Where Context Gets Lost Inside a Program
Even within one program, context can slip away when work moves between threads or callbacks.
We said earlier that the current context is tracked for the code that is running right now. But modern programs often move work around: they hand tasks to a pool of worker threads, schedule callbacks to run later, or use asynchronous styles where a function pauses and resumes. At these boundaries, the program may resume the work in a place where the current context is no longer set, and the active span silently disappears.
Thread Pools
A task handed to a worker thread runs on a thread that knows nothing about the request that created the task.
Callbacks and Timers
Code scheduled to run later may run after the original context has been detached.
Asynchronous Functions
When a function pauses and resumes, the language runtime must carry the context across the gap.
Custom Queues Inside a Program
Work placed on an in-memory queue and processed by another part of the program loses its context unless it is stored with the work.
What Happens When It Is Lost
If the context is lost, the next span created has no parent to find. It starts a brand new trace, becoming an orphan root. Calls it makes to other services carry the new trace’s identifiers rather than the original ones. On a trace viewer, the original trace appears to stop, and a separate unrelated trace appears, with no visible link between them.
Capture the Context
At the moment work is handed off, take the current context, the same way you would pick up the baton.
Carry It with the Work
Store the context with the task, the callback, or the queue entry so it travels to wherever the work will run.
Restore It Before Running
When the work starts, make the captured context current again, so new spans find their parent.
Automatic instrumentation and many language runtimes already do these three steps for common thread pools and asynchronous mechanisms. You mainly need to handle your own custom handoffs.
12What Breaks Propagation Between Services
Most broken traces come from a handful of predictable causes.
When a trace shows up in pieces, the culprit is almost always one of a short list of causes. Knowing the list saves hours of confusion. Because a broken link does not produce an error message, you only find out when you look at a trace and see it stop where it should have continued.
| Cause | What happens | How to fix it |
|---|---|---|
| A service is not instrumented | It neither extracts nor injects, so the chain stops there | Add instrumentation, or at least forward the headers |
| A proxy, gateway, or load balancer drops the headers | The context never reaches the next service | Configure it to pass tracing headers through |
| Mismatched propagator formats | One service writes a format the next does not read | Use composite propagators with at least one common format |
| Lost context at an async boundary | A new orphan trace begins mid-journey | Capture, carry, and restore the context |
| Messages without header support | The consumer cannot read any context | Embed the context in the message or use another carrier |
| A third party does not forward headers | The trace cannot continue beyond the outside service | Accept that the trace ends there, or use links on return |
| Browser cross-origin rules block headers | The browser’s context never reaches your server | Allow the tracing headers in the server’s cross-origin settings |
Gateways, Proxies, and Meshes
Infrastructure between services deserves special attention. API gateways, reverse proxies, and load balancers sometimes remove unknown headers or rebuild requests from scratch. A service mesh sidecar may create its own spans but still needs the application to forward the incoming headers to its outgoing calls, because the mesh cannot guess which outgoing call belongs to which incoming request. Whenever traces break at an infrastructure boundary, check its header handling first.
Assuming that a service mesh makes application changes unnecessary. A mesh can see traffic between services, but without the application forwarding context from incoming to outgoing requests, the mesh cannot connect the pieces into one trace.
Think of a relay race where one runner hands the baton to a helper who puts it in a pocket and then runs the next leg empty-handed. The race still has runners, but the baton never reaches the finish. A silent hand-off failure is exactly like that.
13A Complete Journey, End to End
Let us follow one request through a whole system and see propagation at every step.
To pull everything together, imagine a customer placing an order on a shopping website. The request passes through a gateway, an order service, an inventory service, a payment service, and finally a message queue for shipping. Let us trace how context moves at each step.
flowchart LR
U["Customer browser"] --> G["Gateway"]
G --> O["Order service"]
O --> I["Inventory service"]
O --> P["Payment service"]
O --> Q["Message queue"]
Q --> S["Shipping worker"]
Arrival at the Gateway
The browser may have sent tracing headers. The gateway extracts them if they are trusted, or starts a fresh trace if not, then makes the sampling decision.
Gateway to Order Service
The gateway injects the context into its outgoing call. The order service extracts it and creates a span beneath the gateway’s span.
Order Service to Inventory and Payment
The order service makes two outgoing calls, each injecting the current context. Both new spans become children of the order span, in the same trace.
Publishing to the Queue
The order service injects the context into the message headers before publishing the shipping request.
The Shipping Worker
Later, the worker extracts the context from the message headers and creates its span. The trace now spans the whole journey, including work done after the customer’s page loaded.
What the Engineer Sees
On the trace screen, the whole order appears as one tree: gateway, order, inventory, payment, queue, and shipping. If the payment step is slow, it stands out immediately, and the logs written during that span, carrying the same identifiers, are one click away.
If any single step in this chain fails to inject or extract, the trace splits at that point. The pieces still exist but cannot be joined, which is why finding the broken link is the main debugging skill for tracing.
14Debugging Broken Traces
A simple routine finds the broken link in most cases.
When a trace is split in two, you need to find the exact place where the context was lost. The broken link is always between the last span that appears in the first piece and the first span that appears in the second. A methodical routine will find it quickly.
Find the Gap
Identify the last service whose spans are in the first trace and the first service whose spans start the second trace. The problem lies between these two.
Check the Sender
Does the calling service inject context when it makes this call? Check that the outgoing call is made through an instrumented client and while a span is active.
Check the Path
Inspect any proxy, gateway, or load balancer in between. Do tracing headers survive the trip? Compare the headers leaving the sender with those arriving at the receiver.
Check the Receiver
Does the receiving service extract context? Is its instrumentation enabled for this entry point, and does it use a propagator format that matches what arrives?
Check Internal Handoffs
If the break appears inside one service, look for thread pools, callbacks, or queues where the context may have been dropped.
Useful Clues
A few clues help. Many orphan root spans appearing in a service that should only ever be called by others suggests extraction is failing. A very high number of separate short traces, instead of fewer long ones, is a typical symptom of context loss. If one specific route is always broken but others work, the cause is probably on that route’s path, such as a proxy rule or a custom handoff in that code.
Temporarily logging the incoming tracing headers at a service entry point can quickly reveal whether context arrives at all. Remove the extra logging afterwards, and never log baggage values that may be sensitive.
15Security and Privacy of Propagated Context
Headers that cross boundaries can be forged or can leak information.
Propagation passes information across boundaries, and every boundary raises security questions. The trace identifiers themselves are random and reveal nothing about your users. But the way propagation works creates a few risks that are worth understanding, especially where your system meets the outside world.
Untrusted Incoming Headers
Anyone on the internet can send tracing headers to your public endpoint, including a forged trace identifier or a flag demanding the trace be recorded.
Forced Sampling and Load
If your system blindly honors an incoming sampled flag, outsiders could force heavy recording and drive up costs.
Baggage Leakage
Baggage and context are sent to every downstream service, including external ones, which can read any values in it.
Oversized Headers
A malicious sender may attach a huge baggage header to waste memory or bandwidth.
Trust Boundaries
The most important idea is the trust boundary. Inside your own network, services trust each other’s context. At the edge, where requests come from the public internet, you should decide deliberately whether to trust incoming headers. A common approach is to ignore incoming trace context at the public edge and start a fresh trace, optionally linking to the incoming identifiers rather than continuing them. The same care applies to outgoing calls to third parties: consider turning off propagation, especially baggage, for calls leaving your organization.
Think of a security desk at the entrance of an office. Visitors may present any badge they like, but the desk does not assume it is genuine. It issues a fresh visitor pass of its own. Inside the building, the staff trust each other’s passes. Likewise, your edge should issue a fresh trace rather than trusting a stranger’s.
Never put secrets, tokens, or personal data into baggage. It travels widely and cannot be recalled once sent. Use short, harmless categories instead.
16Where the Collector Fits In
Propagation happens in your applications; the Collector benefits from it.
A common point of confusion is whether the OpenTelemetry Collector takes part in context propagation. The answer is that propagation happens inside your applications and between them, in the headers of real requests. The Collector sits to the side, receiving the finished spans that applications export. It does not forward your customers’ requests, so it does not inject or extract context into them.
But the Collector depends heavily on the results of good propagation. Every span it receives carries a trace identifier and a parent identifier that were set by propagation. If propagation was broken, the Collector will faithfully receive fragments that cannot be joined. And features such as tail sampling rely on grouping all spans with the same trace identifier together so a decision can be made about the whole trace. Good propagation is therefore the foundation on which the Collector’s smarter features stand.
| Question | Applications and their libraries | The Collector |
|---|---|---|
| Does it inject and extract context on live requests? | Yes | No |
| Does it see trace and span identifiers? | Creates and carries them | Receives them on exported spans |
| Can it fix broken propagation? | Yes, by correcting instrumentation | No; it cannot reconnect spans that never shared an identifier |
| Can it use identifiers for decisions? | Head sampling at the start of a trace | Tail sampling and grouping after the trace |
When you route all the spans of a trace through a pool of Collectors, they must reach the same Collector for tail sampling to see the whole trace. The trace identifier, which propagation guarantees is shared, is what makes that grouping possible.
17Real-World Examples
Seeing propagation in familiar situations makes the ideas concrete.
The Mystery of the Split Traces
A team notices that every trace stops at their API gateway and a second unrelated trace begins at the next service. They discover that the gateway strips unknown request headers. After changing its settings to pass tracing headers, the traces join into one, and a slow database call in the second service finally becomes visible as part of the customer’s request.
Migrating Tracing Systems Without Breaking Traces
A company is moving from an older tracing system to OpenTelemetry. Services migrate one at a time, so for months some use the old header format and others the new. They configure composite propagators that accept and emit both. Traces stay unbroken throughout the migration, and the old format is removed once everything has moved.
Priority Handling with Baggage
A web service puts a short customer-tier category into baggage after login. A recommendation service several hops later reads that category and uses a faster, more expensive algorithm for premium customers. No intermediate service needed to change, because the baggage traveled with the request.
A Thread Pool That Lost the Thread
A service hands each payment check to a custom worker pool. Traces show the payment check as a separate disconnected trace. The team wraps tasks so that the current context is captured when a task is submitted and restored when it runs. The spans immediately reappear beneath the original request.
Following a Message Through a Queue
An order creates a shipping message. By injecting the context into the message headers and extracting it in the worker, the team can see in one trace both the customer-facing delay and the later shipping work, even though they happen minutes apart.
Protecting the Public Edge
A company notices strange traces with forged identifiers coming from outside. They configure the public gateway to start a fresh trace for every incoming request, linking to the incoming context rather than trusting it, and turn off baggage propagation to external partners.
18Advantages and Trade-offs
Context propagation is essential for distributed tracing, but it has costs and responsibilities.
Advantages
- Turns separate fragments into a single, complete trace of a request.
- Works across languages, frameworks, and vendors through shared standards.
- Lets every service follow one consistent sampling decision.
- Enables log and metric correlation using the same identifiers.
- Baggage allows business context to travel without changing every service.
- Mostly automatic through instrumentation libraries.
Disadvantages / Trade-offs
- A single broken link silently splits the trace, with no error message.
- Every service and piece of infrastructure on the path must cooperate.
- Extra headers add a small cost to every request.
- Baggage can leak information and add overhead if misused.
- Custom thread handoffs and unusual transports need manual care.
Is It Worth the Trouble?
For any system with more than one service, the answer is yes. Without propagation, distributed tracing is impossible, and you are left correlating logs from different services by hand, guessing from timestamps. The overhead of a few short headers is tiny compared with the time saved during incidents. The trade-offs are best handled by good defaults, consistent configuration across services, and a habit of checking that new components forward context properly.
A Simple Adoption Path
You do not need to get everything right on day one. Start by enabling automatic instrumentation in your main services, using the default propagators. Then verify that traces cross your most important boundaries, especially gateways and queues. Next, fix any breaks by checking headers and async handoffs. After that, decide your trust-boundary policy for the public edge and third parties. Finally, add baggage only if you have a clear use for it, with strict rules about what may go inside. Each step delivers value on its own.
If you find yourself correlating requests across services by matching timestamps and guessing, that is a clear sign that propagation is missing or broken somewhere.
19Best Practices and Common Mistakes
A handful of habits prevent most problems.
Habits Worth Building
Use the Standard Default
Prefer W3C Trace Context plus baggage unless you have a clear reason to use something else.
Keep Formats Consistent
Make sure every service shares at least one format with its neighbors, using composite propagators during migrations.
Test Boundaries Early
Verify that traces cross gateways, proxies, queues, and browsers before relying on them in an incident.
Capture, Carry, Restore
For custom handoffs, always capture context at submission and restore it before running.
Keep Baggage Small and Safe
Include only short, harmless categories, never secrets or personal data.
Define Trust Boundaries
Decide deliberately whether to trust incoming context at the edge and whether to send it to outsiders.
Mistakes to Avoid
Problem
Different services are configured with different single propagation formats, with no format in common between neighbors, or a gateway silently strips the tracing headers.
Why It’s Harmful
The trace splits at every mismatch, with no error. Teams see fragments, waste time on guesswork, and lose trust in tracing.
Correct Approach
Standardize on the default format, use composite propagators during migrations, and test that headers survive every proxy, gateway, and queue on the path.
Problem
Custom thread pools, callbacks, or in-memory queues run work without restoring the captured context, so spans appear as orphan traces.
Why It’s Harmful
Work done on behalf of a request disappears from its trace, hiding the very slow or failing steps you most need to see.
Correct Approach
Capture the context when work is handed off, carry it with the work, and restore it before the work runs. Use instrumentation-provided wrappers where available.
Problem
Teams put user details, tokens, or large amounts of data in baggage, and trust incoming trace headers from the public internet without question.
Why It’s Harmful
Baggage travels to every downstream service, including external ones, leaking information and inflating every request. Forged headers can force heavy recording or confuse traces.
Correct Approach
Keep baggage tiny and harmless, restrict propagation to trusted destinations, and start fresh traces at the public edge, linking to rather than trusting incoming context.
Stopping propagation for unsampled traces to save effort. Downstream services then cannot tell that the trace was deliberately skipped, and the chain of identifiers breaks for everyone after.
20Frequently Asked Questions
Quick answers to the questions beginners ask most often.
It is the mechanism that carries a request’s identity, mainly the trace and span identifiers and optionally baggage, from one place to the next so that all the work for one request joins into a single trace.
Usually not. Automatic instrumentation for popular frameworks, clients, and messaging libraries already injects and extracts context. You only need manual work for custom transports and custom handoffs between threads or queues.
Span context identifies the trace and the current step and has a fixed format. Baggage is a set of your own named values that travel with the request and are not recorded on spans unless you copy them.
A component that knows how to write context into a carrier, such as request headers, and read it back out, following a specific format such as W3C Trace Context.
W3C Trace Context for trace identity, together with W3C Baggage. Other formats such as B3, Jaeger, and X-Ray are available for compatibility with older systems.
Context was lost at some boundary. Common causes are an uninstrumented service, a proxy that drops headers, mismatched formats, or a lost context at a thread or callback boundary. Check the sender, the path, and the receiver in turn.
No. Propagation happens in applications and between them. The Collector receives exported spans that already carry identifiers, and it depends on good propagation for features such as tail sampling.
No. Baggage is only carried along. To make a baggage value searchable on spans, you must copy it onto them explicitly.
The producer injects context into message headers or properties, and the consumer extracts it. For batches that handle many messages at once, links are used instead of a single parent.
Usually not. Many organizations start a fresh trace at the public edge and link to the incoming context instead of continuing it, and they avoid sending baggage to external partners.
21Summary and Key Takeaways
Context propagation is the thread that connects the pieces of a distributed request. Inside a single program, a context object tracks the active span so child spans find their parents. Between programs, a propagator injects the context into a carrier such as request headers and extracts it on the other side, following a shared standard, W3C Trace Context, so that services written anywhere can understand each other. The same mechanism carries the sampling decision so traces are complete or absent, carries optional baggage for business context, and lets logs and metrics link back to traces. Most of the work is automatic, but broken links produce silent fragments, so learn the usual causes: uninstrumented services, headers dropped by infrastructure, mismatched formats, and lost context at asynchronous boundaries. Protect your edge, keep baggage small and harmless, and your traces will tell a complete, trustworthy story.
Key Takeaways
- It connects spans into a trace — propagation carries the trace identifier and the parent span identifier so work across services becomes one story.
- Two layers — in-process propagation moves context between functions and threads, while cross-process propagation moves it across the network using carriers.
- Inject and extract — the sender writes context into headers or message properties, and the receiver reads it out; both ends must agree on the format.
- Standard by default — W3C Trace Context and baggage are the defaults, and composite propagators help with older formats during migrations.
- The sampling decision travels too — the trace flags make every service follow the same keep-or-drop choice, producing complete traces.
- Know what breaks it — uninstrumented services, proxies that drop headers, mismatched formats, and lost context at thread and queue boundaries cause silent splits.
- Mind trust and privacy — verify context at the public edge, keep baggage small and free of secrets, and limit what is sent to outside parties.