What Is a Composite Propagator in OpenTelemetry?

What Is a Composite Propagator in OpenTelemetry?

A plain-English guide to the OpenTelemetry component that lets one service speak several tracing "languages" at the same time, how it works inside, how to configure it safely, and the mistakes that quietly break your traces.

Imagine you are an interpreter at a large international meeting. Some delegates speak English, some speak French, and some speak Spanish. You do not ask everyone to switch to one language before the meeting starts. You simply listen for whichever language is being spoken, and when you pass a message along, you write it down in every language the next person might understand.

Modern software systems have exactly this problem. A single customer click can travel through dozens of services, built by different teams, in different years, with different monitoring tools. Each tool has its own way of writing down “this request belongs to this journey.” If the services cannot understand each other’s notes, the journey breaks into disconnected pieces, and nobody can see the full story.

The Composite Propagator is OpenTelemetry’s interpreter. It lets one service read and write several formats at once, so that old and new parts of a system can still be stitched into one complete picture. In this guide you will learn what it is, why it exists, how it works step by step, how to configure it, and which beginner mistakes to avoid. No prior knowledge is assumed. Every new term is explained the first time it appears.

i
How to read this guide

Read the first five chapters in order, because they build the vocabulary. After that, you can jump to the chapters on configuration, migration, mistakes or the FAQ when you need a specific answer.

1The Big Picture: Why Distributed Systems Lose the Thread

Before we meet the Composite Propagator, we need to understand the problem it solves. It starts with how modern software is built.

Years ago, many applications were a single large program, often called a monolith. When a user clicked a button, one program handled everything: it checked the login, read the database, and built the page. If something went wrong, an engineer could open one set of logs and follow the story from start to finish.

Today, many companies split their software into many small programs called services, which talk to each other over the network. This style is called microservices. One click on a shopping website might touch a login service, a product service, a pricing service, a stock service, a payment service and a recommendation service. Each one runs on its own machines, written by its own team, and often in its own programming language.

This design has big benefits. Teams can work independently, and busy parts of the system can be scaled up without touching the rest. But it creates a new difficulty: when the website is slow, which of the six services is to blame? Each service sees only its own small piece of the story. Nobody sees the whole journey.

Analogy

Think of a parcel that travels from a seller in one city to a buyer in another. It passes through a local pickup van, a sorting center, an airplane, another sorting center and a delivery van. If each company wrote its own private note and never shared the parcel number, you could never answer the question “where is my parcel?” The parcel number printed on the label, and copied by every handler, is what makes the whole journey trackable. In software, that label is called a trace identifier, and passing it along is called context propagation.

What observability needs

Observability is the ability to understand what is happening inside a system by looking at the information it sends out. One of the most useful kinds of information for distributed systems is the trace, which is a record of the full journey of one request through all the services it touched. To build a trace, every service must agree on one thing: the identity of the journey. If service A and service B both handle the same user request but label it with different identities, the monitoring tool will show two unrelated stories instead of one.

That agreement is not automatic. It has to be passed from service to service, riding along with the request itself, usually inside the invisible header lines of an internet message. The part of OpenTelemetry that does this passing is called a propagator. And when a system needs to pass the identity in more than one format at the same time, OpenTelemetry offers the Composite Propagator.

A trace is only as complete as the weakest handoff between two services.

2Traces, Spans and Context: The Basic Vocabulary

Three small words appear everywhere in tracing. Learn them once and the rest of this guide becomes easy.

Span

A span is a record of one piece of work, such as “the payment service charged the card” or “the database answered a query.” A span has a name, a start time, an end time, and some extra details called attributes. Think of a span as one line in a diary that says what happened and how long it took.

Trace

A trace is a collection of spans that all belong to the same request. If a customer’s checkout touched six services, the trace holds the spans from all six, arranged like a family tree. The first span is the parent, the work it triggered creates child spans, and those can have children of their own. The whole tree shows exactly where time was spent.

Trace identifier and span identifier

Every trace has a unique trace ID, a long random number. Every span inside it has its own span ID. When a service calls another service, it tells the next service two things: “we are in trace number such-and-such” and “the span that called you is span number so-and-so.” The second service then creates its own span as a child of the caller’s span. That is how the family tree is built across the network.

Context

In OpenTelemetry, context is a small bag of information that travels along with a piece of work. Inside one program, the context is carried automatically, often by the programming language’s runtime. The bag holds, among other things, the identity of the current span. Think of it as the badge that a visitor wears while walking around a building. Every room can look at the badge and know who the visitor is.

A second kind of information can ride in the same bag: baggage. Baggage is a set of simple key and value pairs, such as “customer tier equals gold” or “region equals europe,” that you want every downstream service to see. We will return to baggage in a later chapter.

Unit of work

Span

One timed piece of work, such as a database call. It has its own span ID and a link to its parent.

Whole journey

Trace

All the spans of one request, tied together by a shared trace ID.

The badge

Context

The bag of information that travels with the work, holding the current span identity and other items.

Extra notes

Baggage

User-chosen key and value pairs that travel with the request so every service can read them.

Analogy

Imagine a relay race. The trace is the whole race. Each runner’s lap is a span. The baton passed from runner to runner is the context. If a runner drops the baton, the race result cannot be recorded as one race anymore. Propagation is the careful handing over of the baton.

3What Is Context Propagation?

Inside one program, the context travels easily. The hard part is when the work crosses the network. This chapter explains how that crossing works.

When service A calls service B, the call is sent as a message over the network. The two services are different programs, possibly on different continents. They do not share memory, so the context bag in service A simply does not exist in service B. For the trace to continue, the information in the bag must be written into the message, sent along, and then read out again at the other side.

This process of carrying the context across a boundary is called context propagation. It has two halves, and the OpenTelemetry names for them are worth remembering.

  • Inject means taking the current context and writing it into the outgoing message. The sender does this just before the message leaves.
  • Extract means reading the context back out of the incoming message. The receiver does this as soon as the message arrives.

The thing that holds the information while it travels is called a carrier. For a web call, the carrier is the set of HTTP headers. For a message queue, the carrier is the message’s metadata fields. For a remote procedure call, the carrier is the call’s metadata. Whatever the transport, the carrier is simply a place where small named text values can be stored.

sequenceDiagram
  participant A as Service A
  participant B as Service B
  participant C as Service C
  Note over A: Span A is active
  A->>B: Request with trace headers injected
  Note over B: Extract headers, start Span B as child of Span A
  B->>C: Request with trace headers injected
  Note over C: Extract headers, start Span C as child of Span B
  C-->>B: Response
  B-->>A: Response
    
Figure 1: Inject on the way out, extract on the way in, so three services form one trace.

Follow the figure from top to bottom. Service A has an active span. Before calling service B, it injects the trace information into the request headers. Service B extracts that information as soon as the request arrives, and starts its own span as a child of A’s span. When B calls C, the same dance repeats. In the end, all three spans share the same trace ID and form one connected tree.

i
Good to know

The response going back usually does not carry the trace information. Context flows forward with requests, not backward with answers. The spans are linked by the parent and child relationship, which was already set when the request went out.

What happens when propagation fails

