What Is Context Injection and Extraction in OpenTelemetry?

What Is Context Injection and Extraction in OpenTelemetry?

Services in a distributed system cannot share memory, so OpenTelemetry teaches them to pass a small envelope of identity along with every call. Injection writes that envelope into an outgoing message, and extraction reads it back out on the other side.

Imagine a relay race in which the runners cannot see each other, and the only thing that connects them is the baton. If the first runner forgets to hand over the baton, the second runner starts the race as if nothing came before. The finish line officials then record two unrelated runs instead of one team effort. In a distributed system, the baton is a tiny piece of tracing identity called context. Injection is the act of placing the baton into the runner’s hand, and extraction is the act of the next runner grabbing it. Without these two steps, your traces fall apart into disconnected pieces.

1The Problem: Traces That Fall Apart

Before learning the solution, it helps to feel the pain that makes it necessary.

One user click, many services

When a customer presses the “Place Order” button in a shopping app, the request rarely stays inside one program. It may visit an API gateway, then an order service, then an inventory service, a payment service, and a notification service. Each of these is a separate process, often running on a different machine, written in a different language, and owned by a different team.

Each service can create its own spans, which are timed records of work. But if each service creates them in isolation, you end up with five small, unrelated traces instead of one story. The engineer looking at the tracing tool sees fragments and cannot answer the most basic question: “which of these pieces belong to the same customer request?”

Simple Analogy

Think of a courier parcel that travels through five warehouses. If every warehouse prints a fresh tracking number, you can never follow the parcel from start to finish. A single tracking number written on the parcel at the start and read by every warehouse is what lets the whole journey be tracked. Context propagation is that tracking number system.

Why services cannot just share memory

Inside one program, passing information around is easy because functions can share variables. Across services, no such sharing exists. Two services only talk by sending messages over a network, such as HTTP requests or queue messages. Any information that must travel from one service to the next has to be written into that message by the sender and read from it by the receiver.

This is exactly what injection and extraction do. They are the writing step and the reading step of carrying tracing identity across process boundaries.

i
Key Idea

Injection and extraction are the two halves of one job called context propagation. One half packs the information before sending. The other half unpacks it after receiving.

2A Quick Refresher on Traces and Spans

A few basic words need to be clear before we go deeper.

Term 1

Trace

The complete story of one request as it travels through a system. All the work done for that request shares one trace identifier.

Term 2

Span

One timed step of work inside a trace, such as “call the payment service” or “run a database query.” Spans have a start time, a duration, and a parent.

Term 3

Trace ID

A long unique number shared by every span in the same trace. It is the family name of all the spans.

Term 4

Span ID

A shorter unique number identifying one particular span. It is the first name of one family member.

Term 5

Parent and Child

A child span is created because of its parent. The parent-child links turn a flat list of spans into a tree that shows who called whom.

Why parent links need the parent’s identity

To create a child span in the payment service, that service must know the span ID of the span in the order service that made the call. Without it, the new span would have no parent and would start a brand new trace with a brand new trace ID. The whole purpose of propagation is to deliver the parent’s trace ID and span ID to the child, even though the child lives in a different process.

Simple Analogy

A family tree only works if each child records the name of the parent. If a newborn is registered without any parent details, they appear as the founder of a new family, even though they truly belong to an existing one.

3What Is Context in OpenTelemetry?

The word “context” is used in many places in programming, so let us pin down exactly what it means here.

The context object

In OpenTelemetry, a context is a small, immutable container that holds values relevant to the current piece of work. Immutable means that it cannot be changed once created. When you want to add something, you create a new context that contains the old values plus the new one. This design keeps things safe when many threads or tasks run at the same time.

The most important value stored in the context is the currently active span, which in turn gives access to the trace ID, the span ID, and the sampling decision. The context can also hold baggage, which is a set of user-defined key-value pairs that you want to travel along with the request.

Where the current context lives

Inside one process, the SDK keeps track of the “current” context for you, using whatever mechanism suits the language, such as thread-local storage or an async-aware storage mechanism. When your code starts a span, the span is made current, and any code running afterwards can discover which span it belongs to without being handed any extra parameters.

!
Common Misconception

The in-process current context does not automatically cross to another service. It lives only inside one running program. Crossing a process boundary needs the deliberate steps of injection and extraction.

Two kinds of context travel

It is useful to separate two journeys. The first is the journey inside one process, where the SDK manages the current context for you. The second is the journey between processes, where the context must be turned into text, sent over the wire, and rebuilt. Injection and extraction deal only with the second journey.

JourneyHow context travelsWho handles it
Inside one processKept in implicit storage tied to the running thread or taskThe OpenTelemetry SDK and instrumentation
Between two processesWritten into the outgoing message and read from the incoming onePropagators doing injection and extraction

4Propagation and the Carrier

Propagation is the big idea. The carrier is the vehicle that does the carrying.

What propagation means

Context propagation means moving context from one place to another so that related work can be linked together. It happens in two directions across a service boundary. On the sending side, the context is serialized, which means converted into a plain text form. On the receiving side, that text is parsed back into a context object.

What a carrier is

A carrier is the thing that physically transports the serialized context. It is whatever message structure the two services already use to talk to each other. For an HTTP call the carrier is the set of request headers. For a message queue the carrier might be the message headers or properties. For a remote procedure call the carrier is the call metadata.

The carrier is not owned by OpenTelemetry. It belongs to the protocol in use. OpenTelemetry simply borrows a little room in it, usually a few header fields, to hold the context text.

Carrier 1

HTTP request headers

The most common carrier. Context travels in standard header fields next to content type and authorization.

Carrier 2

Message queue headers

Queues and streaming platforms let each message carry small named attributes, which is the natural place to put context.

Carrier 3

RPC metadata

Remote procedure call frameworks attach key-value metadata to each call, and context rides inside it.

Carrier 4

Custom envelopes

Systems with their own message formats, such as files or batch jobs, can store context in a dedicated field you define.

Simple Analogy

The carrier is the envelope, and the context is the sticker on the envelope. The postal service does not care what is written on the sticker, but the people at each end do. Injection puts the sticker on, and extraction reads it.

“The carrier belongs to the protocol; the context belongs to OpenTelemetry.”

5Injection: Packing the Context for the Journey

Injection happens on the sending side, right before the request leaves the service.

What injection does

Injection takes the current context, finds the active span inside it, converts its identity into text according to a chosen format, and writes that text into the carrier. After injection, the outgoing message carries the trace ID, the span ID of the caller, and the sampling decision, plus any baggage that the application attached.

When injection happens

Injection happens at the exact moment the client is about to send the message, and while the client span for that call is active. This timing matters. The span ID written into the message should be the ID of the client span representing the outgoing call, because that span will become the parent of the work done on the other side.

1

The client starts an outgoing call span

Before sending, the instrumentation creates a client span describing the call, such as “call payment service,” and makes it the current span.

2

The propagator reads the current context

The configured propagator looks inside the current context and finds the client span’s trace ID, span ID, sampling flag, and any baggage.

3

The values are turned into text

Following its format rules, the propagator writes the identity as short strings, for example a hexadecimal trace ID.

4

The text is written into the carrier

Using a small helper called a setter, the propagator places each string into a named field of the outgoing message, such as an HTTP header.

5

The message is sent

The request leaves the service carrying its hidden identity tag, ready for the next service to find.

i
Good to Know

Injection does not create anything new. It only copies identity that already exists in the current context onto the outgoing message. If there is no active span, there is nothing meaningful to inject.

Example: The order service calls the payment service

While handling a checkout, the order service creates a client span called “charge card.” Right before sending the HTTP request, injection writes that span’s trace ID, span ID, and sampling flag into the request headers. The payment service will later read exactly these values.

6Extraction: Unpacking the Context on Arrival

Extraction is the mirror image of injection and happens on the receiving side.

What extraction does

Extraction takes an incoming message, looks into its carrier for the propagation fields, parses their text back into structured values, and builds a context object that represents the caller’s identity. The result is a context that says “the work I am about to do was caused by that span, in that trace.”

Remote parent: a special idea

The span context rebuilt by extraction is marked as remote. This flag tells the system that the parent span lives in a different process and that this service did not create it. It is a small detail with big value, because samplers and tools use the flag to treat incoming parents differently from local ones.

What happens next

The extracted context is then used as the parent when the server creates its own span. That server span receives the same trace ID as the caller and records the caller’s span ID as its parent. At this instant, the two services are stitched into one trace.

1

A request arrives

The server framework receives the incoming message and, through instrumentation, pauses before creating its own span.

2

The propagator reads the carrier

