What Is Context Propagation in OpenTelemetry?

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?

Question 1

Which request is this?

Each service needs to know the identity of the overall request it is helping to serve.

Question 2

Who called me?

Each step needs to know which earlier step triggered it, so the steps form a tree.

Question 3

Should I record this?

Every service should make the same keep-or-drop decision, so traces are not left with missing pieces.

Question 4

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.

Simple Analogy

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.

i
Good to Know

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.

Part 1

Trace Identifier

A long random number shared by every span in the same trace. It is the wristband number for the whole request.

Part 2

Span Identifier

A shorter random number identifying one particular span. The next service uses it to know who its parent is.

Part 3

Trace Flags

A few on-or-off switches. The most important one says whether this trace is being sampled, meaning recorded.

Part 4

Trace State

Optional vendor-specific notes that tracing systems can use to pass along extra information of their own.

Part 5

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"]
        
FIG 1 — Spans in different services form one tree because they share a trace identifier and point to parents

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.

i
Remember

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.

In-process
Carrying context between functions and threads inside one program
Cross-process
Carrying context between services over the network
Automatic
Usually done for you by instrumentation libraries
Simple Analogy

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.

“Context propagation is the thread that turns many spans into one story.”
i
Good to Know

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.

Idea 1

Immutable

A context never changes after creation. Adding an entry makes a new context, so one part of the code cannot accidentally disturb another.

Idea 2

Current Context

The program tracks which context applies to the code running at this moment, so deep functions can find the active span.

Idea 3

Attach and Detach

Making a context current is called attaching it. When the work is done, it is detached and the previous context returns.

Idea 4

Language-Specific Storage

Each programming language uses its own natural mechanism to remember the current context for the running code.

Simple Analogy

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.

i
Practical Tip

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.

Idea 1

Carrier

The thing that physically travels with the request, such as the headers of a web request or the properties of a message.

Idea 2

Propagator

The component that knows how to write context into a carrier and read it back, following one specific format.

Idea 3

Inject

The sending side’s action: take the current context and write it into the outgoing carrier.

Idea 4

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
        
FIG 2 — Inject on the sending side and extract on the receiving side

Step by Step

1

Service A Is Doing Work

It has an active span, so there is a current context holding the trace identifier and the span identifier.

2

Inject Before Calling

Just before sending a request, the propagator writes the context into the request’s headers.

3

The Request Travels

The headers go along with the request over the network, passing through any gateways or proxies in between.

4

Extract on Arrival

Service B’s library reads the headers and rebuilds the context, marking it as having come from a remote process.

5

Create the Child Span

Service B starts its own span using the extracted context as the parent, so its work joins the same trace.

i
Good to Know

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 headerWhat it holdsWhy it matters
VersionA short number saying which version of the format is usedLets the format evolve safely over time
Trace identifierA long random value shared by the whole traceGroups every span of a request together
Parent identifierThe identifier of the span that made the callLets the receiver link its span beneath the caller’s
Trace flagsSmall switches, including whether the trace is sampledMakes 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.

Simple Analogy

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.

i
Good to Know

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.

QuestionSpan contextBaggage
What does it carry?Identity: trace and span identifiers, flagsYour own named values, such as a customer tier
Who defines the contents?The standard; the format is fixedYou; the names and values are yours to choose
Is it attached to spans automatically?It is the basis of how spans are linkedNo; you must copy chosen values onto spans yourself
Main riskVery lowLeaking 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.

Simple Analogy

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.

!
Important

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.

Format 1

W3C Trace Context

The modern industry standard and the OpenTelemetry default. Best choice for new systems.

Format 2

B3

Originated in the Zipkin tracing system. Comes in a single-header and a multiple-header style.

Format 3

Jaeger

The native format of the Jaeger tracing system, still found in many existing deployments.

Format 4

AWS X-Ray

Used by Amazon’s tracing service and by some Amazon-managed components.

Format 5

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.

SituationSuggested choice
All services are new or use OpenTelemetryThe default: W3C Trace Context plus baggage
Some services still use Zipkin-style headersAdd B3 alongside the default during migration
Some services still use Jaeger-style headersAdd the Jaeger format alongside the default
You call Amazon-managed services that emit X-Ray contextAdd the X-Ray format where needed
!
Common Mistake

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"]
        
FIG 3 — The sampling decision is made once and carried to every later service
Simple Analogy

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.