If service B does not extract the headers, or if it cannot understand them, it behaves as though the request just appeared out of nowhere. It starts a brand new trace with a brand new trace ID. Now the monitoring tool shows two separate traces: one that ends at A, and one that starts at B. This is called a broken trace or an orphan. Broken traces are among the most common and most frustrating problems in distributed monitoring, and the Composite Propagator exists to prevent a major class of them.

4Propagators: The Translators of OpenTelemetry

Now we meet the star of the supporting cast. A propagator is the component that actually performs the inject and extract steps.

A propagator is a small object with a very focused job. It knows one specific format for writing context into a carrier, and the same format for reading it back. It has three abilities, which come directly from the OpenTelemetry specification.

Write

Inject

Take the current context and write the relevant values into the carrier, such as the headers of an outgoing call.

Read

Extract

Look in the carrier for values in its format, and if they are found, put them into a context that the receiving service can use.

List

Fields

Report the names of the carrier entries this propagator might write, such as the header names it uses. Other tools use this list, for example to clean old values out of a carrier before reusing it.

Getters and setters

A propagator has to work with many kinds of carriers: web headers, queue messages, remote call metadata and more. It cannot know the details of each one. So OpenTelemetry gives it two tiny helpers. A setter knows how to write one named value into this particular kind of carrier. A getter knows how to read one named value from it, and how to list the available names. The propagator only talks to the getter and setter, which is why the same propagator works for web calls, queues and anything else.

Analogy

A propagator is like a letter writer who knows exactly how to format a letter in one language. The setter is the postal clerk who knows where to stick the letter on different kinds of packages: on the front of a box, inside the flap of an envelope, or on the side of a crate. The writer does not need to know about packaging. The clerk does not need to know about languages. Together they get any letter onto any package.

One propagator, one format

A simple propagator speaks only one format. The propagator for the W3C Trace Context standard writes and reads the headers defined by that standard. The propagator for B3, an older format from the Zipkin tracing project, writes and reads B3 headers. Each one is a specialist. If your whole company uses one format, you need only one propagator, and life is simple.

But real companies are rarely that tidy. Some services were built five years ago and emit one format. Some were built last year and emit another. Some are bought products that you cannot change at all. When a request must pass through all of them, a single specialist is not enough. That is the gap the Composite Propagator fills, and we will meet it properly in Chapter 6, after we look at the formats themselves.

i
Where propagators are used

You rarely call a propagator yourself. The instrumentation for your web framework, HTTP client, message queue library and database driver does it for you. Your job is mostly to choose and configure the propagators once, at startup, so that all that automatic instrumentation uses them.

5Meet the Propagation Formats

Several formats exist for writing trace context into messages. Knowing the main ones helps you understand why a composite is so often needed.

2
default formats in OpenTelemetry: trace context and baggage
5+
older or vendor formats still found in real systems
1
composite object that can combine them all
FormatWhere it comes fromMain header namesNotes
W3C Trace ContextA web standard published by the World Wide Web Consortiumtraceparent, tracestateThe modern default. Carries a 128-bit trace ID, a span ID and flags such as “sampled.”
W3C BaggageAnother W3C standardbaggageCarries user-defined key and value pairs, not the trace identity.
B3Zipkin projectSingle header b3, or several headers whose names start with X-B3-Very common in older Java systems and service meshes. Comes in a single-header and a multi-header style.
JaegerJaeger tracing projectuber-trace-idUsed by systems instrumented with older Jaeger client libraries.
AWS X-RayAmazon Web ServicesX-Amzn-Trace-IdAdded by some AWS load balancers and used by X-Ray tracing.
OT TraceThe older OpenTracing ecosystemHeaders starting with ot-tracer-Rare today, but found in older vendor libraries.
Google CloudGoogle CloudX-Cloud-Trace-ContextUsed by some Google Cloud load balancers and services.

Why so many?

Distributed tracing grew up in many places at once. Google, Twitter, Uber, Amazon and others each built their own tools before there was any standard. Each tool invented its own header names and value layouts. Companies adopted whichever tool was available when they needed it, and then lived with it for years. Later, the industry agreed on the W3C Trace Context standard, and OpenTelemetry chose it as its default. But agreeing on a new standard does not instantly change the thousands of services already running.

Analogy

Think of electrical plugs around the world. Different countries chose different shapes long before anyone dreamed of international travel. Later, a universal standard may be proposed, but all the old sockets remain in the walls. Travelers carry adapters, and some hotels install sockets that accept several shapes. A Composite Propagator is the hotel socket that accepts several shapes at once.

A quick look at the default format

The W3C traceparent header is a single line made of four parts separated by dashes: a version number, the 32-character trace ID, the 16-character ID of the parent span, and a two-character flags field. The flags field includes a “sampled” bit that tells the downstream services whether this trace is being recorded. The companion tracestate header lets different vendors add their own small notes without breaking each other. Because the standard is open and simple, many languages, frameworks, proxies and cloud services understand it directly.

i
Note on package availability

In most OpenTelemetry languages, the W3C formats ship with the core libraries, while propagators for B3, Jaeger, X-Ray and similar formats are provided in separate add-on packages or extensions. Check the documentation for your language and version before assuming a format is available by default.

6What Is a Composite Propagator?

Now we can give a clear definition and unpack it piece by piece.

A Composite Propagator is a propagator that is built out of other propagators. It does not know any trace format by itself. Instead, it holds a list of ordinary propagators, such as one for W3C Trace Context, one for W3C Baggage and one for B3, and it presents them to the rest of the system as if they were a single propagator. When asked to inject, it asks every propagator in its list to inject. When asked to extract, it asks every propagator in its list to extract.

The OpenTelemetry specification states the idea in simple terms: an implementation must offer a way to group several propagators, which may come from different cross-cutting concerns, so that they can be used together as one entity. The word “composite” comes from a classic software design idea, where a group of objects is treated exactly like a single object.

A Composite Propagator is a propagator made of propagators.

Why this is useful

The rest of your instrumentation, such as the web framework plugin or the queue client plugin, only knows how to talk to one global propagator. It does not know how to juggle a list. The Composite Propagator solves this by wrapping the list in a single object. The plugin calls inject once, and the composite fans that call out to every member. Nothing else in your program has to change.

Analogy

Think of a hotel receptionist who speaks to every guest in a different language. The guests do not carry a phone book of receptionists. They just walk up to the one desk. Behind the desk, the receptionist has colleagues who each speak a language. If a French guest arrives, the French speaker answers. If an English guest arrives, the English speaker answers. When the hotel sends a welcome letter, it prints the message in every language. The front desk is the Composite Propagator. The colleagues are the individual propagators.

What it can combine

There are two main reasons to combine propagators, and they are different in nature.

Reason 1

Different concerns

Trace identity and baggage are different cross-cutting concerns. One propagator carries the trace identity, another carries the baggage. You almost always want both, so the default configuration of OpenTelemetry is already a composite of the two.

Reason 2

Different formats of the same concern

You may need W3C Trace Context and B3 and X-Ray, all carrying the trace identity in their own style, because the services around you speak different dialects.

Both reasons use the same tool. The Composite Propagator does not care whether its members carry different kinds of information or the same information in different clothing.

i
Names in different languages

Most SDKs provide the composite under a recognizable name. Java offers a composite option on its text map propagator type. Python and JavaScript provide a class named Composite Propagator. Go provides a constructor for a composite text map propagator. Some SDKs build one for you automatically when you list several propagators in configuration, which is the topic of Chapter 9.

