You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何为System.Diagnostics.Metrics与OpenTelemetry添加全局标签

全局为OpenTelemetry指标添加DC和Env标签的解决方案

问题背景

需要为服务中所有上报的指标全局添加dc(数据中心)和env(环境)标签,避免开发人员手动为每个测量添加这些维度。尝试通过ConfigureResource添加属性,但Prometheus端点输出的指标中并未包含这些标签。

当前Prometheus输出

# TYPE click_c_clicks_total counter
# UNIT click_c_clicks_total clicks_total
# HELP click_c_clicks_total count the number of clicks
click_c_clicks_total{key="a"} 14 1682246108134
click_c_clicks_total{key="s"} 15 1682246108134
click_c_clicks_total{key="d"} 14 1682246108134
click_c_clicks_total{key="e"} 2 1682246108134
click_c_clicks_total{key="r"} 2 1682246108134
click_c_clicks_total{key="w"} 1 1682246108134

# TYPE click_t_ms histogram
# UNIT click_t_ms ms
# HELP click_t_ms time to click after prompted
click_t_ms_bucket{le="0"} 0 1682246108134
click_t_ms_bucket{le="5"} 0 1682246108134
click_t_ms_bucket{le="10"} 0 1682246108134
click_t_ms_bucket{le="25"} 0 1682246108134
click_t_ms_bucket{le="50"} 0 1682246108134
click_t_ms_bucket{le="75"} 17 1682246108134
click_t_ms_bucket{le="100"} 24 1682246108134
click_t_ms_bucket{le="250"} 42 1682246108134
click_t_ms_bucket{le="500"} 44 1682246108134
click_t_ms_bucket{le="750"} 47 1682246108134
click_t_ms_bucket{le="1000"} 47 1682246108134
click_t_ms_bucket{le="2500"} 47 1682246108134
click_t_ms_bucket{le="5000"} 47 1682246108134
click_t_ms_bucket{le="7500"} 47 1682246108134
click_t_ms_bucket{le="10000"} 47 1682246108134
click_t_ms_bucket{le="+Inf"} 48 1682246108134
click_t_ms_sum 29085 1682246108134
click_t_ms_count 48 1682246108134

# EOF

示例代码

var host = Host
    .CreateDefaultBuilder()
    .ConfigureLogging(logger => logger.AddConsole())
    .ConfigureServices(services =>
    {
        services.AddOpenTelemetry()
            .WithMetrics(metricsBuilder =>
            {
                metricsBuilder.ConfigureResource(resourceBuilder =>
                {
                    resourceBuilder
                        .AddService("ClickService")
                        .AddAttributes(new[]
                        {
                            new KeyValuePair<string, object>("dc", Environment.GetEnvironmentVariable("dc") ?? "us1"),
                            new KeyValuePair<string, object>("env", Environment.GetEnvironmentVariable("env") ?? "local"),
                        })
                        .AddTelemetrySdk();
                });
                var meterProviderBuilder = metricsBuilder.AddMeter("custom-console-meter");

                metricsBuilder.AddConsoleExporter();

                metricsBuilder.AddPrometheusHttpListener(httpListenerOptions =>
                {
                    httpListenerOptions.UriPrefixes = new[] { "http://localhost:9464/" };
                });
            });

        services.AddHostedService<ClickAppHost>();
    });

await host.RunConsoleAsync();

ClickAppHost代码

// 一个简单的循环应用,直到按下CTRL-C停止
// 统计按键点击次数
public class ClickAppHost : IHostedService
{
    private readonly CancellationTokenSource _work = new();

    public Task StartAsync(CancellationToken cancellationToken)
    {
        Console.CancelKeyPress += (sender, args) =>
        {
            Console.WriteLine("收到取消请求");
            _work.Cancel();
        };

        while (!_work.Token.IsCancellationRequested)
        {
            Console.WriteLine("按下任意按键");
            var sw = Stopwatch.GetTimestamp();
            
            var key = Console.ReadKey(intercept: true);
            
            var elapsed = Stopwatch.GetElapsedTime(sw);
            var combination = GetKeyLabel(key);
            Console.WriteLine($"已点击: {combination}");

            Meters.ClickInstrument.Add(1,
                new KeyValuePair<string, object?>("key", combination));
            Meters.DurationInstrumentation.Record((long)elapsed.TotalMilliseconds);
        }

        return Task.CompletedTask;

        static string GetKeyLabel(ConsoleKeyInfo keyInfo) 
            => keyInfo.Modifiers != ConsoleModifiers.None 
                ? $"{keyInfo.Modifiers}-{keyInfo.KeyChar}" 
                : keyInfo.KeyChar.ToString();
    }

