最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

What is the correct method to setup OpenTelemetry tracing in a .NET console app using dependency injection? - Stack Overflow

programmeradmin0浏览0评论

I'm having trouble getting OTEL tracing to work in a .NET console app. I'm trying to setup tracing using the services.AddOpenTelemetry().WithTracing(options => ...) method. I've created a class to store an instance of an ActivitySource so that I can inject that throughout the app.

The following CoPilot sample works and I can see the trace in my local instance of Grafana. However, I don't want to build the app this way since I'd like to use DI.

<Project Sdk="Microsoft.NET.Sdk">

    <PropertyGroup>
        <OutputType>Exe</OutputType>
        <TargetFramework>net9.0</TargetFramework>
        <ImplicitUsings>enable</ImplicitUsings>
        <Nullable>enable</Nullable>
    </PropertyGroup>

    <ItemGroup>
        <PackageReference Include="OpenTelemetry" Version="1.11.1"/>
        <PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.11.1"/>
        <PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.11.1"/>
    </ItemGroup>

</Project>
using System.Diagnostics;
using OpenTelemetry;
using OpenTelemetry.Resources;
using OpenTelemetry.Trace;

namespace OtelTest
{
    class Program
    {
        static void Main(string[] args)
        {
            using var tracerProvider = Sdk.CreateTracerProviderBuilder()
                .AddSource("OtelTest")
                .SetResourceBuilder(ResourceBuilder.CreateDefault().AddService("OtelTestService"))
                .AddOtlpExporter(options => options.Endpoint = new Uri("http://localhost:4317"))
                .Build();

            var activitySource = new ActivitySource("OtelTest");

            using (var activity = activitySource.StartActivity("TestActivity"))
            {
                Console.WriteLine("Hello, World!");
                activity?.SetTag("foo", 1);
                activity?.SetTag("bar", "baz");
            }
        }
    }
}

Here's an expanded sample that demonstrates what I'm trying to (using the same packages as above). When I run this sample I don't get any traces in the local Grafana.

using System.Diagnostics;
using Microsoft.Extensions.DependencyInjection;
using OpenTelemetry.Resources;
using OpenTelemetry.Trace;

namespace OtelTest
{
    public class OtelTestTracing
    {
        public ActivitySource ActivitySource { get; }

        public OtelTestTracing()
        {
            ActivitySource = new ActivitySource("OtelTest");
        }
    }

    public class ActivityService
    {
        private readonly OtelTestTracing _otelTestTracing;

        public ActivityService(OtelTestTracing otelTestTracing)
        {
            _otelTestTracing = otelTestTracing;
        }

        public void DoWork()
        {
            using (var activity = _otelTestTracing.ActivitySource.StartActivity("TestActivity"))
            {
                Console.WriteLine("Hello, World!");
                activity?.SetTag("foo", 1);
                activity?.SetTag("bar", "baz");
            }
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            var serviceCollection = new ServiceCollection();
            ConfigureServices(serviceCollection);

            var serviceProvider = serviceCollection.BuildServiceProvider();
            var activityService = serviceProvider.GetRequiredService<ActivityService>();

            activityService.DoWork();
        }

        private static void ConfigureServices(IServiceCollection services)
        {
            services
                .AddOpenTelemetry()
                .WithTracing(builder => builder
                    .AddSource("OtelTest")
                    .SetResourceBuilder(ResourceBuilder.CreateDefault().AddService("OtelTestService"))
                    .AddOtlpExporter(options => options.Endpoint = new Uri("http://localhost:4317")));

            services.AddSingleton<OtelTestTracing>();
            services.AddTransient<ActivityService>();
        }
    }
}

If I observe the OTEL collector logs in the Docker container instance, it doesn't show any incoming traces. If I set a breakpoint and observe the _otelTestTracing.ActivitySource instance being injected into the ActivityService class I notice that the private _listeners properties is null. Whereas in the working sample _listeners is set of an instance of SynchronizedListeners. That seems to be where it's breaking but I don't understand why.

What changes can I make to get the tracing to work in the DI sample?

I'm having trouble getting OTEL tracing to work in a .NET console app. I'm trying to setup tracing using the services.AddOpenTelemetry().WithTracing(options => ...) method. I've created a class to store an instance of an ActivitySource so that I can inject that throughout the app.

The following CoPilot sample works and I can see the trace in my local instance of Grafana. However, I don't want to build the app this way since I'd like to use DI.