7How a Composite Propagator Works Inside

The idea is simple, and so is the machinery. Let us open it up and watch inject, extract and fields in action.

graph LR
  A["Active Context: trace identity and baggage"] --> B["Composite Propagator"]
  B --> C["W3C Trace Context Propagator"]
  B --> D["W3C Baggage Propagator"]
  B --> E["B3 Propagator"]
  C --> F["Carrier: traceparent and tracestate headers"]
  D --> F
  E --> F
  F --> G["Outgoing Request"]
    
Figure 2: On the way out, the composite asks each member to inject into the same carrier.

Inject: one call, many writes

On the sending side, the composite receives the current context, the carrier and the setter. It then loops over its members in order and asks each one to inject. Each member writes its own header or headers into the same carrier. When the loop finishes, the carrier holds the trace information in every format on the list. The outgoing request therefore carries, for example, the W3C headers and the B3 header side by side.

Extract: one call, many reads

On the receiving side, the composite receives the incoming carrier, the getter and a starting context. It loops over its members in order. Each member looks in the carrier for the headers of its own format. If it finds valid ones, it creates a new context that includes what it found and returns it. If it finds nothing, or finds something broken, it returns the context it was given, unchanged. The composite hands the result of one member to the next member as its starting context. After the loop, the final context is returned to the caller.

1

A request arrives with headers

The web framework plugin receives the request and asks the global propagator to extract context from the headers. The global propagator is the composite.

2

The first member reads its format

Say the first member is the W3C Trace Context propagator. It looks for the traceparent header. If it is there and valid, it returns a context containing the remote span identity.

3

The next member gets the result so far

The composite passes that context to the second member, for example the baggage propagator, which adds any baggage items it finds and passes the context on.

4

Later members may add or replace

If a later member, such as the B3 propagator, also finds valid trace identity, it can replace what the earlier member found. If it finds nothing, it leaves the context alone.

5

The final context is used

The framework plugin starts the service’s span as a child of the remote span stored in the final context, and the trace continues.

Fields: the combined list

The third ability, fields, is also combined. The composite returns the union of the field names of all its members, with duplicates removed. If one member uses traceparent and tracestate, another uses baggage, and another uses b3, the composite reports all four names. Tools that need to know which carrier entries are “propagation entries,” for example to clear stale ones before reusing a message, rely on this combined list.

i
Statelessness

Propagators do not keep information between calls. The composite and its members can safely be shared by every part of the program, including many threads at once. You create it once at startup and register it globally.

Mini example: a gateway in the middle

A company has an API gateway that receives requests from the outside and forwards them to internal services. The gateway uses a composite of W3C Trace Context, W3C Baggage and B3. A mobile app sends a request with W3C headers. The gateway extracts the context, starts a span, and when it calls an older internal billing service, it injects both the W3C headers and the B3 header. The billing service, which only understands B3, reads the B3 header and joins the same trace. A newer service downstream reads the W3C headers and joins the same trace. Both old and new services end up on one trace, with no change to the billing service.

8Order Matters: Conflicts and Precedence

The order of the list is not just a detail. It decides what happens when two formats disagree.

Imagine a request that arrives carrying two sets of trace headers, and the two sets describe different traces. This can happen, for example, when one proxy added an X-Ray header while an application behind it had already added W3C headers. What should the receiving service believe?

The specification does not give the composite a clever way to decide. It simply runs the members in order. Because each extract step starts from the context returned by the previous step, a later member that finds valid data will typically overwrite what an earlier member found. In practice, this means that the last member in the list that finds valid trace information wins. Implementations differ in small details, so check your SDK’s documentation, but this is the common behavior.

Analogy

Think of writing your address on a form that has been passed to several clerks. Each clerk checks whether there is an address in the language they understand and, if so, writes it into the official record, crossing out the earlier one. The record always ends up with the address written by the last clerk who found one. If you want the French address to win, you put the French clerk last in the line.

How to choose the order

  • Put your preferred, most trusted format last for extraction. In many modern setups, that is the W3C Trace Context format, which is the standard.
  • Remember that injection order barely matters. All members write into different headers, so the order of writing does not change what is sent. The order only matters for reading.
  • Be consistent across services. If service A prefers format X and service B prefers format Y, then in a conflict they might continue different traces. Agree on one order and publish it as a company rule.
  • Avoid unnecessary members. Every extra member adds work and creates more chances for conflict. Add only the formats that you really meet in your environment.
SituationWhat the composite doesResult
Only W3C headers arriveThe W3C member finds them, the B3 member finds nothingTrace continues from W3C
Only B3 headers arriveThe W3C member finds nothing, the B3 member finds themTrace continues from B3
Both arrive and agreeBoth members find the same identityTrace continues, no conflict
Both arrive and disagreeThe later member in the list overwrites the earlierThe later format wins
One arrives malformedThe member ignores it and leaves the context aloneThe other format is used, or a new trace starts
None arriveNo member finds anythingA new trace starts, this service is the root
!
Watch out

The baggage propagator and the trace propagators do not compete, because they store different things in the context. Order only matters between members that carry the same kind of information, such as two trace formats.

9Configuring a Composite Propagator

The best news about the Composite Propagator is that it is usually easy to set up. Often you can do it with a single setting and no code at all.

The environment variable way

OpenTelemetry defines a standard environment variable called OTEL_PROPAGATORS. You set it to a comma-separated list of format names, and the SDK builds a composite propagator from them automatically. The names defined by the specification include tracecontext, baggage, b3, b3multi, jaeger, xray, ottrace and none. Not every SDK supports every name, and some of them need an extra package to be installed.

If you do not set the variable, SDKs normally default to a list of tracecontext and baggage, which is itself a composite of two propagators. Setting the variable to none turns propagation off completely, which is almost never what you want in production.

Setting for the variableMeaningTypical use
tracecontext,baggageW3C trace identity and W3C baggageThe default, for new systems
tracecontext,baggage,b3The default plus the single-header B3 formatMigrating from older Zipkin style tracing
tracecontext,baggage,b3multiThe default plus the multi-header B3 formatTalking to older systems that need the separate X-B3 headers
tracecontext,baggage,jaegerThe default plus the Jaeger formatMixed fleets with older Jaeger libraries
tracecontext,baggage,xrayThe default plus the AWS X-Ray formatSystems that sit behind AWS components that add the X-Ray header
noneNo propagation at allSpecial tests only

The setup-in-code way

If you prefer to configure the SDK in code, or if you need a custom propagator, every language lets you build the composite yourself and register it as the global propagator. The pattern is always the same in spirit: create the individual propagators, hand the list to the composite builder, and register the result with the OpenTelemetry API. After that, all instrumentation in the process uses it. The details of the method names differ between languages, so consult the official documentation of your SDK.

Where the configuration must happen

The propagator must be registered before any instrumentation starts handling requests, ideally during application start-up. If a web framework plugin was initialized earlier and cached the old global propagator, it might keep using the old one. Many beginners lose hours to this ordering problem, so make configuration the very first step of start-up.

i
Agents and auto-instrumentation

If you use an agent or an auto-instrumentation tool that attaches to your program without code changes, the environment variable is usually the only knob you need. This is another reason to prefer it. The same container image can run with different propagators in different environments by changing just one setting.

i
What does the API do on its own?

The OpenTelemetry API, by itself, comes with a no-operation propagator that does nothing. This is by design, so that libraries can call the API without forcing any behavior. It is the SDK or agent that installs a real propagator. If you use only the API without an SDK, no context will be propagated at all.

10Data Flow and Lifecycle in a Mixed System

Let us follow one request through a company where old and new services live side by side, and see the composite at work at every handoff.

graph LR
  U["User Mobile App"] -->|"W3C headers"| G["API Gateway with composite: W3C, Baggage, B3"]
  G -->|"W3C and B3 headers"| L["Legacy Billing Service reads B3 only"]
  G -->|"W3C and B3 headers"| N["New Orders Service reads W3C only"]
  L -->|"B3 header"| D["Legacy Database Proxy"]
  N -->|"W3C headers"| P["Payments Service with composite: W3C, Baggage, B3"]
  P -->|"W3C and B3 headers"| L
    
Figure 3: One trace crossing old and new services, held together by composite propagators.

In the figure, the mobile app speaks the modern W3C format. The API gateway has a composite with three members, so it understands the incoming W3C headers and it also writes B3 headers when calling the old billing service. The legacy billing service reads B3 only, which is fine, because it receives B3. The new orders service reads W3C only, which is also fine, because it receives W3C. Later, the payments service calls the legacy billing service again, and its own composite writes both formats.

The old services never learned anything new. The new services never had to carry the burden of the old formats. The only places that needed the composite were the points where the two worlds meet.

Analogy

Picture an international airport. Passengers from different countries speak different languages, but the airport has multilingual signs at the main halls. The rest of the city does not need multilingual signs, because the people there share a language. The composite propagator belongs in the halls where different worlds meet.

The life of a trace across handoffs

  • Start. The first service that receives no usable headers becomes the root. It creates a new trace ID.
  • Each hop. A service extracts what it can understand, creates a child span, and injects headers in all configured formats before calling the next service.
  • Translation. When the formats differ, the composite effectively translates the identity from one dialect into another. The trace ID stays the same, only the clothing changes.
  • End. Each service sends its spans to the monitoring backend, where they are joined by the shared trace ID into one tree.
!
Identifier shape differences

Different formats have slightly different rules about identifiers. W3C uses a 128-bit trace ID. B3 allows 64-bit or 128-bit trace IDs. AWS X-Ray embeds a timestamp inside its trace ID and expects the ID to follow a certain layout. When translating between formats, propagators handle the conversion, but some combinations need care. For example, to make traces work cleanly with X-Ray, you may need an ID generator that creates X-Ray compatible IDs. Always test a round trip in a staging environment.

11Real-World Use Cases

Here are the situations where engineers most often reach for a Composite Propagator. The scenarios are illustrative examples meant to build intuition, not descriptions of any particular company’s internal systems.

1. The default setup you already use

Almost every OpenTelemetry installation uses a composite without anyone noticing. The default list of trace context plus baggage is a composite of two propagators. So even if you have never heard of the term, you have probably been running one.

2. Migrating from B3 or Jaeger to W3C

A company with hundreds of services uses an older format. It wants to move to the W3C standard, but it cannot change every service on the same day. During the migration, services use a composite of both the old and the new formats. Once every service has moved, the old format is removed. Chapter 12 describes this in detail.

3. Service meshes and sidecars

Service meshes and proxy sidecars sit next to applications and may create their own trace spans for each call. Some proxies are commonly configured with B3 or with W3C headers, depending on version and settings. If the proxy and the application use different formats, the traces will show gaps. A composite on the application side makes both layers join the same trace.

4. Cloud load balancers that add their own header

Some cloud load balancers add their own trace header to every incoming request, such as the AWS X-Ray header or the Google Cloud trace header. If your application also wants to honor its own format, a composite lets it read the cloud header when present and still emit W3C headers downstream.

5. Mergers and acquisitions

When two companies merge, they bring two sets of systems with two tracing formats. Replacing one of them takes years. A composite at the boundary services lets traces cross between the two estates in the meantime.

6. Calling third-party or vendor software

A vendor product might only understand a certain format, and you cannot change it. By writing that format in addition to your own, you can keep your trace unbroken through the vendor’s component, as long as the component passes the headers along.

7. Carrying business context with baggage

A team wants every downstream service to know the customer’s subscription tier without passing it through every function argument. They add the baggage propagator next to the trace propagator. The composite carries both. This use case is explored in Chapter 13.

12Migrating Between Formats Step by Step

Migration is the number one reason teams learn about the Composite Propagator. Here is a safe, calm way to move a whole company from an older format to the W3C standard without breaking a single trace.

The danger in any format change is the “big bang.” If you switch every service to the new format at the same moment, you need perfect coordination, and anything that goes wrong breaks everything. If you switch services one by one with no overlap, then every service that has switched cannot talk tracing to the services that have not, and traces break at the seams. The composite lets you replace the big bang with a gentle, three-phase slide.

1

Phase one: learn to read both

Change every service so that it can read both the old and the new format, while still writing only the old one. Since the receiving side now understands both, nothing breaks. Services that have not been changed yet still work, because everyone is still writing the old format.

2

Phase two: write both

Now change every service to write both formats and read both. At this point, a composite with both members is in place everywhere. Every service can talk to every other service, whichever format that service understands. Run in this state for as long as you need while you track progress.

3

Phase three: retire the old format

When you are confident that no remaining service or vendor product still relies on the old format, remove its member from the composite everywhere. The company is now fully on the new standard, and the headers are smaller again.

Analogy

Think of a country changing which side of the road its cars drive on. Doing it overnight everywhere is risky. A smoother plan would first make sure every driver and every road sign can handle both, then change over gradually, then remove the old signs. The composite is the dual road sign that serves both worlds during the changeover.

How to know when you are done

The hardest question in a migration is “is anyone still using the old format?” You can answer it with data. Add a small measurement in a shared library or in your gateway that counts how many incoming requests carry only the old headers and none of the new ones. When the count falls to zero for a long enough time, you can retire the old format. Without this kind of evidence, teams often keep old formats alive for years just because nobody dares to remove them.

i
Tip

Control the list through configuration rather than code. When the list is a single environment variable, rolling out phase two or phase three is a configuration change that can be reverted in seconds if something unexpected happens.

!
Do not forget non-HTTP paths

Traces also cross message queues, scheduled jobs and batch pipelines. Those hops use their own carriers, and old consumers may still read old fields. Include them in your inventory before you declare victory.

13Baggage Inside a Composite

So far we focused on trace identity. But the composite also carries a second kind of information, which brings both power and responsibility.

Baggage is a set of simple key and value pairs that travel with the request across services. For example, you might set a key named customer.tier with the value gold at the front door of your system. Every service downstream, no matter how deep, can read that value from the context without anyone passing it as an argument through every function call.

The W3C Baggage format sends these pairs in a header named baggage, as a comma-separated list. It is a separate standard from trace context, and in OpenTelemetry it has its own propagator. That is why the default list is two members: one for trace identity and one for baggage. The composite carries both in the same pass.

Analogy

