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?”
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.
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.
Trace
The complete story of one request as it travels through a system. All the work done for that request shares one trace identifier.
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.
Trace ID
A long unique number shared by every span in the same trace. It is the family name of all the spans.
Span ID
A shorter unique number identifying one particular span. It is the first name of one family member.
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.
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.
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.
| Journey | How context travels | Who handles it |
|---|---|---|
| Inside one process | Kept in implicit storage tied to the running thread or task | The OpenTelemetry SDK and instrumentation |
| Between two processes | Written into the outgoing message and read from the incoming one | Propagators 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.
HTTP request headers
The most common carrier. Context travels in standard header fields next to content type and authorization.
Message queue headers
Queues and streaming platforms let each message carry small named attributes, which is the natural place to put context.
RPC metadata
Remote procedure call frameworks attach key-value metadata to each call, and context rides inside it.
Custom envelopes
Systems with their own message formats, such as files or batch jobs, can store context in a dedicated field you define.
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.
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.
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.
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.
The values are turned into text
Following its format rules, the propagator writes the identity as short strings, for example a hexadecimal trace ID.
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.
The message is sent
The request leaves the service carrying its hidden identity tag, ready for the next service to find.
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.
A request arrives
The server framework receives the incoming message and, through instrumentation, pauses before creating its own span.
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.
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.
A new context is produced
The extracted values are placed into a new context object, leaving the original untouched, since contexts are immutable.
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.
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.
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.
Propagator
Knows the format. Decides the field names and the text layout for trace identity or baggage.
Setter
A tiny helper that knows how to write one named value into a particular type of carrier.
Getter
A tiny helper that knows how to read a named value, and list the available names, from a carrier.
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.
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"]
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.
| Part | Size | Meaning | Plain explanation |
|---|---|---|---|
| Version | 2 characters | Format version number | Tells the reader which rules to use. The current version is zero. |
| Trace ID | 32 characters | Identifier of the whole trace | The shared family name for all spans in the request. |
| Parent ID | 16 characters | Identifier of the caller’s span | The span that made this call, to become the parent. |
| Trace flags | 2 characters | Bit flags, including sampling | Tells 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.
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.
traceparent
Required for linking spans. Carries the trace ID, parent span ID, and the sampling flag.
tracestate
Optional vendor-specific data that rides alongside traceparent and must be passed on unchanged.
baggage
Optional application-defined key-value pairs that you choose, which flow to every downstream service.
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.
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
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.
Jaeger
The native header format of the Jaeger tracing project, used by services that were instrumented before the standard arrived.
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.
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
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.
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
Reading the story
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.
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.
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.
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.
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.
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.
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.
| Situation | Best relationship | Reason |
|---|---|---|
| One request, one downstream call | Parent and child | Direct cause and effect, and timing is tightly connected |
| One message, one consumer | Parent and child, or a link | Depends on whether you want one continuous timeline |
| Many messages, one batch consumer | Links to each message | A span can have only one parent, but many links |
| Scheduled job triggered long after | Link | Long 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.
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.
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
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.
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.
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
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
| Symptom | Likely cause | What to check |
|---|---|---|
| Each service shows its own separate trace | Context not injected or not extracted | Instrumentation enabled on both client and server |
| Trace breaks at one specific hop | A proxy, gateway, or firewall strips headers | Header allow lists and proxy settings |
| Trace breaks between two languages | Different propagator formats configured | The propagator setting in both services |
| Trace breaks after a queue | Message headers not written or not read | Producer injection and consumer extraction |
| Spans appear with wrong parents inside one service | Context lost across threads or async tasks | Manual context capture in custom thread handling |
| Browser calls start new traces | Cross-origin rules block trace headers | Allowed header settings on the backend |
Habits that work
Standardize on W3C
Use W3C Trace Context and Baggage as the shared default so that every service and tool can understand each other.
Use auto-instrumentation first
Frameworks and client libraries usually handle injection and extraction for you. Rely on them before writing manual code.
Test across boundaries
Send a test request through the full path and check that one trace ID appears everywhere, including across queues.
Allow the headers through
Configure gateways, proxies, and cross-origin rules to pass the propagation headers untouched.
Control baggage
Keep baggage small, non-sensitive, and deliberate. Document which keys are allowed and who reads them.
Protect the edge
Decide how to handle trace context from untrusted clients, and apply that policy at the gateway.
Traps to 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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.