i
Good to Know

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 typeWhere context travelsNotes
Web calls over HTTPRequest headersThe most common case; usually fully automatic
Remote procedure calls such as gRPCCall metadataHandled by the instrumentation for the framework
Message queues and streamsMessage headers or propertiesProducer injects, consumer extracts; some systems lack header support
Scheduled or background jobsA field stored with the job recordYou may need to save and restore the context yourself
Browsers calling serversRequest headers from browser codeCross-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.

Simple Analogy

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.

i
Practical Tip

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.

Boundary 1

Thread Pools

A task handed to a worker thread runs on a thread that knows nothing about the request that created the task.

Boundary 2

Callbacks and Timers

Code scheduled to run later may run after the original context has been detached.

Boundary 3

Asynchronous Functions

When a function pauses and resumes, the language runtime must carry the context across the gap.

Boundary 4

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.

1

Capture the Context

At the moment work is handed off, take the current context, the same way you would pick up the baton.

2

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.

3

Restore It Before Running

When the work starts, make the captured context current again, so new spans find their parent.

i
Good News

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.

CauseWhat happensHow to fix it
A service is not instrumentedIt neither extracts nor injects, so the chain stops thereAdd instrumentation, or at least forward the headers
A proxy, gateway, or load balancer drops the headersThe context never reaches the next serviceConfigure it to pass tracing headers through
Mismatched propagator formatsOne service writes a format the next does not readUse composite propagators with at least one common format
Lost context at an async boundaryA new orphan trace begins mid-journeyCapture, carry, and restore the context
Messages without header supportThe consumer cannot read any contextEmbed the context in the message or use another carrier
A third party does not forward headersThe trace cannot continue beyond the outside serviceAccept that the trace ends there, or use links on return
Browser cross-origin rules block headersThe browser’s context never reaches your serverAllow 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.

!
Common Mistake

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.

Simple Analogy

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"]
        
FIG 4 — One request crossing a gateway, services, and a message queue
1

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.

2

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.

3

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.

4

Publishing to the Queue

The order service injects the context into the message headers before publishing the shipping request.

5

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.

i
Good to Know

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.

1

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.

2

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.

3

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.

4

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?

5

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.

i
Practical Tip

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.

Risk 1

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.

Risk 2

Forced Sampling and Load

If your system blindly honors an incoming sampled flag, outsiders could force heavy recording and drive up costs.

Risk 3

Baggage Leakage

Baggage and context are sent to every downstream service, including external ones, which can read any values in it.

Risk 4

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.

Simple Analogy

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.

!
Important

Never put secrets, tokens, or personal data into baggage. It travels widely and cannot be recalled once sent. Use short, harmless categories instead.

“Trust context inside your walls; verify it at the gate.”

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.

QuestionApplications and their librariesThe Collector
Does it inject and extract context on live requests?YesNo
Does it see trace and span identifiers?Creates and carries themReceives them on exported spans
Can it fix broken propagation?Yes, by correcting instrumentationNo; it cannot reconnect spans that never shared an identifier
Can it use identifiers for decisions?Head sampling at the start of a traceTail sampling and grouping after the trace
i
Good to Know

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.

i
Decision Hint

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

Practice 1

Use the Standard Default

Prefer W3C Trace Context plus baggage unless you have a clear reason to use something else.

Practice 2

Keep Formats Consistent

Make sure every service shares at least one format with its neighbors, using composite propagators during migrations.

Practice 3

Test Boundaries Early

Verify that traces cross gateways, proxies, queues, and browsers before relying on them in an incident.

Practice 4

Capture, Carry, Restore

For custom handoffs, always capture context at submission and restore it before running.

Practice 5

Keep Baggage Small and Safe

Include only short, harmless categories, never secrets or personal data.

Practice 6

Define Trust Boundaries

Decide deliberately whether to trust incoming context at the edge and whether to send it to outsiders.

Mistakes to Avoid

ANTI-PATTERN-01 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.

ANTI-PATTERN-02 Avoid
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.

ANTI-PATTERN-03 Avoid
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.

!
Common Mistake

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.

Q1What is context propagation in one sentence?

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.

Q2Do I have to write propagation code myself?

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.

Q3What is the difference between context and baggage?

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.

Q4What is a propagator?

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.

Q5What is the default format?

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.

Q6Why do my traces appear in two separate pieces?

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.

Q7Does the Collector propagate context?

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.

Q8Is baggage added to spans automatically?

No. Baggage is only carried along. To make a baggage value searchable on spans, you must copy it onto them explicitly.

Q9How does a message queue fit in?

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.

Q10Should I trust trace headers from the public internet?

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.