The trace identity is the passport number of a traveler. Baggage is the luggage tag attached to the suitcase that says “handle with care” or “priority customer.” Everyone along the journey can read the tag and act on it, but nobody should be putting a secret diary in the suitcase tag.

What baggage is not

Baggage is not automatically attached to spans or metrics. The fact that a value is in the baggage means it is available to code that looks at the context. If you want a baggage value to appear as an attribute on spans, you have to add a step that copies it, for example a span processor or an explicit line in your instrumentation. Many people expect the copy to happen automatically and are surprised when it does not.

Why baggage deserves extra care

  • It goes everywhere. Baggage is sent on every outgoing call that uses the propagator, including calls to services owned by other companies. Anything you put in it can be seen by every recipient.
  • It uses network space. Each item adds bytes to every request on every hop. The W3C standard suggests limits on the number of entries and total size, and servers and proxies may reject headers that are too large.
  • It is easy to forge. Anyone who can send a request to your public edge can send a baggage header of their own choosing. Never trust baggage values for security decisions without checking them.
!
Golden rule of baggage

Put only small, non-sensitive, bounded values into baggage. Never put personal data, passwords, tokens or secrets in baggage. Treat it as a postcard that anybody along the way can read.

Choosing whether to include baggage at all

If you do not use baggage, you can leave the baggage member out of the list. Many teams keep it because the default includes it and because it may be used by libraries without their knowledge. If you keep it, consider stripping unknown baggage at your public edge, so that outside callers cannot inject values into your internal system.

14Design Patterns with Composite Propagators

A design pattern is a proven way of solving a problem that keeps coming back. These are the patterns teams use most often with composite propagators.

Pattern

Standard everywhere, translators at the borders

Keep most of the system on W3C only and place composites only in the services that sit at the border with older systems or vendor components. This limits extra header size and complexity to where they are needed.

Pattern

Dual-write migration window

Temporarily write and read both the old and new formats everywhere, track the old format’s usage, and then remove it. This is the three-phase plan from the previous chapters.

Pattern

Configure by environment, not by code

Define the propagator list through a single setting that can be changed per environment and rolled back quickly. Avoid hard-coding the list in many places.

Pattern

Edge sanitizing

At your public gateway, decide whether to trust incoming trace headers and baggage. Many teams start a new trace for untrusted callers and link it to the incoming one, rather than continuing it blindly.

Pattern

Custom propagator for a private header

If your company has its own header format, write a small custom propagator for it and add it to the composite like any other member. The rest of the system does not need to know.

Pattern

Shared platform library

Wrap the agreed list in a shared company library or base image so that every team gets the same list by default, instead of each team inventing its own.

The custom propagator pattern in more detail

Sometimes the format you need does not exist as a ready-made propagator. Maybe your company built an in-house tracing tool a decade ago and still has services that depend on it. OpenTelemetry lets you write your own propagator by implementing the same three abilities: inject, extract and fields. Once written, you register it as a member of the composite, and the framework instrumentation uses it without any special treatment.

The responsibilities of a custom propagator are narrow. When extracting, it must never throw an error on bad input, because incoming headers come from the outside world and can contain anything. It must return the unchanged context when it finds nothing valid. When injecting, it must only write the fields it announced in its fields list. If you follow these rules, your custom member will sit comfortably beside the standard ones.

Analogy

A custom propagator is like adding a new language specialist to the hotel desk. As long as the new specialist follows the desk rules, never panics at an unreadable letter, and only fills in her own part of the form, the desk keeps running smoothly.

The edge sanitizing pattern in more detail

Anyone on the internet can send your public endpoint a request with a trace header of their own making. If your gateway blindly continues that trace, outsiders can influence your traces: they can mark traces as “sampled” to make you record more data than you planned, or send huge baggage to eat your resources. A cautious approach is for the gateway to ignore incoming trace headers from untrusted sources and start a fresh trace, optionally recording the incoming identity as a link on the new trace. Internal services, which are trusted, then use the composite normally.

15Advantages, Disadvantages and Trade-offs

The Composite Propagator is a very helpful tool, but it is not free. An honest look at both sides helps you use it wisely.

Advantages

  • Joins old and new services into one unbroken trace.
  • Makes gradual migrations safe and reversible.
  • Requires no change to the code of services that only read one format.
  • Hides complexity: the rest of the instrumentation sees a single propagator.
  • Carries trace identity and baggage together in one pass.
  • Easy to configure with a single environment variable in most SDKs.
  • Stateless and thread-safe, so it can be shared freely.

Disadvantages

  • Every extra member adds headers to every request and every message.
  • More members create more chances for conflicts when formats disagree.
  • The order of members affects which format wins, which can surprise people.
  • Translating between formats with different identifier shapes can need extra care.
  • Old formats can linger forever if nobody tracks their use.
  • Debugging gets harder, because a problem may sit in just one of several formats.
  • Some formats require extra packages that must be kept up to date.

The central trade-off: compatibility versus simplicity

Each format you add makes your system understandable to more neighbors, but each one also costs a little: more bytes on every call, more code in every service, more cases to test. The best composite is the smallest composite that still keeps your traces whole. A common rule of thumb is to allow only formats that you can name a concrete service or product for. If you cannot point to a real user of a format, remove it.

Composite versus other approaches

ApproachHow it worksGood forDrawback
Single propagatorEvery service uses one formatNew systems with full controlFails when any neighbor speaks another format
Composite propagatorEach service reads and writes several formatsMixed fleets and migrationsExtra headers and order rules
Translating gatewayOne gateway converts formats between zonesClear boundaries between two estatesThe gateway becomes a critical single point
Proxy-based tracing onlyThe sidecar creates spans and the app does nothingQuick start without code changesMisses what happens inside the application

When not to use a composite

  • When every service you talk to already speaks the W3C standard, because extra members only add overhead.
  • When a legacy service does not forward headers at all. A composite on your side cannot save a trace that the other side drops. In that case, you might link traces instead.
  • When you need to keep a very small message size, such as for tiny messages on constrained networks. Carefully consider whether the extra headers are worth their cost.

16Common Mistakes and Anti-patterns

These mistakes appear again and again in real systems. Each one looks harmless, and each one can silently break traces.

ANTI-PATTERN 01 Avoid
The mistake

Different services use different propagator lists with no agreement, for example one team uses W3C only, another uses B3 only, and nobody has a composite at the meeting point.

Why it hurts

Every meeting point between two different lists becomes a break in the trace. Each break splits one journey into two unrelated traces, and nobody can see the full picture.

Better approach

Agree on a company standard list, publish it, and put it in a shared library or base configuration. Add extra members only at the places that truly need them.

ANTI-PATTERN 02 Avoid
The mistake

Registering the propagator too late, after the web framework or HTTP client instrumentation has already started and cached the default.

Why it hurts

Some instrumentation grabs the global propagator once and keeps it. If the composite is registered afterwards, those components never see it, and they quietly keep using the old behavior.

Better approach

Configure propagation as the very first step of application start-up, or use the environment variable so that the SDK or agent does it before anything else.

ANTI-PATTERN 03 Avoid
The mistake

Adding every format you have ever heard of, “just in case.”

Why it hurts

Each extra member adds headers to every call, adds the possibility of conflicts and makes debugging harder. Header space is limited, and some servers reject requests with too much header data.

Better approach

Include only the formats you can match to a real service or product. Review the list regularly and remove formats that nobody uses.