Using a helper called a getter, the propagator looks for its known fields in the headers or properties of the message.

3

The text is parsed and validated

The propagator checks that the text is well formed. If it is, it builds a remote span context. If it is not, it quietly ignores the field.

4

A new context is produced

The extracted values are placed into a new context object, leaving the original untouched, since contexts are immutable.

5

The server span is created as a child

The instrumentation starts the server span using the extracted context as the parent, and then makes the span current for the rest of the request handling.

!
Important Behavior

If extraction finds nothing, or finds invalid text, it does not raise an error. It simply returns the context unchanged, and the server span becomes the root of a brand new trace. This graceful behavior keeps requests working, but it also means broken propagation fails silently, which is why testing it matters.

Simple Analogy

A receptionist checks each visitor’s badge. If the badge is valid, the visitor is treated as part of an existing event. If there is no badge or it is smudged, the visitor is registered as a new arrival instead of causing an alarm.

7The Propagators API: Setters, Getters, and Carriers

OpenTelemetry offers a clean, small interface that hides the differences between carriers.

The propagator

A propagator is the component that knows one specific wire format. It has exactly two jobs, matching our two halves of the story. One job is to inject a context into a carrier. The other job is to extract a context from a carrier. Because the interface is so small, you can swap one format for another without changing the instrumented code.

Why setters and getters exist

Carriers come in many shapes. An HTTP header collection behaves differently from a message properties map or a metadata object. To avoid writing a separate propagator for every combination, OpenTelemetry splits the work. The propagator decides what text to write and which names to use. The setter and getter decide how to actually put text into, or read text from, a particular carrier type.

Piece 1

Propagator

Knows the format. Decides the field names and the text layout for trace identity or baggage.

Piece 2

Setter

A tiny helper that knows how to write one named value into a particular type of carrier.

Piece 3

Getter

A tiny helper that knows how to read a named value, and list the available names, from a carrier.

Piece 4

Carrier

The actual message part being used, such as request headers or message properties.

Global propagator and configuration

Instrumentation libraries do not choose a format themselves. They ask the SDK for the globally configured propagator and use that. This is why a single configuration setting can change the format used by every instrumented library in your application. The usual configuration is a standard environment variable named OTEL_PROPAGATORS, which holds a comma-separated list of format names. The common default combines W3C Trace Context and W3C Baggage.

i
Configuration Hint

Because the choice is global, make sure all services that talk to each other agree on at least one common format. A mismatch is one of the most frequent causes of broken traces.

flowchart LR
    CTX["Current context"] --> P1["Propagator inject"]
    P1 --> S["Setter writes fields"]
    S --> CAR["Carrier, for example headers"]
    CAR --> G["Getter reads fields"]
    G --> P2["Propagator extract"]
    P2 --> NCTX["New context on receiver"]
        
FIG 1 — The pieces involved in injection and extraction

8The W3C Trace Context Format

The standard that most modern systems use to write trace identity on the wire.

Why a standard matters

Before a common standard existed, every tracing tool invented its own header names and layouts. A service using one tool could not pass identity to a service using another. The W3C Trace Context specification solved this by defining a single, vendor-neutral format. It is the default choice in OpenTelemetry today, which is why traces can flow smoothly across products from different vendors.

The traceparent header

The core of the standard is a single header called traceparent. It holds four pieces of information separated by dashes, all written as lowercase hexadecimal text.

PartSizeMeaningPlain explanation
Version2 charactersFormat version numberTells the reader which rules to use. The current version is zero.
Trace ID32 charactersIdentifier of the whole traceThe shared family name for all spans in the request.
Parent ID16 charactersIdentifier of the caller’s spanThe span that made this call, to become the parent.
Trace flags2 charactersBit flags, including samplingTells the receiver whether the caller decided to record this trace.

The sampled flag

The trace flags part carries one especially important bit, the sampled flag. It tells the receiver whether the trace is being recorded. This lets all services in the chain make the same decision, so you do not get traces with missing middle sections. We will come back to this when we discuss sampling.

Validation rules

The specification forbids an all-zero trace ID or an all-zero parent ID. A header that breaks the layout rules is treated as invalid, and a receiver must not use it. This protects systems from malformed or damaged values.

32
Hex characters in a trace ID
16
Hex characters in a span ID
4
Parts in the traceparent header
Simple Analogy

The traceparent header is like a boarding pass. It has the flight number, the seat number, and a status stamp. Any airline desk in the world can read it because everyone agreed on the layout.