<Project Sdk="Microsoft.NET.Sdk">

    <PropertyGroup>
        <OutputType>Exe</OutputType>
        <TargetFramework>net9.0</TargetFramework>
        <ImplicitUsings>enable</ImplicitUsings>
        <Nullable>enable</Nullable>
    </PropertyGroup>

    <ItemGroup>
        <PackageReference Include="OpenTelemetry" Version="1.11.1"/>
        <PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.11.1"/>
        <PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.11.1"/>
    </ItemGroup>

</Project>
using System.Diagnostics;
using OpenTelemetry;
using OpenTelemetry.Resources;
using OpenTelemetry.Trace;

namespace OtelTest
{
    class Program
    {
        static void Main(string[] args)
        {
            using var tracerProvider = Sdk.CreateTracerProviderBuilder()
                .AddSource("OtelTest")
                .SetResourceBuilder(ResourceBuilder.CreateDefault().AddService("OtelTestService"))
                .AddOtlpExporter(options => options.Endpoint = new Uri("http://localhost:4317"))
                .Build();

            var activitySource = new ActivitySource("OtelTest");

            using (var activity = activitySource.StartActivity("TestActivity"))
            {
                Console.WriteLine("Hello, World!");
                activity?.SetTag("foo", 1);
                activity?.SetTag("bar", "baz");
            }
        }
    }
}

Here's an expanded sample that demonstrates what I'm trying to (using the same packages as above). When I run this sample I don't get any traces in the local Grafana.

using System.Diagnostics;
using Microsoft.Extensions.DependencyInjection;
using OpenTelemetry.Resources;
using OpenTelemetry.Trace;

namespace OtelTest
{
    public class OtelTestTracing
    {
        public ActivitySource ActivitySource { get; }

        public OtelTestTracing()
        {
            ActivitySource = new ActivitySource("OtelTest");
        }
    }

    public class ActivityService
    {
        private readonly OtelTestTracing _otelTestTracing;

        public ActivityService(OtelTestTracing otelTestTracing)
        {
            _otelTestTracing = otelTestTracing;
        }

        public void DoWork()
        {
            using (var activity = _otelTestTracing.ActivitySource.StartActivity("TestActivity"))
            {
                Console.WriteLine("Hello, World!");
                activity?.SetTag("foo", 1);
                activity?.SetTag("bar", "baz");
            }
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            var serviceCollection = new ServiceCollection();
            ConfigureServices(serviceCollection);

            var serviceProvider = serviceCollection.BuildServiceProvider();
            var activityService = serviceProvider.GetRequiredService<ActivityService>();

            activityService.DoWork();
        }

        private static void ConfigureServices(IServiceCollection services)
        {
            services
                .AddOpenTelemetry()
                .WithTracing(builder => builder
                    .AddSource("OtelTest")
                    .SetResourceBuilder(ResourceBuilder.CreateDefault().AddService("OtelTestService"))
                    .AddOtlpExporter(options => options.Endpoint = new Uri("http://localhost:4317")));

            services.AddSingleton<OtelTestTracing>();
            services.AddTransient<ActivityService>();
        }
    }
}

If I observe the OTEL collector logs in the Docker container instance, it doesn't show any incoming traces. If I set a breakpoint and observe the _otelTestTracing.ActivitySource instance being injected into the ActivityService class I notice that the private _listeners properties is null. Whereas in the working sample _listeners is set of an instance of SynchronizedListeners. That seems to be where it's breaking but I don't understand why.

What changes can I make to get the tracing to work in the DI sample?

Share Improve this question asked yesterday Matthew MacFarlandMatthew MacFarland 2,7293 gold badges29 silver badges42 bronze badges
Add a comment  | 

1 Answer 1

Reset to default 0

I wouldn't say there's a "correct" way for this, but there are patterns that are more common that help avoid some of the inefficient patterns.

First, ActivitySource should be static, there's no benefit to using instance methods, and they should generally be scoped to the DLL/project, rather than the class. These map to "instrumentation scope" in the OpenTelemetry world, and are generally reserved for libraries.

You should ensure that your application calls TracerProvider.Dispose() and waits before it closes, this will ensure that any in-memory spans have been pushed to the exporter (same for MetricProvider and LogRecordProvider).

You also need to force the TracerProvider to "build". This is done through the use of a BackgroundService. If you're using something like a HostBuilder, these background services are already started, but if you're using the ServiceProvider manually, this won't happen. I believe you could resolve the BackgroundService, or resolve TracerProvider, and this would work.

My recommendation though, would be to switch to using HostApplicationBuilder instead of the IServiceCollection directly, this is generally accepted as a better way to write console applications now.

与本文相关的文章

发布评论

评论列表(0)

  1. 暂无评论