ANTI-PATTERN 04 Avoid
The mistake

Ignoring the order of members and then being surprised by which trace wins when two formats disagree.

Why it hurts

Two services with differently ordered lists can disagree about which trace a request belongs to, leading to duplicate or half-connected traces that are very hard to explain.

Better approach

Use the same order everywhere, and place your preferred format where it wins. Document the reason.

ANTI-PATTERN 05 Avoid
The mistake

Putting personal data, tokens or large payloads into baggage because “it is an easy way to pass data around.”

Why it hurts

Baggage travels on every outgoing call, including to third parties, and can be logged by proxies. It leaks private data, enlarges every request and can be forged by outside callers.

Better approach

Keep baggage tiny, non-sensitive and bounded. Pass real business data through proper channels.

ANTI-PATTERN 06 Avoid
The mistake

Trusting incoming trace headers from the open internet and continuing them blindly at the public edge.

Why it hurts

Outsiders can force sampling decisions, link their requests to your traces or inflate baggage. This can raise your monitoring costs or confuse investigations.

Better approach

At the public edge, start fresh traces for untrusted callers, and limit or remove incoming baggage.

17Best Practices

These habits keep your composite small, predictable and easy to debug. Think of them as a checklist you run through whenever you touch propagation settings.

Default

Start from the standard

Begin with W3C trace context and W3C baggage, and add other formats only when a real need appears.

Evidence

One format, one reason

For every extra format, write down which service or product needs it. If the reason disappears, remove the format.

Order

Same order everywhere

Use one agreed order across all services, with the preferred format placed so that it wins when formats conflict.

Config

Use the environment variable

Define the list through the standard setting rather than code, so it can be changed and reverted quickly and is visible in deployment files.

Timing

Configure first

Set up propagation before any instrumentation starts, so that nothing caches the wrong default.

Edge

Guard the front door

Decide on purpose whether the public gateway trusts incoming trace headers and baggage. Do not leave it to chance.

Size

Watch header budgets

Know the header size limits of your proxies, servers and queues, and keep your total propagation data well below them.

Review

Audit regularly

Once or twice a year, check which formats are actually arriving and retire the ones nobody sends.

A short pre-launch checklist

  • Does every service that talks to a neighbor share at least one format with that neighbor?
  • Is the list the same in every environment, or are differences intended and documented?
  • Is the order identical across services?
  • Is the propagator registered before any instrumentation starts?
  • Has the public edge been configured to handle untrusted headers safely?
  • Are there tests that send a request through at least one old-format service and confirm the trace stays connected?

18Composite Propagators in Different Languages and Agents

The idea is the same everywhere, but each language and tool has its own way of naming and building it. Knowing the general shape helps you find the right page of the documentation quickly.

OpenTelemetry has official SDKs for many programming languages. They all follow the same specification, so a composite behaves the same way in all of them: write every member’s headers on the way out, read every member’s headers on the way in, and report the combined list of fields. What differs is the spelling of the names and the place where you plug the composite in.

Java

Composite option on the text map propagator

Java offers a way to combine several text map propagators into one, and a container for propagators that is given to the SDK builder. The Java agent also reads a system property equivalent to the standard environment variable.

Python

Composite Propagator class

The Python SDK has a class with this name that takes a list of propagators. It is registered through a global setter. The SDK also builds one automatically from the environment variable.

JavaScript

Composite Propagator in the core package

In JavaScript and TypeScript, the core package offers a class with this name. Additional formats such as B3 and Jaeger come as separate packages that you install and add to the list.

Go

Composite text map propagator constructor

Go offers a constructor that accepts several propagators and returns one. You register the result globally, and you can also read the list from the environment through the autoconfiguration helpers.

.NET

Composite text map propagator

The .NET libraries provide a composite type that takes an enumerable of propagators, and the default propagator in the SDK is built the same way from trace context and baggage.

Agents

Zero-code auto-instrumentation

Agents and auto-instrumentation tools usually read the standard setting and build the composite for you. You change the list by changing one setting and restarting the program.

What stays the same

  • The composite has no format of its own. It only forwards calls to its members.
  • Extraction runs members in the order given, so later members can override earlier ones.
  • The fields list is the combined set of all members’ fields.
  • The composite never throws on malformed headers. Bad input is simply ignored.

What differs

  • Which formats come built in. The W3C formats are almost always included. B3, Jaeger, X-Ray and others are often add-on packages, and their names and maintenance status vary between languages.
  • How automatically the list is built. Some SDKs read the environment variable by default. Others need you to opt in to a configuration helper.
  • Global versus per-component settings. Most SDKs use a single global propagator. A few libraries allow a per-client override, which can be handy for one special outgoing call but easy to forget about later.
i
Tip for polyglot companies

If your company writes services in several languages, test the same pair of services in every language combination, for example Java calling Python and Go calling Java. Small differences in how headers are parsed, such as case sensitivity or the handling of whitespace, can show up only in certain combinations.

Analogy

Think of a world-wide chain of hotels that all follow the same rule book: “Every front desk must greet guests in every language on the sign.” Each country’s hotel puts the sign up in its own way, with its own words for “welcome.” The behavior is the same, the wording differs. The OpenTelemetry specification is the rule book, and each language’s SDK is a hotel in a different country.

19The Composite Propagator and Its Neighbors

Several other OpenTelemetry ideas sit close to propagation and are often confused with it. Putting them side by side removes the confusion.

In-band and out-of-band data

There are two very different roads that telemetry takes. Propagation travels in-band: it rides along inside the very requests and messages that your services exchange. Exporting travels out-of-band: each service separately sends its recorded spans to a collector or backend over its own connection. The two meet at the monitoring backend, which uses the shared trace ID that propagation distributed to join the spans that exporting delivered.

graph TD
  A["Service A"] -->|"in-band: request with propagated headers"| B["Service B"]
  A -->|"out-of-band: exports spans"| C["Collector"]
  B -->|"out-of-band: exports spans"| C
  C --> D["Tracing Backend"]
  D --> E["One joined trace using the shared trace ID"]
    
Figure 4: Propagation carries identity in-band, exporting delivers spans out-of-band, and the backend joins them.

This picture explains why a propagation problem looks the way it does. The spans are still recorded and exported correctly, so the backend is full of data, but the shared identity was lost along the way, so the backend cannot join them. The data is there. The thread is not.

Comparing the neighbors

ConceptWhat it doesHow it relates to propagation
Composite PropagatorCombines several propagators into oneThe subject of this guide
SamplerDecides whether a trace is recordedThe decision is carried to other services in the sampled flag of the trace context, so propagation and sampling cooperate
Span linkConnects one span to another without a parent and child relationshipUsed when context arrives from an untrusted source or from batch and queue situations
ResourceDescribes who produced the telemetryStays with the producer and is not propagated to other services
ExporterSends recorded telemetry to a destinationOut-of-band, separate from propagation
tracestateLets vendors add notes inside the W3C trace contextPart of one format, whereas the composite combines different formats
BaggageCarries user-chosen key and value pairsA separate concern that is usually combined with trace identity in the composite

Sampling and propagation: a special relationship