9Tracestate and Baggage: Two Extra Passengers

Trace identity is not the only thing that can travel. Two other fields often ride along.

Tracestate: vendor notes

The tracestate header is a companion to traceparent. It holds a list of key-value pairs that tracing vendors can use to carry their own extra information, without breaking the shared standard. For example, a vendor might store a sampling priority or a routing hint. Other systems are expected to pass the field along unchanged, even if they do not understand it, and to respect size limits on the list.

Baggage: your own data

Baggage is a different and more flexible feature. It lets your application attach its own key-value pairs to the context so that they travel to every downstream service. Typical examples are a customer tier, a feature flag name, or a region code. Any service in the chain can read the values and use them to enrich its own telemetry or change its behavior.

Baggage travels in its own header, separate from traceparent. It is handled by its own propagator, which is why the default configuration lists both Trace Context and Baggage side by side.

Field 1

traceparent

Required for linking spans. Carries the trace ID, parent span ID, and the sampling flag.

Field 2

tracestate

Optional vendor-specific data that rides alongside traceparent and must be passed on unchanged.

Field 3

baggage

Optional application-defined key-value pairs that you choose, which flow to every downstream service.

!
Common Mistake

Baggage is not automatically added to spans or metrics. It simply travels with the request. If you want a baggage value to appear as an attribute on spans, you must copy it there deliberately, or use a processor designed for that purpose.

Example: Passing the customer tier

At the gateway, the application places a customer tier of “premium” into baggage. The inventory, payment, and shipping services all read that value and tag their own spans with it. Engineers can now filter all traces for premium customers without any service looking up the tier itself.

“Trace context says who is calling; baggage says what the caller wants everyone to know.”

10Other Formats and Composite Propagators

Real systems are rarely uniform. Older tools and special platforms use other formats, and OpenTelemetry can speak several at once.

Other well-known formats

Format 1

B3

Originally created by the Zipkin community. It exists in a multi-header form and a compact single-header form, and many older systems still use it.

Format 2

Jaeger

The native header format of the Jaeger tracing project, used by services that were instrumented before the standard arrived.

Format 3

Cloud provider formats

Some cloud platforms define their own trace header. OpenTelemetry offers propagators for several of them so that traces can cross into and out of those platforms.

Format 4

OpenTracing style

Legacy systems migrating from older instrumentation may use prefixed headers carrying trace data, which a dedicated propagator can understand.

Composite propagators

A composite propagator is a wrapper that holds several propagators and runs them together. On injection, it writes all the formats at once, so that recipients using any of them can understand the message. On extraction, it tries each format in the configured order, and the first one that finds valid identity wins.

This is the practical tool for migration. Suppose half of your services still speak B3, and the other half have moved to W3C Trace Context. By configuring both formats everywhere, traces flow across the whole fleet during the transition, and you can retire the old format later.

flowchart TD
    OUT["Outgoing message"] --> COMP["Composite propagator"]
    COMP --> W["W3C Trace Context writes traceparent"]
    COMP --> B["Baggage writes baggage"]
    COMP --> L["Legacy format writes older headers"]
    W --> MSG["Message with all headers"]
    B --> MSG
    L --> MSG
        
FIG 2 — A composite propagator writes several formats into one message
Simple Analogy

A multilingual sign at an international airport shows the same message in several languages. A traveller reads whichever language they know, and everyone still reaches the right gate.

i
Migration Tip

During a migration, list the new standard first so it is preferred on extraction, and keep the legacy format after it as a fallback. Remove the legacy format only after you confirm that no service depends on it.

11A Full Journey: One Request Across Three Services

Let us follow one customer request and watch injection and extraction work together.

The scenario

A customer places an order. The request arrives at an order service, which calls a payment service, which in turn calls a fraud-check service. All three services use OpenTelemetry with the default propagators.

sequenceDiagram
    participant C as Customer App
    participant O as Order Service
    participant P as Payment Service
    participant F as Fraud Service
    C->>O: Request without trace identity
    Note over O: No identity found, start new trace and root span
    Note over O: Create client span, inject identity
    O->>P: Request with traceparent
    Note over P: Extract identity, create child server span
    Note over P: Create client span, inject identity
    P->>F: Request with traceparent
    Note over F: Extract identity, create child server span
    F-->>P: Response
    P-->>O: Response
    O-->>C: Response
        
