Azure / Azure/azure-functions-host
W3C trace context not propagated for session-enabled Service Bus triggers (OpenTelemetry)
- Dominant language
- C#
- Stars
- 2k
- Forks
- 482
- Avg merge
- 2d 10h
- Merged PRs (30d)
- 36
Description
# W3C trace context not propagated for session-enabled Service Bus triggers (OpenTelemetry)
## Description
When using OpenTelemetry (`"telemetryMode": "OpenTelemetry"` in host.json) with session-enabled Service Bus triggers (`IsSessionsEnabled = true`), the W3C trace context (`traceparent`/`tracestate`) embedded in the Service Bus message is **not** used as the parent trace. Instead, the Functions host creates a new root activity, breaking distributed tracing across process boundaries.
Non-session Service Bus triggers propagate trace context correctly. The issue is specific to session-enabled triggers.
## Root Cause
The Functions host subscribes to a hardcoded list of ActivitySource names in [`OpenTelemetryConfigurationExtensions.ConfigureTracing()`](https://github.com/Azure/azure-functions-host/blob/dev/src/WebJobs.Script/Diagnostics/OpenTelemetry/OpenTelemetryConfigurationExtensions.cs):
```csharp
ReadOnlySpan sources =
[
OpenTelemetryConstants.ActivitySourceNames.ServiceBusProcessor, // "Azure.Messaging.ServiceBus.ServiceBusProcessor"
OpenTelemetryConstants.ActivitySourceNames.EventHubsProcessor,
// ...
];
```
The list includes `"Azure.Messaging.ServiceBus.ServiceBusProcessor"` but is **missing** `"Azure.Messaging.ServiceBus.ServiceBusSessionProcessor"`.
The Azure.Messaging.ServiceBus SDK's `DiagnosticScopeFactory.GetActivitySource()` ([source](https://github.com/Azure/azure-sdk-for-net/blob/main/sdk/core/Azure.Core/src/Shared/DiagnosticScopeFactory.cs)) constructs ActivitySource names dynamically from the operation name:
```csharp
string clientName = ns + "." + ((indexOfDot < 0) ? name : name.Substring(0, indexOfDot));
return ActivitySources.GetOrAdd(clientName, static n => new ActivitySource(n));
```
Combined with `DiagnosticProperty.cs` ([source](https://github.com/Azure/azure-sdk-for-net/blob/main/sdk/servicebus/Azure.Messaging.ServiceBus/src/Diagnostics/DiagnosticProperty.cs)):
| Trigger type | Activity name | Resulting ActivitySource name | Host subscribed? |
|---|---|---|---|
| Non-session | `ServiceBusProcessor.ProcessMessage` | `Azure.Messaging.ServiceBus.ServiceBusProcessor` | **Yes** |
| Session | `ServiceBusSessionProcessor.ProcessSessionMessage` | `Azure.Messaging.ServiceBus.ServiceBusSessionProcessor` | **No** |
Because the host has no listener for the session ActivitySource:
1. The SDK's `DiagnosticScope.Start()` calls `ActivitySource.StartActivity()` which returns `null` (no listeners)
2. `Activity.Current` remains `null` when the function is invoked
3. `ActivitySourceWrapper` ([source](https://github.com/Azure/azure-functions-host/blob/dev/src/WebJobs.Script/Diagnostics/OpenTelemetry/ActivitySourceWrapper.cs)) sees `Activity.Current == null` and creates a **root** activity → new OperationId/trace
## Reproduction
1. Create two Azure Functions (isolated worker, .NET):
- **Function A** (HTTP trigger): Sends a message to a session-enabled Service Bus queue
- **Function B** (Service Bus trigger with `IsSessionsEnabled = true`): Receives and processes the message
2. Configure `host.json` with `"telemetryMode": "OpenTelemetry"`
3. Configure OpenTelemetry with Azure Monitor exporter in both functions
4. Send a request to Function A
5. Observe in Application Insights that Function A and Function B have **different** Operation IDs
If you repeat the same test with sessions disabled (`IsSessionsEnabled = false`), Function A and Function B share the **same** Operation Id.
## Expected Behavior
Session-enabled Service Bus triggers should propagate the W3C trace context from the message, maintaining the same Operation Id across the distributed trace — identical to non-session triggers.
## Proposed Fix
Add the missing ActivitySource name to `OpenTelemetryConstants.cs`:
```csharp
internal static class ActivitySourceNames
{
internal const string WebJobs = "Microsoft.Azure.WebJobs";
internal const string Host = "Microsoft.Azure.Functions.Host";
internal const string ServiceBusProcessor = "Azure.Messaging.ServiceBus.ServiceBusProcessor";
+ internal const string ServiceBusSessionProcessor = "Azure.Messaging.ServiceBus.ServiceBusSessionProcessor";
internal const string EventHubsProcessor = "Azure.Messaging.EventHubs.EventProcessor";
// ...
}
```
And subscribe to it in `OpenTelemetryConfigurationExtensions.ConfigureTracing()`:
```csharp
ReadOnlySpan sources =
[
OpenTelemetryConstants.ActivitySourceNames.ServiceBusProcessor,
+ OpenTelemetryConstants.ActivitySourceNames.ServiceBusSessionProcessor,
OpenTelemetryConstants.ActivitySourceNames.EventHubsProcessor,
// ...
];
```
## Environment
- **Azure Functions Host**: 4.x (confirmed against `dev` branch as of 2025-07-15)
- **Azure.Messaging.ServiceBus**: 7.20.1
- **Microsoft.Azure.Functions.Worker.Extensions.ServiceBus**: 5.24.0
- **Microsoft.Azure.Functions.Worker.OpenTelemetry**: 1.1.0
- **.NET**: 10
- **Worker model**: Isolated
Contributor guide
Assessment
This issue has not been assessed yet.