Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Activity is null when using Microsoft Hosting Extensions and net472

I am trying to use OpenTelemetry with my net472 app that uses Microsoft.Extensions.Hosting.

I create my host like this:

Host.CreateDefaultBuilder()
                .ConfigureServices((hostContext, services) =>
                {
                    services.AddOpenTelemetry().WithTracing(tracerProviderBuilder =>
                    {
                        tracerProviderBuilder
                            .SetResourceBuilder(ResourceBuilder.CreateDefault().AddService("MySample"))
                            .AddConsoleExporter()
                            .AddSource(serviceName);
                    }).StartWithHost();
                })
                .Build();

If I then try to create a new activity like this, it is null:

var activitySource = new ActivitySource(serviceName);
using var activity = activitySource.StartActivity("Hello");

If instead I register OpenTelemetry like this, it works just fine:


using var tracerProvider = Sdk.CreateTracerProviderBuilder()
                .SetResourceBuilder(ResourceBuilder.CreateDefault().AddService("MySample"))
                .AddSource(serviceName)
                .AddConsoleExporter()
                .Build();

How can I get an ActivitySource that has the configured listener using the first approach of creating a Host?

like image 433
sergi Avatar asked Sep 05 '25 03:09

sergi


2 Answers

So the AddOpenTelemetry extension in the OpenTelemetry.Extensions.Hosting package doesn't do much. It does (essentially) this:

services.TryAddEnumerable(
   ServiceDescriptor.Singleton<IHostedService, TelemetryHostedService>());

internal sealed class TelemetryHostedService : IHostedService
{
    private readonly IServiceProvider serviceProvider;

    public TelemetryHostedService(IServiceProvider serviceProvider)
    {
        this.serviceProvider = serviceProvider;
    }

    public Task StartAsync(CancellationToken cancellationToken)
    {
        // The sole purpose of this HostedService is to ensure all
        // instrumentations, exporters, etc., are created and started.
        Initialize(this.serviceProvider);

        return Task.CompletedTask;
    }

    public Task StopAsync(CancellationToken cancellationToken)
    {
        return Task.CompletedTask;
    }

    internal static void Initialize(IServiceProvider serviceProvider)
    {
        Debug.Assert(serviceProvider != null, "serviceProvider was null");

        var meterProvider = serviceProvider!.GetService<MeterProvider>();
        if (meterProvider == null)
        {
            HostingExtensionsEventSource.Log.MeterProviderNotRegistered();
        }

        var tracerProvider = serviceProvider!.GetService<TracerProvider>();
        if (tracerProvider == null)
        {
            HostingExtensionsEventSource.Log.TracerProviderNotRegistered();
        }
    }
}

Its sole purpose is to access the TracerProvider and/or MeterProvider to start the OTel SDK once the IServiceProvider is available. It does this by registering an IHostedService which is a special service the .NET hosts (generic or web) know to look for and invoke.

I just replied over on https://github.com/open-telemetry/opentelemetry-dotnet/issues/4276. When I use an actual host on net472, everything works fine.

It is unclear to me why some of the above is not working. A couple examples seem to be calling "Build" without "Run" which won't work. It is during the "Run" phase where IHostedServices are started. @endeffects snippet seems like it should work, but it is missing the registration pieces.

My guess as to what is going on is for these .NET Framework applications no host is being used, only IServiceCollection \ IServiceProvider. In that case, request the providers directly...

var services = new ServiceCollection();
services.AddOpenTelemetr().WithTracing().WithMetrics();

using var serviceProvider = services.BuildServiceProvider();

// Start OTel SDK
var tracerProvider = serviceProvider.GetRequiredService<TracerProvider>();
var meterProvider = serviceProvider.GetRequiredService<MeterProvider>();

Note: In that code you do NOT need to dispose either TracerProvider or MeterProvider as they are accessed through the IServiceProvider and will be disposed when it is disposed.

Worth noting is that there is no requirement to use the IServiceCollection \ IServiceProvider at all on .NET Framework. It is a matter of preference. The OTel .NET SDK ships the Sdk.CreateMeterProviderBuilder & Sdk.CreateTracerProviderBuilder APIs specifically to support .NET Framework (also people who may want multiple providers).

Further reading: https://github.com/open-telemetry/opentelemetry-dotnet/tree/990deee419ab4c1449efd628bed3df57a50963a6/docs/trace/customizing-the-sdk#building-a-tracerprovider

like image 124
Blanch Avatar answered Sep 07 '25 22:09

Blanch


The solution that worked for me is to resolve the TracerProvider using the ServiceCollection. That way the listener gets subscribed and ActivitySource is able to start activities.

This is how I register the services.

Host.CreateDefaultBuilder()
                .ConfigureServices((hostContext, services) =>
                {
                    services.AddSingleton(new ActivitySource(serviceName));
                    services.AddOpenTelemetry().WithTracing(tracerProviderBuilder =>
                    {
                        tracerProviderBuilder
                            .SetResourceBuilder(ResourceBuilder.CreateDefault().AddService("MySample"))
                            .AddConsoleExporter()
                            .AddSource(serviceName);
                    });
                })
                .Build();

And then when TracerProvider is resolved, it's build using the configured TracerProvider:

using var tracerProvider = ServiceLocator.GetService<TracerProvider>();
var activitySource = ServiceLocator.GetService<ActivitySource>();

// Now this doesn't provide a null object
using var activity = activitySource.StartActivity("Hello");

Just for reference, this is ServiceLocator:

public static class ServiceLocator
    {
        internal static IHost Host { get; set; }

        public static T GetService<T>() where T : class
        {
            return (T)Host.Services.GetService(typeof(T));
        }
    }
like image 20
sergi Avatar answered Sep 07 '25 20:09

sergi