FIG 3 — The same trace ID flows across all three services

Reading the story

1

The edge starts the trace

The customer app is not instrumented, so the order service finds no incoming identity. It creates a root span and a fresh trace ID.

2

The order service injects

Before calling the payment service, it makes a client span and injects the trace ID, that client span’s ID, and the sampling flag into the request headers.

3

The payment service extracts and continues

It reads the headers, rebuilds the remote context, and creates a server span whose parent is the order service’s client span. The trace ID stays the same.

4

The chain repeats

The payment service injects again before calling the fraud service, and the fraud service extracts. Each hop uses the previous hop’s span as its parent.

5

The tracing backend assembles the tree

All spans arrive at the tracing backend separately, but because they share one trace ID and have parent links, the backend draws them as one connected tree.

i
Observation

The spans never meet inside one process. The tracing backend joins them later purely by matching identifiers. The quality of injection and extraction therefore decides the quality of the final picture.

1
Trace ID across all hops
2
Injections in this journey
2
Extractions in this journey

12Beyond HTTP: Messaging, Async Work, and Batch Jobs

Synchronous calls are the easy case. Real systems also use queues, schedulers, and background workers.

Messaging systems

With a message queue, the sender and receiver are not connected at the same moment. The sender puts a message on the queue and moves on, and the receiver picks it up later, maybe seconds or hours later. The context must therefore be stored inside the message itself, in its headers or properties, so that it survives the wait.

On the sending side, injection writes identity into the message properties before publishing. On the receiving side, extraction reads those properties when a consumer takes the message. The consumer span can then be linked back to the producer span.

Parent-child versus links

Asynchronous workflows raise a design question. Should the consumer’s work be a child of the producer’s span, or should it only be loosely linked? For a single message processed soon after sending, a parent-child relationship is natural. For batches, where one consumer processes many messages at once, a single parent is impossible, so the consumer span records links to the span contexts of every message in the batch.

SituationBest relationshipReason
One request, one downstream callParent and childDirect cause and effect, and timing is tightly connected
One message, one consumerParent and child, or a linkDepends on whether you want one continuous timeline
Many messages, one batch consumerLinks to each messageA span can have only one parent, but many links
Scheduled job triggered long afterLinkLong gaps make a single timeline misleading

In-process async boundaries

Even inside one process, work can hop between threads or tasks. Thread pools, timers, and callbacks may run code on a different thread than the one that holds the current context. If the context is not carried along, the spans created in the new thread will not find their parent. Instrumentation libraries usually wrap these mechanisms, but custom thread handling may need you to capture the context and attach it again manually.

Browsers and mobile apps

Frontend applications can also start traces and inject headers into calls to the backend. For browsers there is an extra rule: cross-origin calls only allow headers that the backend explicitly permits, so the backend must allow the trace header names, or the browser will refuse to send them.

!
Watch Out

Some messaging platforms limit header size or strip unknown headers. Test the full path of a message from producer to consumer to confirm that the context arrives intact.

Simple Analogy

Posting a letter is different from making a phone call. The context is written on the envelope before posting, and the recipient reads it days later. A phone call, by contrast, lets the caller say their name immediately.

13Propagation and Sampling Decisions

Injection and extraction do more than link spans. They also carry the decision about whether to record the trace at all.

What sampling is

Recording every trace in a busy system is expensive. Sampling is the practice of keeping only a portion of traces. A sampler is the component that decides, at the start of a trace, whether to record it.

How the decision travels

When the first service decides to sample a trace, it sets the sampled flag in the trace flags. Injection copies that flag into the outgoing message. On extraction, the downstream service reads the flag and, with a common parent-based sampler, follows the same decision. This keeps all spans of a trace together, either all recorded or all dropped.

flowchart LR
    A["Service A decides to sample"] --> B["Injects sampled flag"]
    B --> C["Service B extracts flag"]
    C --> D{"Parent sampled?"}
    D -- "Yes" --> E["Record spans in B"]
    D -- "No" --> F["Do not record spans in B"]
    E --> G["Inject same flag onward"]
    F --> G
        
FIG 4 — The sampling decision is passed along with the context

Why consistency matters

If each service made its own random decision, a trace might be recorded in service A, dropped in service B, and recorded again in service C. The tracing backend would show a tree with a missing middle. By passing the decision through the context, every service stays in agreement.