    public Task StopAsync(CancellationToken cancellationToken)
    {
        _work.Cancel();

        return Task.CompletedTask;
    }
}

public static class Meters
{
    private static readonly Meter _applicationMeter = new("custom-console-meter", "1.0.0");

    public static readonly Counter<int> ClickInstrument 
        = _applicationMeter.CreateCounter<int>("click_c", "clicks_total", "统计点击次数");

    public static readonly Histogram<long> DurationInstrumentation 
        = _applicationMeter.CreateHistogram<long>("click_t", "ms", "统计提示后到点击的耗时");
}

依赖项

<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.0-preview.3.23174.8" />
    <PackageReference Include="OpenTelemetry.Exporter.Console" Version="1.5.0-alpha.2" />
    <PackageReference Include="OpenTelemetry.Exporter.Prometheus.HttpListener" Version="1.5.0-alpha.1" />
    <PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.5.0-alpha.2" />

解决方案

核心原因

OpenTelemetry的Resource属性默认不会自动转换为Prometheus的指标标签,需要通过指标处理器或指标转换逻辑手动注入全局标签。

实现思路

以下是两种可行方案:

方案1:使用TransformMetrics快速实现(OTEL 1.4+支持)

修改WithMetrics配置,添加全局标签转换逻辑:

services.AddOpenTelemetry()
    .WithMetrics(metricsBuilder =>
    {
        // 保留原有Resource配置(用于其他导出器或上下文)
        metricsBuilder.ConfigureResource(resourceBuilder =>
        {
            resourceBuilder
                .AddService("ClickService")
                .AddAttributes(new[]
                {
                    new KeyValuePair<string, object>("dc", Environment.GetEnvironmentVariable("dc") ?? "us1"),
                    new KeyValuePair<string, object>("env", Environment.GetEnvironmentVariable("env") ?? "local"),
                })
                .AddTelemetrySdk();
        });

        // 添加全局标签转换
        metricsBuilder.TransformMetrics(metric =>
        {
            var dc = Environment.GetEnvironmentVariable("dc") ?? "us1";
            var env = Environment.GetEnvironmentVariable("env") ?? "local";
            return metric
                .AddTag("dc", dc)
                .AddTag("env", env);
        });

        metricsBuilder.AddMeter("custom-console-meter");
        metricsBuilder.AddConsoleExporter();
        metricsBuilder.AddPrometheusHttpListener(httpListenerOptions =>
        {
            httpListenerOptions.UriPrefixes = new[] { "http://localhost:9464/" };
        });
    });

方案2:自定义MetricProcessor(兼容旧版本)

如果使用的OTEL版本不支持TransformMetrics,可以自定义处理器:

public class GlobalLabelInjectorProcessor : MetricProcessor
{
    private readonly string _dc;
    private readonly string _env;

    public GlobalLabelInjectorProcessor()
    {
        _dc = Environment.GetEnvironmentVariable("dc") ?? "us1";
        _env = Environment.GetEnvironmentVariable("env") ?? "local";
    }

    public override void OnEnd(Metric metric)
    {
        // 为每个指标添加全局标签
        metric.AddTag("dc", _dc);
        metric.AddTag("env", _env);
        base.OnEnd(metric);
    }
}

然后在配置中添加处理器:

metricsBuilder.AddProcessor(new GlobalLabelInjectorProcessor());

效果验证

修改后,Prometheus输出的指标会自动带上dc和env标签,示例如下:

click_c_clicks_total{key="a",dc="us1",env="local"} 14 1682246108134

内容的提问来源于stack exchange,提问作者Eugene Krapivin

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.23 23:04:54