opentelemetry-dotnet icon indicating copy to clipboard operation
opentelemetry-dotnet copied to clipboard

[bug] resource attributes are not added to metrics defined without dependency injection

Open Meir017 opened this issue 5 months ago • 4 comments

Package

OpenTelemetry.Extensions.Hosting

Package Version

Package Name Version
OpenTelemetry.Api 1.12.0
OpenTelemetry 1.12.0

Runtime Version

net9.0

Description

metrics defined using the IMeterFactory send default tags defined by the following:

builder.Services.AddOpenTelemetry()
    .ConfigureResource(resource =>
    {
        resource.AddAttributes(
        [
            new("service_name", builder.Environment.ApplicationName)
        ]);
    })

metrics defined as static metrics don't include these added attributes as tags (such as https://github.com/dotnet/runtime/blob/b55365541d8666ef358fda08514165d6a7da47fb/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/RuntimeMetrics.cs#L39)

Steps to Reproduce

generating a new project using dotnet new aspire-starter with 9.3 templates installed.

then in the Extenstions.cs file add the following

        builder.Services.AddOpenTelemetry()
            .ConfigureResource(resource =>
            {
+                resource.AddAttributes(
+                [
+                    new("service_name", builder.Environment.ApplicationName)
+                ]);
            })
            .WithMetrics(metrics =>
            {
                metrics.AddAspNetCoreInstrumentation()
                    .AddHttpClientInstrumentation()
                    .AddRuntimeInstrumentation();

+                metrics.AddMeter("Meir-service");
+                metrics.AddMeter("Meir-service-with-di");
            })

then in the Program.cs of the web-api project:

add the following:


public class CustomMetersWithoutDI
{
    public static readonly Meter _meter = new("Meir-service", "1.0.0");

    private static readonly Counter<long> _counter = _meter.CreateCounter<long>("custom_counter", "1.0.0", "A custom counter for demonstration purposes");
    private static readonly Histogram<double> _histogram = _meter.CreateHistogram<double>("custom_histogram", "1.0.0", "A custom histogram for demonstration purposes");
    private static readonly UpDownCounter<long> _upDownCounter = _meter.CreateUpDownCounter<long>("custom_updown_counter", "1.0.0", "A custom up-down counter for demonstration purposes");
    private static readonly ObservableGauge<double> _observableGauge = _meter.CreateObservableGauge<double>("custom_observable_gauge", () => Random.Shared.NextDouble() * 100, "A custom observable gauge for demonstration purposes");
    private static readonly ObservableCounter<long> _observableCounter = _meter.CreateObservableCounter<long>("custom_observable_counter", () => Random.Shared.Next(1, 100), "A custom observable counter for demonstration purposes");
    private static readonly ObservableUpDownCounter<long> _observableUpDownCounter = _meter.CreateObservableUpDownCounter<long>("custom_observable_updown_counter", () => Random.Shared.Next(-50, 50), "A custom observable up-down counter for demonstration purposes");

    public static void ReportMetrics()
    {
        _counter.Add(1);
        _histogram.Record(Random.Shared.NextDouble() * 100);
        _upDownCounter.Add(1);
    }
}

public class CustomMetersWithDI
{
    private readonly Counter<long> _counter;
    private readonly Histogram<double> _histogram;
    private readonly UpDownCounter<long> _upDownCounter;
    private readonly ObservableGauge<double> _observableGauge;
    private readonly ObservableCounter<long> _observableCounter;
    private readonly ObservableUpDownCounter<long> _observableUpDownCounter;


    public CustomMetersWithDI(IMeterFactory meterFactory)
    {
        var meter = meterFactory.Create(new MeterOptions("Meir-service-with-di"));

        _counter = meter.CreateCounter<long>("custom_counter", "1.0.0", "A custom counter for demonstration purposes");
        _histogram = meter.CreateHistogram<double>("custom_histogram", "1.0.0", "A custom histogram for demonstration purposes");
        _upDownCounter = meter.CreateUpDownCounter<long>("custom_updown_counter", "1.0.0", "A custom up-down counter for demonstration purposes");
        _observableGauge = meter.CreateObservableGauge<double>("custom_observable_gauge", () => Random.Shared.NextDouble() * 100, "A custom observable gauge for demonstration purposes");
        _observableCounter = meter.CreateObservableCounter<long>("custom_observable_counter", () => Random.Shared.Next(1, 100), "A custom observable counter for demonstration purposes");
        _observableUpDownCounter = meter.CreateObservableUpDownCounter<long>("custom_observable_updown_counter", () => Random.Shared.Next(-50, 50), "A custom observable up-down counter for demonstration purposes");

    }

    public void ReportMetrics()
    {
        _counter.Add(1);
        _histogram.Record(Random.Shared.NextDouble() * 100);
        _upDownCounter.Add(1);
    }
}

and modify the code:

service:

builder.Services.AddOpenApi();

+builder.Services.AddSingleton<CustomMetersWithDI>();

var app = builder.Build();

endpoint:

-app.MapGet("/weatherforecast", () =>
+app.MapGet("/weatherforecast", ([FromServices] CustomMetersWithDI customMeters) =>
{
    var forecast = Enumerable.Range(1, 5).Select(index =>
        new WeatherForecast
        (
            DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
            Random.Shared.Next(-20, 55),
            summaries[Random.Shared.Next(summaries.Length)]
        ))
        .ToArray();

+    CustomMetersWithoutDI.ReportMetrics();
+    customMeters.ReportMetrics();

    return forecast;
})

Expected Result

the service_name tag should appear on all metrics

Actual Result

the service_name tag appears on all the aspnetcore metrics, not on the HttpClient metrics nor the runtime metrics

Additional Context

No response

Meir017 avatar Jul 10 '25 15:07 Meir017

the service_name tag appears on all the aspnetcore metrics, not on the HttpClient metrics nor the runtime metrics

Where and how are you validating this? Are you using ConsoleExporter? Or OTLP?

Resource attributes ("service_name" in the example) is not added as a tag in any metric by default. They are exported as Resource attributes itself, outside of any metric... So it is unclear what you meant by "service_name tag appears on all aspnetcore metrics.." Once you describe how you are validating, that can help us understand more.

cijothomas avatar Jul 10 '25 16:07 cijothomas

I just observed the same issue when I swapped SetResourceBuilder() on each of logs, metrics and traces for a single call to ConfigureResource().

When I deployed the change my data disappeared from my observability dashboard as service.name was no longer being set. Here's the commit that reverts that change to restore things to working order: https://github.com/martincostello/costellobot/commit/c32e9fc5b83131589d74af3e73e5da7f640c6ff6

martincostello avatar Jul 10 '25 17:07 martincostello

@cijothomas I'm using the aspire dashboard for to view the telemetry.

complete sample project here - https://github.com/Meir017/resource-attributes-are-not-added-to-metrics-defined-without-dependency-injection

Meir017 avatar Jul 10 '25 19:07 Meir017

@JamesNK is the dashboard doing anything special when collecting metrics that could explain this?

Meir017 avatar Jul 11 '25 05:07 Meir017