Coal Chris
Coal Chris

Reputation: 51

.NET OpenTelemetry Not Adding Listener For ActivitySource

Why is OpenTelemetry .NET not adding a listener for my ActivitySource in a hosted service on generic host?

I have some configurations to pass into OpenTelemetry set-up, so I depend on a class MyClass. This means registering that class, and then configuring a callback action for TracerProviderBuilder which uses that class. However, when I later create an ActivitySource and start an Activity with ActivitySource.StartActivity(...) then it returns null because no listeners were attached to the source. By debugging and inspecting the ActivitySource, I could see the list of s_activeSources which included the OpenTelemetry.HttpRequest ActivitySource created by .AddHttpClientInstrumentation() so it's odd that no listener was added for that either.

Note: This is being run in a unit test and failing, whereas it is working in a long-running worker service program. Not sure if unit tests are too short-lived or are influenced by test environment e.g. Visual Studio

Below is a minimal version of my scenario:

Host.CreateDefaultBuilder()
.ConfigureServices((context, services) =>
{
    services
    .AddSingleton<MyClass>()
    .AddOpenTelemetryTracing(hostingBuilder =>
        hostingBuilder
        .Configure(
            (sp, builder) =>
            {
                var myClass = sp.GetRequiredService<MyClass>();
                
                // Do something with myClass on builder
                
                builder
                .AddAspNetInstrumentation()
                .AddHttpClientInstrumentation()
                .AddConsoleExporter()
                .AddSource("TestService*");
            }))
    .AddHostedService<TestService>();
})
.Build();

TestService.cs

internal class TestService : BackgroundService
{
    private static ActivitySource testActivitySource = new ActivitySource("TestService");

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        await Task.Run(() =>
        {
            using (var activity = testActivitySource.StartActivity("TestService", ActivityKind.Server))
            {
                Console.WriteLine("I'm inside the activity using clause!");

                activity?.SetTag("foo", 1);

                Console.WriteLine($"activity.Tags: {activity.Tags}"); // This throws NullReferenceException since activity is null due to no listeners
            }

            Console.WriteLine("I'm outside the activity using clause!");
        });
    }
}

Package versions:

<package id="OpenTelemetry" version="1.3.0" />
<package id="OpenTelemetry.Api" version="1.3.0" />
<package id="OpenTelemetry.Exporter.Console" version="1.3.0" />
<package id="OpenTelemetry.Extensions.Hosting" version="1.0.0-rc9.5" />
<package id="OpenTelemetry.Instrumentation.AspNet" version="1.0.0-rc9.5" />
<package id="OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule" version="1.0.0-rc9.5" />
<package id="OpenTelemetry.Instrumentation.Http" version="1.0.0-rc9.5" />

I've also tried moving the .AddConsoleExporter() line out of the .Configure method and simply called straight away by hostingBuilder but still no listener attached to any ActivitySource.

Upvotes: 5

Views: 3152

Answers (2)

Kenneth Carter
Kenneth Carter

Reputation: 101

I use NUnit to conduct my testing of instrumentation that needs an activity created. I use the following test fixture to rig an activity source listener.

    public abstract class BaseTraceTelemetryTestFixture<TCategory>
    : BaseOptionsTestFixture<TelemetryOptions>
{
    protected readonly ActivityListener _listener;

    protected MockTracer<TCategory>? _tracer;

    protected TraceActivityAccessor? _traceActivityAccessor;

    protected virtual ITraceActivityAccessor? TraceActivityAccessor => _traceActivityAccessor;

    public BaseTraceTelemetryTestFixture()
    {
        _listener = ActivityListnerExtensions.AddActivityListener(listener =>
        {
            listener.ActivityStarted = ActivityStarted;

            listener.ActivityStopped = ActivityStopped;

            listener.ShouldListenTo = ShouldListenTo;

            listener.Sample = Sample;

            listener.SampleUsingParentId = SampleUsingParentId;
        });
    }

    /// <summary>
    /// Activity callback used to listen to the activity started event
    /// </summary>
    /// <param name="activity"></param>
    protected virtual void ActivityStarted(Activity activity)
    {

    }

    /// <summary>
    /// Activity callback used to listen to the activity stopped event
    /// </summary>
    /// <param name="activity"></param>
    protected virtual void ActivityStopped(Activity activity)
    {
        if (_tracer != null)
        {
            _tracer.ResultOfTest = activity;
        }
    }
    /// <summary>
    /// 
    /// </summary>
    /// <param name="source"></param>
    /// <returns></returns>
    protected virtual bool ShouldListenTo(ActivitySource source) => true;

    protected virtual ActivitySamplingResult Sample(ref ActivityCreationOptions<ActivityContext> context)
    {
        return ActivitySamplingResult.AllData;
    }

    protected virtual ActivitySamplingResult SampleUsingParentId(ref ActivityCreationOptions<string> parentId)
    {
        return ActivitySamplingResult.AllData;
    }

    public override void SetUp()
    {
        base.SetUp();

        _traceActivityAccessor = CreateTraceActivityAccessor();

        _tracer = Substitute.ForPartsOf<MockTracer<TCategory>>(_traceActivityAccessor, _options);
    }

    public virtual TraceActivityAccessor CreateTraceActivityAccessor()
    {
        return new TraceActivityAccessor(_options);
    }

    public virtual void TestFixtureTearDown()
    {
        _listener?.Dispose();
    }

    public override void TearDown()
    {
        base.TearDown();
        _traceActivityAccessor = null;
        _tracer = null;
    }
}

Upvotes: 0

Amit Patil
Amit Patil

Reputation: 55

In your unit test project, u have created the builder, but you haven't started the builder. Unless you don't start the Host it will not add the listener. Try using below code snippet.

var builder = Host.CreateDefaultBuilder()
            .ConfigureServices((context, services) =>
            {
                services
                .AddSingleton<MyClass>()
                .AddOpenTelemetryTracing(hostingBuilder =>
                    hostingBuilder
                    .Configure(
                        (sp, builder) =>
                        {
                            var myClass = sp.GetRequiredService<MyClass>();

                            // Do something with myClass on builder

                            builder
                            .AddAspNetInstrumentation()
                            .AddHttpClientInstrumentation()
                            .AddConsoleExporter()
                            .AddSource("TestService*");
                        }))
                .AddHostedService<TestService>();
            }).Build();

        var host = builder.Build();

        await host.StartAsync();

        await host.StopAsync();

        host.Dispose();

Upvotes: 0

Related Questions