i
Dropped Is Still Propagated

Even when a span is not recorded, the context should still be injected downstream, with the sampled flag cleared. This lets downstream services know the decision and keeps identity consistent for logs and other signals.

!
Edge Cases

Tail sampling, where the decision is made after a trace is complete, works differently, because the original flag cannot know the final outcome. In that case, all services may record spans and send them to a collector, which then decides what to keep. Propagation still links spans, but the flag no longer decides storage.

14Security and Privacy Considerations

Anything that crosses a boundary can be read, forged, or leaked. Context propagation is no exception.

Untrusted incoming context

Headers from the public internet can be set by anyone. A malicious client could send a forged traceparent with a chosen trace ID and a sampled flag turned on. If your service blindly trusts it, an attacker could force you to record huge numbers of traces, filling storage, or could try to pollute your traces with misleading identifiers.

To reduce this risk, many organizations ignore or replace incoming trace context at the public edge, such as at an API gateway, while trusting it on internal calls. Some also limit sampling decisions that come from outside.

Leaking data to third parties

Injection writes context into every outgoing call that instrumentation covers, including calls to third-party services outside your control. Trace identifiers are not usually secret, but baggage can contain business data. If a customer tier or internal identifier is placed in baggage, it may be sent to an external vendor that you called.

Size and abuse

Baggage and tracestate have size limits for good reason. Large values can bloat every request, slow down networks, or even cause servers to reject calls. Strict limits on both the number of entries and the total length help keep systems healthy.

Advantages of Propagation

  • Connects spans from many services into one trace
  • Carries the sampling decision consistently across services
  • Works across languages and vendors through open standards
  • Lets teams share business context through baggage
  • Supports gradual migration through composite propagators
  • Requires no shared storage between services

Disadvantages / Trade-offs

  • Adds a small amount of data to every call
  • Fails silently when headers are lost or invalid
  • Exposes identifiers and baggage to every recipient
  • Trusts incoming data unless you add protections
  • Needs consistent configuration across all services
i
Practical Rule

Treat baggage like a postcard: anyone handling it can read it. Never put secrets, personal data, or credentials into it.

15Troubleshooting, Best Practices, and Anti-patterns

When a trace looks broken, the cause is almost always found at a boundary between two services.

How to recognize broken propagation

SymptomLikely causeWhat to check
Each service shows its own separate traceContext not injected or not extractedInstrumentation enabled on both client and server
Trace breaks at one specific hopA proxy, gateway, or firewall strips headersHeader allow lists and proxy settings
Trace breaks between two languagesDifferent propagator formats configuredThe propagator setting in both services
Trace breaks after a queueMessage headers not written or not readProducer injection and consumer extraction
Spans appear with wrong parents inside one serviceContext lost across threads or async tasksManual context capture in custom thread handling
Browser calls start new tracesCross-origin rules block trace headersAllowed header settings on the backend

Habits that work

Practice 1

Standardize on W3C

Use W3C Trace Context and Baggage as the shared default so that every service and tool can understand each other.

Practice 2

Use auto-instrumentation first

Frameworks and client libraries usually handle injection and extraction for you. Rely on them before writing manual code.

Practice 3

Test across boundaries

Send a test request through the full path and check that one trace ID appears everywhere, including across queues.

Practice 4

Allow the headers through

Configure gateways, proxies, and cross-origin rules to pass the propagation headers untouched.

Practice 5

Control baggage

Keep baggage small, non-sensitive, and deliberate. Document which keys are allowed and who reads them.

Practice 6

Protect the edge

Decide how to handle trace context from untrusted clients, and apply that policy at the gateway.

Traps to avoid

ANTI-PATTERN-01 Avoid
Problem

Different services in the same system are configured with different propagator formats, with no common format among them.

Why It’s Harmful

Neither side can read the other’s headers, so every hop starts a new trace. Because extraction fails silently, nobody sees an error, only broken traces.

Correct Approach

Agree on one standard format, or configure a composite propagator that includes every format in use during a migration.

ANTI-PATTERN-02 Avoid
Problem

Teams place customer emails, internal account numbers, or tokens into baggage to make them easy to read in downstream services.

Why It’s Harmful

Baggage is sent to every service called, including third parties. This leaks private data and may violate privacy rules.

Correct Approach

Use opaque, low-risk identifiers, keep baggage minimal, and strip it before calling external services.

ANTI-PATTERN-03 Avoid
Problem

A gateway or proxy is configured to remove unknown headers for security, and propagation headers are removed along with them.

Why It’s Harmful

Every trace breaks at the gateway. Teams assume instrumentation is broken and waste time debugging application code.

Correct Approach

Explicitly allow the propagation header names to pass through, while still applying a deliberate policy about untrusted external values.

ANTI-PATTERN-04 Avoid
Problem

Custom threads or task runners are used without carrying the current context into the new thread.

Why It’s Harmful

Spans created in the background work appear as unrelated roots, so slow background steps vanish from the main request’s trace.

Correct Approach

Capture the current context before handing off work, and make it current again inside the new thread, or use context-aware wrappers provided by the SDK.

16Frequently Asked Questions

Short answers to questions that come up again and again.

Q1Do I have to write injection and extraction code myself?

Usually not. Instrumentation libraries for common web frameworks, HTTP clients, database drivers, and messaging systems perform injection and extraction automatically. You write manual code mainly for custom protocols or unusual carriers.

Q2Is context propagation the same as distributed tracing?

No. Distributed tracing is the broader practice of recording and viewing requests across services. Context propagation is the mechanism that makes it possible, because it links the spans from different services into one trace.

Q3Does the OpenTelemetry Collector do injection and extraction?

No. Injection and extraction are done inside applications by SDKs and instrumentation. The collector receives already-finished telemetry, and it uses the trace IDs inside the data, but it does not forward request context between your services.

Q4What happens if a service does not support OpenTelemetry?

If it is only a pass-through, such as a simple proxy that forwards headers unchanged, the trace can continue on the other side. If it drops the headers, the trace breaks there. The service will not appear in the trace, because it creates no spans.

Q5Why is the span ID in the header different from the one the server later creates?

The header carries the ID of the caller’s span, which becomes the parent. The server then creates its own new span with its own new ID, and records the received ID as its parent. Every span has a unique ID, and the header always points to the previous one.

Q6Can I use more than one propagator at the same time?

Yes. A composite propagator combines several formats. On injection, all formats are written. On extraction, they are tried in order. This is the standard way to support both old and new formats during a migration.

Q7Is baggage automatically visible in my traces?

No. Baggage travels with the request but does not become a span attribute by itself. You must read it and add it as an attribute yourself, or use a component designed to copy selected baggage entries onto spans.

Q8What if the incoming header is malformed?

The propagator treats it as if no context were present and does not raise an error. The server span then starts a new trace. For this reason, silent failures should be caught by testing, not by waiting for errors.

Q9Should logs also carry the trace ID?

It is a good habit. Once context is extracted, the current span’s trace ID and span ID are available to logging libraries, which can add them to each log line. This lets you jump from a log entry to its trace.

Q10Does propagation slow down my requests?

The cost is very small. It involves writing and reading a few short text fields. The more noticeable cost, if any, comes from large baggage values, so keep those compact.

17Summary and Key Takeaways

Modern applications are built from many small services, and each service sees only its own slice of a request. Context injection and extraction are the two small actions that stitch those slices into a whole. Injection copies the identity of the current span into an outgoing message, and extraction reads that identity from an incoming message and uses it as the parent of new work. Propagators define the format, getters and setters adapt to each carrier, and standards such as W3C Trace Context keep different systems speaking the same language. When this works, one trace ID follows a request from the edge to the deepest dependency. When it fails, it usually fails quietly at a boundary, so thoughtful configuration, testing, and security habits are what keep your traces whole.

Key Takeaways

  • Context is the shared identity of a request — it holds the active span, the sampling decision, and optional baggage, and it is immutable inside each process.
  • Injection packs, extraction unpacks — injection writes context into an outgoing carrier, and extraction rebuilds a remote context from an incoming one.
  • Propagators, setters, and getters split the work — the propagator knows the format, while setters and getters know how to use each type of carrier.
  • W3C Trace Context is the common default — the traceparent header carries version, trace ID, parent span ID, and flags, with tracestate and baggage as optional companions.
  • Composite propagators enable migration — support several formats at once so that old and new services can share traces during a transition.
  • Sampling decisions travel with context — the sampled flag keeps all services in agreement about whether to record a trace.
  • Failures are silent, so test and protect — verify propagation across gateways, queues, and threads, keep baggage free of secrets, and decide how to treat untrusted incoming context.