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 badges1 Answer
Reset to default 0I 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.