Recording every trace in a busy system is too expensive, so systems record only a share of them. The decision is usually made once, at the start of the trace, by a sampler in the first service. All other services should follow the same decision, otherwise a trace would be recorded in some services and missing in others. The decision travels with the propagated context, in the sampled flag of the W3C trace context or the equivalent field in other formats.

This has a consequence for composites. When a composite translates between formats, the sampled decision must be translated too. Well-written propagators do this correctly. But if a legacy format lacks a sampling field, or a legacy service ignores it, then the sampling decision may be lost, and a downstream service may decide differently. This is another reason to test end-to-end paths that cross format boundaries.

Analogy

Imagine a school trip where the teacher decides at the first stop whether a photographer will take pictures. The decision is written on the group’s badge so that every guide along the way knows whether to take pictures too. If one guide uses a badge in another language and misreads the note, that guide might take pictures that the teacher did not want, or miss the ones that were wanted. The composite is the badge-translator, and it has to translate that note as carefully as the names.

i
Remember

The Composite Propagator does not decide what to record, where to send data, or how to describe a service. Its single job is to carry identity and context across boundaries. Keeping that single job in mind makes it much easier to know where to look when something goes wrong.

20Security, Privacy and Cost

Propagation headers look harmless, but they travel through every system your request touches. A little care here prevents leaks, abuse and surprise bills.

Security

Trace headers are input from the outside world, and input from the outside world should never be fully trusted. There are a few specific risks to know about.

  • Forced sampling. The W3C trace flags include a “sampled” bit. An outsider who sets this bit on every request might make your system record many more traces than you planned, driving up your monitoring costs. Your public edge should decide for itself whether to honor the incoming sampling decision.
  • Trace linking and confusion. An attacker can send a request with a chosen trace ID, trying to mix their requests into a real trace or to confuse investigations. Starting a fresh trace for untrusted callers, and recording the incoming identity as a link only, keeps your data clean.
  • Header flooding. Extremely large baggage or oversized headers can be used to waste memory and bandwidth. Set strict limits at the edge and let your proxies reject oversized headers.
  • Malformed input. A good propagator must ignore bad headers gracefully. This is why custom propagators should never throw errors when parsing, and why you should keep your propagation libraries up to date.
  • Information disclosure. If your internal services send trace headers or baggage to outside partners, those partners learn identifiers from your system. Consider stripping them on calls that leave your organization.

Privacy

The trace ID and span ID are random numbers and carry no personal data. Baggage, however, is free-form text. If a developer puts an email address, an account number or a user name into baggage, it spreads to every service, every proxy log and every partner the request reaches. The safest rule is to keep baggage free from personal data and to review every key before it is allowed.

Cost

The cost of a composite propagator is small per call, but calls add up. Every member writes extra header bytes, and those bytes travel on every request and every message.

+1
to several extra headers per member in the list
Every
call and every message carries them
8 KB
a common order of magnitude for total header limits in many servers

As a simple example, imagine a W3C trace parent header of about 55 characters, a B3 header of about 50 characters, and a small baggage header of 100 characters. Together that is about 200 bytes extra per call. At ten thousand calls per second, that is about two megabytes per second of header overhead, which is small but not zero. If the list grows to six formats and baggage grows to a kilobyte, the overhead can become a measurable part of network usage, and you may hit header size limits in proxies and servers.

i
Tip

The cheapest saving is to remove formats you do not need. The second cheapest is to keep baggage small. Both are settings changes, not code rewrites.

21Propagation in Messaging and Asynchronous Systems

Web calls are the easy case. Many real systems also pass work through queues, topics and schedulers. The composite works there too, but there are extra points to watch.

When a service puts a message on a queue, the work is picked up by a consumer later, maybe seconds or hours afterward, maybe on a different machine. The context cannot travel through the network connection, because there is none. Instead, the producer injects the context into the message’s metadata, and the consumer extracts it when the message is read. The carrier is the set of message headers or message attributes, depending on the queue product.

Analogy

Sending a request over the web is like making a phone call: both sides are connected at the same moment. Sending a message through a queue is like posting a letter. The sender writes the trace details on the envelope, drops it into the post box and goes home. Hours later, a different person opens the envelope and reads the details. The Composite Propagator is the clerk who writes on the envelope in every language that the possible readers might understand.

Things that differ from web calls

  • Limited metadata space. Some queue products limit how many separate attributes or headers a message can carry. For example, some cloud queue services allow only a small, fixed number of message attributes, such as ten, and your own application attributes share that budget. Since each propagation format may consume one or more attributes, a long composite can use up a large part of the budget. Check the limits of your queue before adding members.
  • Time gaps. The consumer may run long after the producer finished. The consumer’s span is often better described as a link to the producer’s span rather than a direct child, because they are not part of the same live call.
  • Many consumers. One message may fan out to many consumers. Each of them extracts the same context and starts its own span, which creates a wide tree.
  • Batching. A consumer that reads a batch of messages may handle messages from many different traces in one go. It should extract from each message individually and link to each producer.
  • Retries and dead-letter queues. A message may be delivered more than once. Each delivery carries the same original context, which is usually what you want.

Carriers that only allow certain characters

Some carriers restrict the characters that may appear in names and values, or treat header names as case-insensitive. A well-written getter and setter hide these differences from the propagator. If you ever write your own getter or setter for a new kind of carrier, remember to handle case-insensitive lookups and to return an empty result rather than failing when a key is missing.

Illustration: an order pipeline

A web service accepts an order and places a message on a queue. A worker service takes the message, charges the card, and puts another message on a second queue for the shipping service. The web service and the worker both use a composite of W3C Trace Context and W3C Baggage. The shipping service was built long ago and understands only B3. By adding B3 to the composite used by the worker, the shipping service can join the same trace. Since the queue allows only a handful of message attributes, the team measured the attribute budget first and confirmed that three propagation formats still left room for the application’s own attributes. This is an invented example to show the thinking.

22Testing and Validating Your Propagation Setup

A broken propagator rarely causes an error. It causes missing connections, which you only notice when you look for them. That is why deliberate testing is so valuable.

Test the composite as a unit

Build the composite exactly as production does, then run two simple checks in a test. First, inject a known context into an empty carrier and confirm that the carrier now holds the headers of every format on the list. Second, extract from a carrier that contains headers of just one format and confirm that the resulting context holds the expected trace ID and span ID. Repeat for each format. These tests are fast and catch typos in the configuration, a missing package and wrong ordering.

Test a conflict on purpose

Create a carrier with two formats that disagree, and check which one wins. Write down the result as a test. If anyone later changes the order of the list, the test will fail and point out the change in behavior, which prevents silent surprises.

Test a malformed header

Feed the composite headers that are empty, truncated, have the wrong length, or contain unexpected characters. The correct behavior is that the composite never throws an error, ignores the bad input, and either falls back to another format or leaves the context empty so a new trace begins.

Test across real services

Unit tests prove the propagator works. Only an end-to-end test proves that the real services, proxies and queues pass the headers along. In a staging environment, send a request with a recognizable trace ID through every kind of path in your system: web to web, web to queue to worker, gateway to legacy service. Then search your tracing backend for that trace ID and confirm that spans from every service appear in one tree. If a hop is missing, you have found a break before your customers did.

Check 1

Round trip per format

Each format on the list can be written and then read back, giving the same trace ID and span ID.

Check 2

Conflict result is known

