← 回文章列表

2024年8月19日

幫 ASP.NET Core 接上 OpenTelemetry:從零到 Grafana/Tempo/Loki

OpenTelemetryGrafanaObservabilityASP.NET Core

分散式架構下服務一多(Message Queue、各種微服務、Restful/gRPC/GraphQL API),要追蹤 API 異常、改善流程、確認 SLI 指標,都需要一套可觀測性架構打 底。這篇把「如何從零幫一個 ASP.NET Core 專案接上 OpenTelemetry、並串到 Grafana 全家桶」整理成一份可以直接照抄的參考做法,完整可執行的範例放在 Web-Application-Observability-and-Availability。

解決方案架構

以 OpenTelemetry(OTEL) 作為統一的可觀測性框架,搭配 Grafana 生態系:

  • OpenTelemetry Collector:收集 Trace 與 Metric 的集線器,可輸出到 Grafana、Jaeger 等不同後端
  • Prometheus:儲存時序資料(CPU、Memory 等)的監控方案
  • Grafana:將 Prometheus、資料庫等來源的資料視覺化
  • Grafana Tempo:開源、可大規模擴展的分散式追蹤後端
  • Grafana Loki:可擴展、多租戶的日誌聚合系統

可觀測性解決方案架構

本機執行

Shell
# 先調整 configs/ 內各服務的 IP 與 Port
docker compose up -d

幫 ASP.NET Core 8 接上可觀測性

安裝套件:OpenTelemetry.Extensions.Hosting、 OpenTelemetry.Instrumentation.AspNetCore、 OpenTelemetry.Instrumentation.Http、 OpenTelemetry.Exporter.OpenTelemetryProtocol、 OpenTelemetry.Exporter.Prometheus.AspNetCore

Tracing 與 Metrics 的註冊:

C#
var otel = builder.Services.AddOpenTelemetry();
otel.ConfigureResource(resource => resource
    .AddService(serviceName: builder.Environment.ApplicationName));

otel.WithMetrics(metrics => metrics
    .AddAspNetCoreInstrumentation()
    .AddMeter(greeterMeter.Name)
    .AddMeter("Microsoft.AspNetCore.Hosting")
    .AddPrometheusExporter());

otel.WithTracing(tracing =>
{
    tracing.AddAspNetCoreInstrumentation(o =>
    {
        // 從 X-Forwarded-For 或連線資訊解析真實客戶端 IP,寫入 span tag
        o.EnrichWithHttpRequest = (activity, request) => { /* ... */ };
    });
    tracing.AddHttpClientInstrumentation();
    tracing.AddSource(TracingActivitySource.WebApiRegisteredActivity.Name);
    tracing.AddSource(TracingActivitySource.ServiceRegisteredActivity.Name);
    tracing.AddOtlpExporter(o => o.Endpoint = new Uri("http://otel:4317"));
});

分層命名 ActivitySource,讓一次請求跨 WebApi/Service/Repository 三層的 Trace 都能被串起來:

C#
public static class TracingActivitySource
{
    public static readonly ActivitySource WebApiRegisteredActivity = new("Monitor.Sample.Api");
    public static readonly ActivitySource ServiceRegisteredActivity = new("Monitor.Sample.Service");
    public static readonly ActivitySource RepositoryRegisteredActivity = new("Monitor.Sample.Repository");
}
C#
public Task<int> GetNumberAsync()
{
    using (TracingActivitySource.ServiceRegisteredActivity.StartActivity(
        $"{nameof(TracingTestService)}.{nameof(GetNumberAsync)}"))
    {
        return tracingTestRepository.GetNumberAsync();
    }
}

踩過的坑

⚠️ OpenTelemetry.Exporter.Prometheus.AspNetCore 版本 1.8.0 會導致 Prometheus 收集 log 錯誤,需改回 1.7.0 或更新版本才能解決 (對應 issue)。

延伸應用

這次在範例專案上把 Traces、Logs、Metrics 三條線都串起來了,下一步是套用到正式 環境:統一告警儀表板,並依業務情境挑選合適的 Prometheus 指標類型(Counter、 Gauge、Histogram),讓告警規則與 PromQL 查詢有一致的設計基礎。