关于 configuring-opentelemetry-dotnet
configuring-opentelemetry-dotnet 是配置 .NET OpenTelemetry 的技能,面向需要为 ASP.NET Core 应用添加可观测性的开发者。它涵盖追踪、指标、日志关联、自定义埋点和上下文传播。
主要功能
- 01配置追踪、指标与日志导出
- 02关联日志与追踪上下文
- 03创建自定义 Activity 与指标
- 04设置分布式上下文传播
- 05提供配置验证与常见问题排查
适用场景
- 01为 ASP.NET Core 添加分布式追踪
- 02通过 OTLP 导出可观测性数据
- 03为业务操作添加自定义跨度和指标
- 04排查服务间追踪上下文传播
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ dotnet/ skills --skill configuring-opentelemetry-dotnet安装后在 Claude Code 中输入/configuring-opentelemetry-dotnet确认已加载。
详细介绍
在 .NET 中配置 OpenTelemetry
适用场景
- 为 ASP.NET Core 应用添加分布式追踪
- 设置 OpenTelemetry 导出器(OTLP 是主要协议;Jaeger 原生支持 OTLP;Prometheus 的 OTLP 数据接收需要显式启用)
- 为业务操作创建自定义指标或追踪跨度
- 排查跨服务的分布式追踪上下文传播问题
不适用场景
- 用户只需要应用级日志记录(使用 ILogger、Serilog)
- 用户直接使用 Application Insights SDK(API 不同)
- 用户需要使用商业供应商专有 SDK 的 APM
输入
| 输入 | 必需 | 描述 |
|---|---|---|
| ASP.NET Core 项目 | 是 | 要进行插桩的应用 |
| 可观测性后端 | 否 | 数据导出目标:OTLP 收集器、Aspire 仪表板、Jaeger(原生支持 OTLP) |
工作流
步骤 1:安装正确的程序包
OpenTelemetry NuGet 程序包种类繁多。请仅安装以下程序包:
# Core SDK + ASP.NET Core instrumentation + logging integration
dotnet add package OpenTelemetry.Extensions.Hosting
dotnet add package OpenTelemetry.Instrumentation.AspNetCore
dotnet add package OpenTelemetry.Instrumentation.Http
# Exporter
dotnet add package OpenTelemetry.Exporter.OpenTelemetryProtocol # OTLP exporter for traces, metrics, AND logs
# Optional — dev/local debugging only (do NOT include in production deployments)
# dotnet add package OpenTelemetry.Exporter.Console
不要只安装 OpenTelemetry——还需要 OpenTelemetry.Extensions.Hosting,才能正确集成 DI。
可选:其他自动插桩程序包
只安装与你的应用所用库相匹配的程序包:
dotnet add package OpenTelemetry.Instrumentation.SqlClient # SQL Server queries
dotnet add package OpenTelemetry.Instrumentation.EntityFrameworkCore # EF Core
dotnet add package OpenTelemetry.Instrumentation.GrpcNetClient # gRPC calls
dotnet add package OpenTelemetry.Instrumentation.Runtime # GC, thread pool metrics
步骤 2:在 Program.cs 中配置所有信号
using OpenTelemetry.Resources;
using OpenTelemetry.Trace;
using OpenTelemetry.Metrics;
using OpenTelemetry.Logs;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddOpenTelemetry()
.ConfigureResource(resource => resource
.AddService(serviceName: builder.Environment.ApplicationName))
.WithTracing(tracing => tracing
.AddAspNetCoreInstrumentation(options =>
{
// Filter out health check endpoints from traces
options.Filter = httpContext =>
!httpContext.Request.Path.StartsWithSegments("/healthz");
})
.AddHttpClientInstrumentation(options =>
{
options.RecordException = true;
})
// Optional: add SQL instrumentation if using SqlClient directly
// .AddSqlClientInstrumentation(options =>
// {
// options.SetDbStatementForText = true;
// options.RecordException = true;
// })
// Custom activity sources (must match ActivitySource names in your code)
.AddSource("MyApp.Orders")
.AddSource("MyApp.Payments")
.AddSource("MyApp.Messaging"))
.WithMetrics(metrics => metrics
.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation()
// Optional: .AddRuntimeInstrumentation() for GC and thread pool metrics
// (requires OpenTelemetry.Instrumentation.Runtime package)
// Custom meters (must match Meter names in your code)
.AddMeter("MyApp.Metrics"))
.WithLogging(logging =>
{
logging.IncludeScopes = true;
// logging.IncludeFormattedMessage = true; // Enable if you need the formatted message string in log exports
})
// Single OTLP exporter for all signals — reads OTEL_EXPORTER_OTLP_ENDPOINT
// env var (defaults to http://localhost:4317). Override via environment variable
// or appsettings.json configuration.
.UseOtlpExporter();
步骤 3:了解日志与追踪的关联
步骤 2 中的 .WithLogging() 调用会将 ILogger 与 OpenTelemetry 集成:
- 每条日志记录都会自动包含 TraceId 和 SpanId,以便与追踪关联
.ConfigureResource()中配置的服务资源会自动传播到日志UseOtlpExporter()同样适用于日志和指标,不仅限于追踪- 无需额外程序包或单独调用
SetResourceBuilder
步骤 4:为业务操作创建自定义跨度(Activities)
using System.Diagnostics;
using Microsoft.Extensions.Logging;
public class OrderService
{
// Create an ActivitySource matching what you registered in Step 2
private static readonly ActivitySource ActivitySource = new("MyApp.Orders");
private readonly ILogger<OrderService> _logger;
public OrderService(ILogger<OrderService> logger) => _logger = logger;
public async Task<Order> ProcessOrderAsync(CreateOrderRequest request)
{
// Start a new span
using var activity = ActivitySource.StartActivity("ProcessOrder");
// Add attributes (tags) to the span
activity?.SetTag("order.customer_id", request.CustomerId);
activity?.SetTag("order.item_count", request.Items.Count);
try
{
// Child span for validation
using (var validationActivity = ActivitySource.StartActivity("ValidateOrder"))
{
await ValidateOrderAsync(request);
validationActivity?.SetTag("validation.result", "passed");
}
// Child span for payment
using (var paymentActivity = ActivitySource.StartActivity("ProcessPayment",
ActivityKind.Client)) // Client = outgoing call
{
paymentActivity?.SetTag("payment.method", request.PaymentMethod);
await ProcessPaymentAsync(request);
}
var order = new Order { Id = Guid.NewGuid(), CustomerId = request.CustomerId, Status = "Completed" };
activity?.SetTag("order.status", "completed");
activity?.SetStatus(ActivityStatusCode.Ok);
return order;
}
catch (Exception ex)
{
activity?.SetStatus(ActivityStatusCode.Error, ex.Message);
// Log via ILogger — OpenTelemetry captures this with trace correlation.
// Prefer logging over activity.RecordException() as OTel is deprecating
// span events for exception recording in favor of log-based exceptions.
_logger.LogError(ex, "Order processing failed for customer {CustomerId}", request.CustomerId);
throw;
}
}
}
重要:ActivitySource 名称必须与配置中的 AddSource("...") 相匹配。 不匹配的来源会被静默忽略——这是最常见的调试问题。
步骤 5:创建自定义指标
使用 IMeterFactory(通过 DI 注入)创建计量器,以确保生命周期管理得当且便于测试。
using System.Diagnostics;
using System.Diagnostics.Metrics;
public class OrderMetrics
{
private readonly Counter<long> _ordersProcessed;
private readonly Histogram<double> _orderProcessingDuration;
private readonly UpDownCounter<int> _activeOrders;
public OrderMetrics(IMeterFactory meterFactory)
{
// Meter name must match AddMeter("...") in configuration
var meter = meterFactory.Create("MyApp.Metrics");
// Counter — use for things that only go up
_ordersProcessed = meter.CreateCounter<long>(
"orders.processed", "orders", "Total orders successfully processed");
// Histogram — use for measuring distributions (latency, sizes)
_orderProcessingDuration = meter.CreateHistogram<double>(
"orders.processing_duration", "ms", "Time to process an order");
// UpDownCounter — use for things that go up AND down
_activeOrders = meter.CreateUpDownCounter<int>(
"orders.active", "orders", "Currently processing orders");
}
public void RecordOrderProcessed(string region, double durationMs)
{
// Tags enable dimensional filtering (by region, status, etc.)
var tags = new TagList
{
{ "region", region },
{ "order.type", "standard" }
};
_ordersProcessed.Add(1, tags);
_orderProcessingDuration.Record(durationMs, tags);
}
}
在 DI 中注册 OrderMetrics:
builder.Services.AddSingleton<OrderMetrics>();
步骤 6:为分布式场景配置上下文传播
使用 AddHttpClientInstrumentation() 时,HTTP 调用会自动传播追踪上下文。对于非 HTTP 场景:
using System;
using System.Collections.Generic;
using System.Diagnostics;
using OpenTelemetry.Context.Propagation;
// ActivitySource should be static — register via .AddSource("MyApp.Messaging") in Step 2
private static readonly ActivitySource MessageSource = new("MyApp.Messaging");
// Manual context propagation (e.g., across message queues)
// On the SENDING side:
var propagator = Propagators.DefaultTextMapPropagator;
var activityContext = Activity.Current?.Context ?? default;
var context = new PropagationContext(activityContext, Baggage.Current);
var carrier = new Dictionary<string, string>();
propagator.Inject(context, carrier, (dict, key, value) => dict[key] = value);
// Send carrier dictionary as message headers
// On the RECEIVING side:
var parentContext = propagator.Extract(default, carrier,
(dict, key) => dict.TryGetValue(key, out var value) ? new[] { value } : Array.Empty<string>());
Baggage.Current = parentContext.Baggage;
using var activity = MessageSource.StartActivity("ProcessMessage",
ActivityKind.Consumer,
parentContext.ActivityContext); // Links to parent trace!
验证
- 追踪数据出现在可观测性后端(Jaeger、Aspire 仪表板等)
- HTTP 请求会自动创建包含正确动词、URL 和状态码的跨度
- 自定义
ActivitySource名称与AddSource()注册项相匹配 - 自定义
Meter名称与AddMeter()注册项相匹配 - 日志包含 TraceId 和 SpanId,以便进行关联
- 健康检查端点已从追踪中筛除
- 错误跨度中包含异常详情
常见问题
| 问题 | 解决方案 |
|---|---|
ActivitySource.StartActivity 返回 null | 来源名称与任何 AddSource() 都不匹配——名称必须完全一致 |
| 导出器中没有追踪数据 | 检查 OTLP 端点:gRPC 使用端口 4317,HTTP 使用端口 4318 |
| 缺少 HTTP 客户端跨度 | 确保已注册 AddHttpClientInstrumentation();它适用于 IHttpClientFactory/DI 和 new HttpClient()(为管理生命周期,请使用 IHttpClientFactory) |
| 标签基数过高 | 不要将用户 ID、请求 ID 或 UUID 用作指标标签——这会导致存储量激增 |
| OTLP 的 gRPC 与 HTTP 不匹配 | 默认使用 gRPC(端口 4317);如果收集器只接受 HTTP,请设置 OtlpExportProtocol.HttpProtobuf |
Meter / ActivitySource 生命周期 | ActivitySource 应为静态;通过 DI 中的 IMeterFactory 创建 Meter(不要使用 new Meter()),以确保生命周期管理得当且便于测试 |
评论
支持 Markdown