When two formats disagree, the winner is the one you intended, and a test pins the behavior.

Check 3

Bad input is harmless

Broken headers never cause errors and never stop a request from being handled.

Check 4

End-to-end connectivity

A request through old and new services appears as one complete trace in the backend.

Check 5

Header size within limits

The total size of propagation headers stays well below the limits of every proxy and server on the path.

Check 6

Edge behavior

Untrusted incoming headers are handled by the policy you chose, not by accident.

23Monitoring and Troubleshooting Broken Traces

Sooner or later you will open a trace and find that it stops halfway, or that two traces should have been one. This chapter gives you a calm way to find the cause.

Signs of a propagation problem

  • Many traces consist of a single span from one service, with no children.
  • A service shows up as the root of many traces even though it is never the entry point.
  • A request that clearly crosses a boundary appears as two unconnected traces, one on each side.
  • Traces break only on certain paths, such as the path through one particular proxy or queue.

A troubleshooting walk-through

Suppose the trace breaks between service A and service B. Work from the sender to the receiver, one hop at a time.

1

Did A inject at all?

Look at the outgoing request from A, using a debugging proxy, logs or a test endpoint that echoes headers. Confirm that the expected propagation headers are present. If none are, A’s instrumentation is not using the propagator, or the propagator is set to none.

2

Which formats did A write?

Compare the formats present with the list you expect. A missing format usually means the composite was built without it, or its package is not installed.

3

Did something in between remove or rewrite headers?

Proxies, gateways, load balancers and security tools sometimes strip unknown headers or replace trace headers with their own. Check each hop’s configuration.

4

Did B receive the headers?

Log the incoming headers at B in a test environment. If they arrive intact, the problem is on B’s reading side. If they do not arrive, the problem is in between.

5

Can B read at least one of the formats?

Check B’s propagator list against the formats that arrived. If B only reads a format that A did not write, the trace will break. This is the exact situation that the composite exists to prevent.

6

Was the propagator registered early enough?

Confirm that the propagator was set before B’s framework instrumentation started. If instrumentation cached an older default, B might ignore the headers even though its settings look right.

7

Are the values valid?

Make sure the identifiers have the right length and character set, and that all-zero identifiers are not being sent. Invalid values are ignored by propagators on purpose, so they look the same as missing values.

Measuring propagation health over time

You can turn troubleshooting into monitoring. Many tracing backends let you count how many traces have only one span, or how many spans have no parent even though their service is never an entry point. A sudden rise in these numbers after a deployment is a strong hint that a propagation change broke something. Add this check to your release routine, and watch it for a day after any change to the propagator list.

i
Tip

Create a tiny test endpoint in a non-production environment that simply echoes back the propagation headers it received. It turns the confusing question “what did the proxy do to my headers?” into a one-minute experiment.

24Frequently Asked Questions

Short, direct answers to the questions beginners ask most often.

Q1What is a Composite Propagator in one sentence?
It is a propagator that wraps a list of other propagators, so that injecting and extracting context work with several formats at once while the rest of your code sees only one propagator.
Q2Do I need to create a composite myself?
Often you do not. If you list several propagators in the standard OTEL_PROPAGATORS setting, or rely on the default of trace context plus baggage, the SDK builds a composite for you.
Q3What is the default propagator in OpenTelemetry?
SDKs normally default to a composite of W3C Trace Context and W3C Baggage. The bare API alone uses a no-operation propagator that does nothing, so a real SDK or agent is needed for propagation to occur.
Q4What happens during inject?
The composite asks every member in turn to write its headers into the carrier. After the loop, the carrier contains the trace information in every format on the list.
Q5What happens during extract?
The composite runs each member in order, passing the context from one to the next. Members that find valid headers of their format add them to the context. Members that find nothing leave it unchanged.
Q6If two formats disagree, which one wins?
Typically the member that comes later in the list and finds valid data overwrites what earlier members found. Implementations can differ in details, so check your SDK’s documentation and add a test that pins the behavior.
Q7Does the order matter when injecting?
Hardly. Each member writes its own separate headers, so the sending order does not change what is sent. Order matters mainly for extraction.
Q8Why would I need more than W3C Trace Context?
Because services or products around you may only understand an older or vendor format, such as B3, Jaeger or X-Ray. If you do not write or read those formats, traces break where you meet them.
Q9Is the baggage propagator part of the composite?
In the default setup, yes. Baggage is a separate concern from trace identity, and its propagator is a separate member. The two do not conflict because they store different things.
Q10Does baggage appear on my spans automatically?
No. Baggage lives in the context. To see a baggage value as a span attribute, you must copy it deliberately, for example with a span processor or by adding code in your instrumentation.
Q11How many formats should I include?
As few as possible. Include a format only if you can name a service or product that needs it. Each extra format adds header bytes and the chance of conflicts.
Q12Can I write my own propagator and add it?
Yes. Implement inject, extract and fields for your format, make sure it never throws on bad input, and add it as a member of the composite like any other.
Q13Is a composite thread-safe?
Propagators are designed to be stateless, so a composite can be shared safely across the whole application. Create it once at start-up and register it globally.
Q14Does a composite fix a legacy service that drops headers?
No. If a service discards the headers it receives and does not forward them, the trace will break there whatever you do on your side. In such cases you may need to link traces or fix the service.
Q15Should my public gateway trust incoming trace headers?
Be careful. Untrusted callers can force sampling, send huge baggage or inject confusing identifiers. Many teams start a fresh trace at the public edge and record the incoming identity as a link only.
Q16Where do I set the propagators in my program?
As early as possible during start-up, before any instrumentation initializes. The simplest way is the environment variable, which is read by the SDK or agent before your application code runs.
Q17How do I know when I can remove an old format?
Measure it. Count incoming requests that carry only the old headers. When the count stays at zero for a long enough period, and no vendor product depends on it, remove the member and watch for broken traces.

25Summary and Key Takeaways

You started with an interpreter at an international meeting and ended with a full picture of how a Composite Propagator keeps traces whole across old and new systems. Here is everything that matters in one place.

A distributed trace is only complete if every service passes the identity of the journey to the next one. That passing is done by propagators, each of which speaks one format. Real companies speak many formats at once, because systems grow over years. The Composite Propagator wraps several propagators into a single one, writing every format on the way out and reading every format on the way in. Used with care, with a small list, a consistent order, early configuration and a thoughtful public edge, it keeps your traces connected and your migrations calm.

Key Takeaways

  • What it is: a propagator made of propagators, which injects and extracts with several formats while the rest of your code sees only one.
  • Why it exists: mixed fleets, migrations, cloud load balancers, service meshes and vendor products often speak different formats, and a single format breaks traces at the seams.
  • How it works: inject loops through all members and writes every format. Extract loops through all members in order, passing the context along, so the last member with valid data usually wins.
  • Defaults and configuration: the default is trace context plus baggage, and the standard OTEL_PROPAGATORS setting builds a composite for you without code changes.
  • Keep it small and consistent: include only formats you can justify, keep the same order everywhere, and register the propagator before any instrumentation starts.
  • Protect the edge and the baggage: do not trust untrusted headers blindly, and never put personal or secret data into baggage.
  • Test and watch: prove round trips and conflicts in tests, verify end-to-end traces in staging, and monitor for single-span traces after every change.