Skip to content

OpenTelemetryChatClient streaming sets Activity.Current = null when ActivitySource has no listeners #7442

@mKiddeT

Description

@mKiddeT

Description

OpenTelemetryChatClient.GetStreamingResponseAsync contains the following workaround for dotnet/runtime#47802:

using Activity? activity = CreateAndConfigureActivity(options);

// ... streaming ...

Activity.Current = activity; // workaround for https://github.com/dotnet/runtime/issues/47802

When activity is null, this actively sets Activity.Current = null after each yield return, destroying trace context for all downstream middleware in the streaming pipeline.


Root Cause

The workaround sets Activity.Current unconditionally. However, CreateAndConfigureActivity only creates an Activity when _activitySource.HasListeners() is true:

private Activity? CreateAndConfigureActivity(ChatOptions? options)
{
    Activity? activity = null;
    if (_activitySource.HasListeners())
    {
        // activity is only set if there are listeners
    }
    return activity;
}

This means in any environment where no listener is attached to the ActivitySource, activity is always null, and the workaround unconditionally sets Activity.Current = null on every yield return throughout the entire stream — regardless of what Activity.Current was before entering the middleware.


Relation to Existing Issues

This is the same class of bug reported and fixed for FunctionInvokingChatClient in #7320 / #7321. The fix in PR #7321 was not applied to OpenTelemetryChatClient.cs.


Additional Notes

Specifying a sourceName masks the bug
The bug can be masked by specifying a sourceName that has active listeners, causing HasListeners() to return true and activity to be non-null:

new ChatClientBuilder(inner)
    .UseOpenTelemetry(sourceName: "my-source") // masks the bug if "my-source" has listeners
    .Build();

This makes the bug environment-dependent and difficult to diagnose, as it may not reproduce in environments where OpenTelemetry is fully configured with a matching source name, but will silently destroy trace context in environments where it is not.

Downstream middleware impact
Any middleware registered after OpenTelemetryChatClient in the pipeline that relies on Activity.Current for logging or tracing will silently lose trace context on every streaming response. This includes scenarios such as:

  • Completion logging middleware reading Activity.Current?.DisplayName for operation names
  • Agent tool call chains where trace context must be preserved across multiple streaming round-trips

Proposed Fix

The workaround should be guarded to only restore Activity.Current when an activity was actually started:

// Before — unconditionally sets Activity.Current = null when activity is null
Activity.Current = activity; // workaround for https://github.com/dotnet/runtime/issues/47802

// After — only restores when an activity was actually started
if (activity is not null)
{
    Activity.Current = activity; // workaround for https://github.com/dotnet/runtime/issues/47802
}

This ensures Activity.Current is never actively destroyed, regardless of listener configuration or source name.


Expected Behavior

Activity.Current should be preserved across yield points in GetStreamingResponseAsync regardless of whether _activitySource.HasListeners() is true or false, consistent with the fix applied to FunctionInvokingChatClient in #7321.


Actual Behavior

In any streaming scenario where _activitySource.HasListeners() is false, Activity.Current is set to null after each yield return, causing all downstream middleware to lose trace context for the remainder of the stream.


Reproduction Steps

  1. Register OpenTelemetryChatClient in a streaming pipeline without specifying a sourceName, or with a sourceName that has no listeners attached
  2. Set Activity.Current to a known activity before entering the pipeline
  3. Stream a response via GetStreamingResponseAsync
  4. Observe that Activity.Current is null after the first yield return

Environment

  • Microsoft.Extensions.AI 10.4.1
  • .NET 8.0

References

Metadata

Metadata

Labels

area-aiMicrosoft.Extensions.AI libraries

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions