Skip to content

Conversation

zhiyuanliang-ms
Copy link
Member

@zhiyuanliang-ms zhiyuanliang-ms commented Apr 11, 2025

Why this PR?

OpenTelemetry tracing in .NET is built on Activity.

Only Activity from ActivitySource which is registered to TracerProvider by AddSource method will be traced by OpenTelemetry. (Implementation details: TracerProviderSdk OTEL will create activity listener to trace activities.)

This PR adds an ActivitySource with name called "Microsoft.Extensions.Configuration.AzureAppConfiguration" to allow OpenTelemetry to collect tracing for the Load and Refresh activities.

Bug fixes

Activity created by the .NET SDK(ConfigurationClient) cannot be traced because of the request tracing in .NET provider which is built on "CustomDiagnosticsHeaders" activity.

Below are the implementation details of the request tracing in .NET SDK and .NET provider:
Request tracing hook: SDK, provider

The .NET provider will start a new Activity with no source/listener. This activity will have ActivityTraceFlags set to ActivityTraceFlags.None. According to doc, this kind of activity will not be traced. And the activity created by the ConfigurationClient from SDK will be the child activity of the activity we created and it will inherit the ActivityTraceFlags which will cause the child activity not to be traced.

The below code demostrates the issue in a simplified way:

using System.Diagnostics;
using OpenTelemetry.Trace;

var builder = Host.CreateApplicationBuilder(args);

List<Activity> exportedActivities = new();
var activitySource = new ActivitySource("TestSource"); // mocked activity source of ConfigurationClient

builder.Services.AddOpenTelemetry()
    .WithTracing(builder =>
    {
        builder.AddInMemoryExporter(exportedActivities)
            .AddSource(["TestSource"]); // OTEL sdk will create an ActivityListener to listen to the registered ActivitySource
    });


using (var host = builder.Build())
{
    host.Start();
    var activityWithoutSource = new Activity("mocked activity started by .NET provider for request tracing");
    activityWithoutSource.Start();
    Console.WriteLine(Activity.Current?.DisplayName);
    Console.WriteLine(Activity.Current?.ActivityTraceFlags);
    //Activity.Current.ActivityTraceFlags = ActivityTraceFlags.Recorded; // if we manually set the flag to Recorded, then the activity below will then be catched by OTEL

    var testActivity = activitySource.StartActivity("mocked activity created by ConfigurationClient");
    Console.WriteLine(Activity.Current?.DisplayName);
    Console.WriteLine(Activity.Current?.ActivityTraceFlags); // the child activity will inherit the ActivityTraceFlags from its parent activity
    testActivity?.Stop();

    activityWithoutSource.Stop();

    Console.WriteLine(exportedActivities.Count());
    if (exportedActivities.Count() > 0)
    {
        Console.WriteLine(exportedActivities[0].OperationName);
    }
}

Verify that request tracing can work with the change in this PR

Currently, we are lacking testcases about request tracing in this repo.

The following code can be used to verify that the request tracing won't be effected. (OpenTelemetry.Extensions.Hosting and OpenTelemetry.Exporter.InMemory packages are required)

using Azure.Data.AppConfiguration;
using System.Diagnostics;
using OpenTelemetry.Trace;

var builder = Host.CreateApplicationBuilder(args);

var client = new ConfigurationClient("<connection-string>");
List<Activity> exportedActivities = new();

builder.Services.AddOpenTelemetry()
    .WithTracing(builder =>
    {
        builder.AddInMemoryExporter(exportedActivities)
            //.AddSource([$"{typeof(ConfigurationClient).Namespace}.*", "ConfigurationProvider"]);
            .AddSource(["ConfigurationProvider"]);
        //.AddSource([$"{typeof(ConfigurationClient).Namespace}.*"]);
    });

var activitySource = new ActivitySource("ConfigurationProvider");

using (var host = builder.Build())
{
    host.Start();

    using (var refreshActivity = activitySource.StartActivity("Refresh"))
    {
        Console.WriteLine($"refreshActivity is null: {refreshActivity == null}");

        var requestTracingActivity = new Activity("Azure.CustomDiagnosticHeaders");
        requestTracingActivity.Start();
        client.GetConfigurationSetting("test"); // the SDK has its own activity source: "Azure.Data.AppConfiguration.ConfigurationClient"
                                                // Add breakpoint in the SDK's CustomHeadersPolicy.cs file can verify that the "CustomDiagnosticHeaders" activity will be catched.
        requestTracingActivity.Stop();
    }

    Console.WriteLine(exportedActivities.Count());
    if (exportedActivities.Count() > 0)
    {
        Console.WriteLine(exportedActivities[0].OperationName);
    }
}

@zhiyuanliang-ms zhiyuanliang-ms force-pushed the zhiyuanliang/activity-source branch from 8d81c4c to 934f825 Compare April 16, 2025 03:28
@zhiyuanliang-ms zhiyuanliang-ms changed the base branch from zhiyuanliang/health-check to main April 16, 2025 03:28
@zhiyuanliang-ms zhiyuanliang-ms force-pushed the zhiyuanliang/activity-source branch from 934f825 to 66bd907 Compare April 16, 2025 03:32
@zhiyuanliang-ms zhiyuanliang-ms merged commit bf8b06b into main Apr 24, 2025
3 checks passed
@zhiyuanliang-ms zhiyuanliang-ms deleted the zhiyuanliang/activity-source branch April 24, 2025 03:00
This was referenced Aug 1, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants