From dffeacc6fa1ebe055c11661d38473947cb5b4ad7 Mon Sep 17 00:00:00 2001 From: Ramon Smits Date: Thu, 1 Oct 2026 15:24:40 +0200 Subject: [PATCH 1/3] =?UTF-8?q?=F0=9F=93=9D=20Document=20baggage=20propaga?= =?UTF-8?q?tion=20behavior=20on=20receive?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Describes how NServiceBus applies the baggage header to the process span: only together with NServiceBus trace context, also when a transport SDK receive span is the parent (the Azure Service Bus, RabbitMQ, and Amazon SQS clients do not propagate baggage), without re-adding a key the SDK span already carries, and regardless of whether a new trace is started. Adds usage guidance and clarifies the baggage header on the headers page. --- nservicebus/messaging/headers.md | 6 +++++- .../opentelemetry_traces_core_[10,11).partial.md | 13 +++++++++++++ .../opentelemetry_traces_core_[11,).partial.md | 13 +++++++++++++ 3 files changed, 31 insertions(+), 1 deletion(-) diff --git a/nservicebus/messaging/headers.md b/nservicebus/messaging/headers.md index 24202eecd8e..17ee2ceb00a 100644 --- a/nservicebus/messaging/headers.md +++ b/nservicebus/messaging/headers.md @@ -320,7 +320,11 @@ The headers are: * [`traceparent`](https://www.w3.org/TR/trace-context/#traceparent-header) #end-if * [`tracestate`](https://www.w3.org/TR/trace-context/#tracestate-header) -* [`baggage`](https://www.w3.org/TR/baggage/#baggage-http-header-format) +* [`baggage`](https://www.w3.org/TR/baggage/#baggage-http-header-format) - the baggage of the activity that sent the message + +#if-version [10.3,) +Receivers read `tracestate` and `baggage` only from messages that also carry `NServiceBus.TraceParent` or `traceparent`. The baggage is applied to the process span even when a transport SDK receive span is its parent, because the transport SDKs do not propagate baggage. See [OpenTelemetry](/nservicebus/operations/opentelemetry.md) for details. +#end-if #if-version [10.3,) ### NServiceBus.TraceParent diff --git a/nservicebus/operations/opentelemetry_traces_core_[10,11).partial.md b/nservicebus/operations/opentelemetry_traces_core_[10,11).partial.md index 4917e81cb0e..a393bdd51b5 100644 --- a/nservicebus/operations/opentelemetry_traces_core_[10,11).partial.md +++ b/nservicebus/operations/opentelemetry_traces_core_[10,11).partial.md @@ -253,6 +253,19 @@ Custom `RecoverabilityAction` and `AuditAction` implementations that forward the In version 10, NServiceBus uses a custom propagator. Propagation moves to the built-in .NET `DistributedContextPropagator` when the version 11 behavior is enabled, and the custom propagator is removed in version 11. The `baggage` header format changes with it. See the [version 10 to 11 upgrade guide](/nservicebus/upgrades/10to11/) for the serialization details and for the effect on a rolling upgrade. +#### Baggage + +NServiceBus writes the [baggage](https://www.w3.org/TR/baggage/) of the current activity to the `baggage` header of every message that flows through the outgoing pipeline. On receive, NServiceBus applies the baggage from that header to the process span, so handlers and behaviors can read it with `Activity.Current.GetBaggageItem`. Baggage therefore flows end to end on every transport, including transports whose SDK has no OpenTelemetry instrumentation. + +On receive, the following rules apply: + +- Baggage and `tracestate` are read from the message only when it also carries NServiceBus trace context, that is a `NServiceBus.TraceParent` or `traceparent` header. The W3C specifications define both headers as companions of `traceparent`. A message without a trace header gets a process span that is a child of `Activity.Current`, if any, and inherits the baggage of that activity. The `baggage` header of such a message is ignored. +- When the process span is a child of a transport SDK receive span, as described under Transport SDK spans above, NServiceBus still applies the baggage from the message. The Azure Service Bus, RabbitMQ, and Amazon SQS clients do not propagate baggage, so without this step the baggage would not reach the handlers. Should the SDK receive span already carry a baggage key with the same name, the item from the message is not added again and the value on the SDK span is used. This prevents the same key from being written twice when the message is sent on. +- Baggage follows the message, not the trace. When the receiver starts a new trace, for example for a delayed message or because `StartNewTraceOnReceive` was used, the baggage from the message is still applied to the process span. + +> [!NOTE] +> Baggage is meant for a small number of cross-cutting values, such as a tenant identifier or the identifier of the originating request. Baggage is never removed along a conversation and travels with every message to every receiver, including subscribers, the audit queue, and the error queue. Do not put sensitive or large values in baggage. NServiceBus does not enforce the limits of 64 items and 8,192 bytes defined by the W3C Baggage specification. + ### Failed spans and the error.type tag When a span fails, NServiceBus sets the span status to `Error` and adds an `error.type` tag containing the fully qualified exception type name. This tag is set on the innermost span where the exception was thrown. diff --git a/nservicebus/operations/opentelemetry_traces_core_[11,).partial.md b/nservicebus/operations/opentelemetry_traces_core_[11,).partial.md index 63db509587e..310114cce3b 100644 --- a/nservicebus/operations/opentelemetry_traces_core_[11,).partial.md +++ b/nservicebus/operations/opentelemetry_traces_core_[11,).partial.md @@ -190,6 +190,19 @@ This keeps the forwarded message correlated to its original sender instead of th Custom `RecoverabilityAction` and `AuditAction` implementations that forward the received message get the same behavior: the headers are dispatched as they were received, so the trace stays intact without any additional work. +#### Baggage + +NServiceBus writes the [baggage](https://www.w3.org/TR/baggage/) of the current activity to the `baggage` header of every message that flows through the outgoing pipeline. On receive, NServiceBus applies the baggage from that header to the process span, so handlers and behaviors can read it with `Activity.Current.GetBaggageItem`. Baggage therefore flows end to end on every transport, including transports whose SDK has no OpenTelemetry instrumentation. + +On receive, the following rules apply: + +- Baggage and `tracestate` are read from the message only when it also carries NServiceBus trace context, that is a `NServiceBus.TraceParent` or `traceparent` header. The W3C specifications define both headers as companions of `traceparent`. A message without a trace header gets a process span that is a child of `Activity.Current`, if any, and inherits the baggage of that activity. The `baggage` header of such a message is ignored. +- When the process span is a child of a transport SDK receive span, as described under Transport SDK spans above, NServiceBus still applies the baggage from the message. The Azure Service Bus, RabbitMQ, and Amazon SQS clients do not propagate baggage, so without this step the baggage would not reach the handlers. Should the SDK receive span already carry a baggage key with the same name, the item from the message is not added again and the value on the SDK span is used. This prevents the same key from being written twice when the message is sent on. +- Baggage follows the message, not the trace. When the receiver starts a new trace, for example for a delayed message or because `StartNewTraceOnReceive` was used, the baggage from the message is still applied to the process span. + +> [!NOTE] +> Baggage is meant for a small number of cross-cutting values, such as a tenant identifier or the identifier of the originating request. Baggage is never removed along a conversation and travels with every message to every receiver, including subscribers, the audit queue, and the error queue. Do not put sensitive or large values in baggage. NServiceBus does not enforce the limits of 64 items and 8,192 bytes defined by the W3C Baggage specification. + ### Failed spans and the error.type tag When a span fails, NServiceBus sets the span status to `Error` and adds an `error.type` tag containing the fully qualified exception type name. This tag is set on the innermost span where the exception was thrown. From 59390c63a4fd80e9816c254b81f7491208f9bade Mon Sep 17 00:00:00 2001 From: Ramon Smits Date: Mon, 5 Oct 2026 21:44:00 +0200 Subject: [PATCH 2/3] =?UTF-8?q?=F0=9F=93=9D=20Explain=20that=20NServiceBus?= =?UTF-8?q?=20baggage=20uses=20Activity,=20not=20the=20OpenTelemetry=20Bag?= =?UTF-8?q?gage=20API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Activity.Baggage and OpenTelemetry.Baggage are separate stores. Neither one reads the other. A user who sets baggage through the OpenTelemetry API gets no baggage header on the message, because NServiceBus reads Activity.Baggage. Links the community OpenTelemetry .NET instrumentation reference for the difference between the two APIs. --- .../opentelemetry_traces_core_[10,11).partial.md | 7 +++++++ .../operations/opentelemetry_traces_core_[11,).partial.md | 7 +++++++ 2 files changed, 14 insertions(+) diff --git a/nservicebus/operations/opentelemetry_traces_core_[10,11).partial.md b/nservicebus/operations/opentelemetry_traces_core_[10,11).partial.md index a393bdd51b5..560bd7e3416 100644 --- a/nservicebus/operations/opentelemetry_traces_core_[10,11).partial.md +++ b/nservicebus/operations/opentelemetry_traces_core_[10,11).partial.md @@ -266,6 +266,13 @@ On receive, the following rules apply: > [!NOTE] > Baggage is meant for a small number of cross-cutting values, such as a tenant identifier or the identifier of the originating request. Baggage is never removed along a conversation and travels with every message to every receiver, including subscribers, the audit queue, and the error queue. Do not put sensitive or large values in baggage. NServiceBus does not enforce the limits of 64 items and 8,192 bytes defined by the W3C Baggage specification. +NServiceBus reads and writes baggage through `System.Diagnostics.Activity`. It does not use the `Baggage` API of the `OpenTelemetry.Api` package. The two are separate stores, and neither one reads the other: + +- In handler, behavior, and library code, use `Activity.Current?.SetBaggage(...)` and `Activity.Current?.GetBaggageItem(...)`. NServiceBus writes those items to the `baggage` header of outgoing messages, and applies the header to the process span on receive. +- Baggage set through `Baggage.SetBaggage(...)` from `OpenTelemetry.Api` is not written to outgoing messages. Copy the value onto `Activity.Current` before sending when a message has to carry it. + +The community [OpenTelemetry .NET instrumentation reference](https://github.com/Aaronontheweb/dotnet-skills/blob/master/skills/opentelementry-dotnet-instrumentation/traces-and-propagation-reference.md#net-baggage-api) describes both APIs and when to use each. + ### Failed spans and the error.type tag When a span fails, NServiceBus sets the span status to `Error` and adds an `error.type` tag containing the fully qualified exception type name. This tag is set on the innermost span where the exception was thrown. diff --git a/nservicebus/operations/opentelemetry_traces_core_[11,).partial.md b/nservicebus/operations/opentelemetry_traces_core_[11,).partial.md index 310114cce3b..ed01c0f0112 100644 --- a/nservicebus/operations/opentelemetry_traces_core_[11,).partial.md +++ b/nservicebus/operations/opentelemetry_traces_core_[11,).partial.md @@ -203,6 +203,13 @@ On receive, the following rules apply: > [!NOTE] > Baggage is meant for a small number of cross-cutting values, such as a tenant identifier or the identifier of the originating request. Baggage is never removed along a conversation and travels with every message to every receiver, including subscribers, the audit queue, and the error queue. Do not put sensitive or large values in baggage. NServiceBus does not enforce the limits of 64 items and 8,192 bytes defined by the W3C Baggage specification. +NServiceBus reads and writes baggage through `System.Diagnostics.Activity`. It does not use the `Baggage` API of the `OpenTelemetry.Api` package. The two are separate stores, and neither one reads the other: + +- In handler, behavior, and library code, use `Activity.Current?.SetBaggage(...)` and `Activity.Current?.GetBaggageItem(...)`. NServiceBus writes those items to the `baggage` header of outgoing messages, and applies the header to the process span on receive. +- Baggage set through `Baggage.SetBaggage(...)` from `OpenTelemetry.Api` is not written to outgoing messages. Copy the value onto `Activity.Current` before sending when a message has to carry it. + +The community [OpenTelemetry .NET instrumentation reference](https://github.com/Aaronontheweb/dotnet-skills/blob/master/skills/opentelementry-dotnet-instrumentation/traces-and-propagation-reference.md#net-baggage-api) describes both APIs and when to use each. + ### Failed spans and the error.type tag When a span fails, NServiceBus sets the span status to `Error` and adds an `error.type` tag containing the fully qualified exception type name. This tag is set on the innermost span where the exception was thrown. From 2d3ddb60e48234196d571b015b57204a7e52670b Mon Sep 17 00:00:00 2001 From: Irina Dominte Date: Wed, 7 Oct 2026 15:04:10 +0300 Subject: [PATCH 3/3] Added some improvements --- nservicebus/messaging/headers.md | 2 +- .../opentelemetry_traces_core_[10,11).partial.md | 10 ++++------ .../opentelemetry_traces_core_[11,).partial.md | 8 +++----- 3 files changed, 8 insertions(+), 12 deletions(-) diff --git a/nservicebus/messaging/headers.md b/nservicebus/messaging/headers.md index 17ee2ceb00a..432dd7bc749 100644 --- a/nservicebus/messaging/headers.md +++ b/nservicebus/messaging/headers.md @@ -323,7 +323,7 @@ The headers are: * [`baggage`](https://www.w3.org/TR/baggage/#baggage-http-header-format) - the baggage of the activity that sent the message #if-version [10.3,) -Receivers read `tracestate` and `baggage` only from messages that also carry `NServiceBus.TraceParent` or `traceparent`. The baggage is applied to the process span even when a transport SDK receive span is its parent, because the transport SDKs do not propagate baggage. See [OpenTelemetry](/nservicebus/operations/opentelemetry.md) for details. +Receivers read `tracestate` and `baggage` only from messages that also carry `NServiceBus.TraceParent` or `traceparent`. The baggage is applied to the process span even when a transport SDK receive span is its parent (in version 11, or in version 10 with the version 11 behavior enabled), because the transport SDKs do not propagate baggage. See [OpenTelemetry](/nservicebus/operations/opentelemetry.md) for details. #end-if #if-version [10.3,) diff --git a/nservicebus/operations/opentelemetry_traces_core_[10,11).partial.md b/nservicebus/operations/opentelemetry_traces_core_[10,11).partial.md index 560bd7e3416..4b74699a9b3 100644 --- a/nservicebus/operations/opentelemetry_traces_core_[10,11).partial.md +++ b/nservicebus/operations/opentelemetry_traces_core_[10,11).partial.md @@ -255,24 +255,22 @@ In version 10, NServiceBus uses a custom propagator. Propagation moves to the bu #### Baggage -NServiceBus writes the [baggage](https://www.w3.org/TR/baggage/) of the current activity to the `baggage` header of every message that flows through the outgoing pipeline. On receive, NServiceBus applies the baggage from that header to the process span, so handlers and behaviors can read it with `Activity.Current.GetBaggageItem`. Baggage therefore flows end to end on every transport, including transports whose SDK has no OpenTelemetry instrumentation. +NServiceBus writes the [baggage](https://www.w3.org/TR/baggage/) of the current activity to the `baggage` header of every message that flows through the outgoing pipeline. On receive, NServiceBus applies the baggage from that header to the process span, so handlers and behaviors can read it with `Activity.Current.GetBaggageItem`. Baggage therefore flows end to end on every transport, including transports whose SDK has no OpenTelemetry instrumentation. This requires tracing to be enabled: when nothing listens to the `NServiceBus.Core` activity source, no process span is created and the baggage from the message is not applied. On receive, the following rules apply: - Baggage and `tracestate` are read from the message only when it also carries NServiceBus trace context, that is a `NServiceBus.TraceParent` or `traceparent` header. The W3C specifications define both headers as companions of `traceparent`. A message without a trace header gets a process span that is a child of `Activity.Current`, if any, and inherits the baggage of that activity. The `baggage` header of such a message is ignored. -- When the process span is a child of a transport SDK receive span, as described under Transport SDK spans above, NServiceBus still applies the baggage from the message. The Azure Service Bus, RabbitMQ, and Amazon SQS clients do not propagate baggage, so without this step the baggage would not reach the handlers. Should the SDK receive span already carry a baggage key with the same name, the item from the message is not added again and the value on the SDK span is used. This prevents the same key from being written twice when the message is sent on. -- Baggage follows the message, not the trace. When the receiver starts a new trace, for example for a delayed message or because `StartNewTraceOnReceive` was used, the baggage from the message is still applied to the process span. +- When the version 11 behavior is enabled and the process span is a child of a transport SDK receive span, as described under Transport SDK spans above, NServiceBus still applies the baggage from the message. The Azure Service Bus, RabbitMQ, and Amazon SQS clients do not propagate baggage, so without this step the baggage would not reach the handlers. Should the SDK receive span already carry a baggage key with the same name, the item from the message is not added again and the value on the SDK span is used. This prevents the same key from being written twice when the message is sent on. +- Baggage follows the message, not the trace. When the receiver starts a new trace, for example for a delayed message or because `StartNewTraceOnReceive` was used, the baggage from the message is still applied to the process span. The `tracestate` header is not, because trace state belongs to the trace it was recorded in. > [!NOTE] -> Baggage is meant for a small number of cross-cutting values, such as a tenant identifier or the identifier of the originating request. Baggage is never removed along a conversation and travels with every message to every receiver, including subscribers, the audit queue, and the error queue. Do not put sensitive or large values in baggage. NServiceBus does not enforce the limits of 64 items and 8,192 bytes defined by the W3C Baggage specification. +> Baggage is meant for a small number of cross-cutting values, such as a tenant identifier or the identifier of the originating request. Baggage is never removed along a conversation and travels with every message to every receiver, including subscribers, the audit queue, and the error queue. Do not put sensitive or large values in baggage. NServiceBus does not limit the size of the baggage. The W3C Baggage specification only requires systems to propagate up to 64 list members and 8,192 bytes, so larger baggage may be truncated or dropped by other systems along the way. NServiceBus reads and writes baggage through `System.Diagnostics.Activity`. It does not use the `Baggage` API of the `OpenTelemetry.Api` package. The two are separate stores, and neither one reads the other: - In handler, behavior, and library code, use `Activity.Current?.SetBaggage(...)` and `Activity.Current?.GetBaggageItem(...)`. NServiceBus writes those items to the `baggage` header of outgoing messages, and applies the header to the process span on receive. - Baggage set through `Baggage.SetBaggage(...)` from `OpenTelemetry.Api` is not written to outgoing messages. Copy the value onto `Activity.Current` before sending when a message has to carry it. -The community [OpenTelemetry .NET instrumentation reference](https://github.com/Aaronontheweb/dotnet-skills/blob/master/skills/opentelementry-dotnet-instrumentation/traces-and-propagation-reference.md#net-baggage-api) describes both APIs and when to use each. - ### Failed spans and the error.type tag When a span fails, NServiceBus sets the span status to `Error` and adds an `error.type` tag containing the fully qualified exception type name. This tag is set on the innermost span where the exception was thrown. diff --git a/nservicebus/operations/opentelemetry_traces_core_[11,).partial.md b/nservicebus/operations/opentelemetry_traces_core_[11,).partial.md index ed01c0f0112..94cd65051fb 100644 --- a/nservicebus/operations/opentelemetry_traces_core_[11,).partial.md +++ b/nservicebus/operations/opentelemetry_traces_core_[11,).partial.md @@ -192,24 +192,22 @@ Custom `RecoverabilityAction` and `AuditAction` implementations that forward the #### Baggage -NServiceBus writes the [baggage](https://www.w3.org/TR/baggage/) of the current activity to the `baggage` header of every message that flows through the outgoing pipeline. On receive, NServiceBus applies the baggage from that header to the process span, so handlers and behaviors can read it with `Activity.Current.GetBaggageItem`. Baggage therefore flows end to end on every transport, including transports whose SDK has no OpenTelemetry instrumentation. +NServiceBus writes the [baggage](https://www.w3.org/TR/baggage/) of the current activity to the `baggage` header of every message that flows through the outgoing pipeline. On receive, NServiceBus applies the baggage from that header to the process span, so handlers and behaviors can read it with `Activity.Current.GetBaggageItem`. Baggage therefore flows end to end on every transport, including transports whose SDK has no OpenTelemetry instrumentation. This requires tracing to be enabled: when nothing listens to the `NServiceBus.Core` activity source, no process span is created and the baggage from the message is not applied. On receive, the following rules apply: - Baggage and `tracestate` are read from the message only when it also carries NServiceBus trace context, that is a `NServiceBus.TraceParent` or `traceparent` header. The W3C specifications define both headers as companions of `traceparent`. A message without a trace header gets a process span that is a child of `Activity.Current`, if any, and inherits the baggage of that activity. The `baggage` header of such a message is ignored. - When the process span is a child of a transport SDK receive span, as described under Transport SDK spans above, NServiceBus still applies the baggage from the message. The Azure Service Bus, RabbitMQ, and Amazon SQS clients do not propagate baggage, so without this step the baggage would not reach the handlers. Should the SDK receive span already carry a baggage key with the same name, the item from the message is not added again and the value on the SDK span is used. This prevents the same key from being written twice when the message is sent on. -- Baggage follows the message, not the trace. When the receiver starts a new trace, for example for a delayed message or because `StartNewTraceOnReceive` was used, the baggage from the message is still applied to the process span. +- Baggage follows the message, not the trace. When the receiver starts a new trace, for example for a delayed message or because `StartNewTraceOnReceive` was used, the baggage from the message is still applied to the process span. The `tracestate` header is not, because trace state belongs to the trace it was recorded in. > [!NOTE] -> Baggage is meant for a small number of cross-cutting values, such as a tenant identifier or the identifier of the originating request. Baggage is never removed along a conversation and travels with every message to every receiver, including subscribers, the audit queue, and the error queue. Do not put sensitive or large values in baggage. NServiceBus does not enforce the limits of 64 items and 8,192 bytes defined by the W3C Baggage specification. +> Baggage is meant for a small number of cross-cutting values, such as a tenant identifier or the identifier of the originating request. Baggage is never removed along a conversation and travels with every message to every receiver, including subscribers, the audit queue, and the error queue. Do not put sensitive or large values in baggage. NServiceBus does not limit the size of the baggage. The W3C Baggage specification only requires systems to propagate up to 64 list members and 8,192 bytes, so larger baggage may be truncated or dropped by other systems along the way. NServiceBus reads and writes baggage through `System.Diagnostics.Activity`. It does not use the `Baggage` API of the `OpenTelemetry.Api` package. The two are separate stores, and neither one reads the other: - In handler, behavior, and library code, use `Activity.Current?.SetBaggage(...)` and `Activity.Current?.GetBaggageItem(...)`. NServiceBus writes those items to the `baggage` header of outgoing messages, and applies the header to the process span on receive. - Baggage set through `Baggage.SetBaggage(...)` from `OpenTelemetry.Api` is not written to outgoing messages. Copy the value onto `Activity.Current` before sending when a message has to carry it. -The community [OpenTelemetry .NET instrumentation reference](https://github.com/Aaronontheweb/dotnet-skills/blob/master/skills/opentelementry-dotnet-instrumentation/traces-and-propagation-reference.md#net-baggage-api) describes both APIs and when to use each. - ### Failed spans and the error.type tag When a span fails, NServiceBus sets the span status to `Error` and adds an `error.type` tag containing the fully qualified exception type name. This tag is set on the innermost span where the exception was thrown.