opentelemetry
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseOpenTelemetry
OpenTelemetry
Core Principles
核心原则
- Three pillars, one setup — Configure traces, metrics, and logs through a single call. Use
AddOpenTelemetry()for cross-cutting export to any OTLP-compatible backend.UseOtlpExporter() - Use for metrics — Never create
IMeterFactoryinstances withMeter. The factory manages lifetime through DI and prevents leaks.new - Null-safe activities — returns
StartActivity()when no listener is attached. Always usenullwhen setting tags or events.?. - Environment variables over code — Use and
OTEL_EXPORTER_OTLP_ENDPOINTso deployments control telemetry routing without code changes.OTEL_SERVICE_NAME - Low-cardinality metric tags — Keep metric tag combinations under ~1000 per instrument. Use span attributes or logs for high-cardinality data like user IDs or request IDs.
- 三大支柱,一套配置 — 通过单次调用配置链路追踪、指标与日志。使用
AddOpenTelemetry()实现跨信号导出至任意兼容OTLP的后端。UseOtlpExporter() - 使用管理指标 — 切勿通过
IMeterFactory创建new实例。工厂通过依赖注入(DI)管理生命周期,避免内存泄漏。Meter - 空安全的Activity — 当没有监听器附着时,会返回
StartActivity()。设置标签或事件时务必使用null操作符。?. - 环境变量优先于代码配置 — 使用和
OTEL_EXPORTER_OTLP_ENDPOINT,让部署环节无需修改代码即可控制遥测路由。OTEL_SERVICE_NAME - 低基数指标标签 — 每个指标工具的标签组合数需保持在约1000以内。用户ID、请求ID等高基数数据应使用Span属性或日志记录。
Patterns
实践模式
Full Setup with All Three Signals
全信号完整配置
csharp
// Program.cs
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddOpenTelemetry()
.ConfigureResource(resource => resource
.AddService(
serviceName: builder.Environment.ApplicationName,
serviceVersion: "1.0.0"))
.WithTracing(tracing => tracing
.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation()
.AddEntityFrameworkCoreInstrumentation()
.AddSource("MyApp.Orders"))
.WithMetrics(metrics => metrics
.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation()
.AddRuntimeInstrumentation()
.AddMeter("MyApp.Orders"))
.WithLogging() // no per-signal exporter here —
.UseOtlpExporter(); // UseOtlpExporter covers all three signals
// UseOtlpExporter replaces per-signal AddOtlpExporter calls. Never combine
// the two — mixing them throws NotSupportedException (see Anti-patterns).The OTLP endpoint defaults to (gRPC). Override via:
http://localhost:4317OTEL_EXPORTER_OTLP_ENDPOINT=http://collector:4317
OTEL_SERVICE_NAME=MyApp.Apicsharp
// Program.cs
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddOpenTelemetry()
.ConfigureResource(resource => resource
.AddService(
serviceName: builder.Environment.ApplicationName,
serviceVersion: "1.0.0"))
.WithTracing(tracing => tracing
.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation()
.AddEntityFrameworkCoreInstrumentation()
.AddSource("MyApp.Orders"))
.WithMetrics(metrics => metrics
.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation()
.AddRuntimeInstrumentation()
.AddMeter("MyApp.Orders"))
.WithLogging() // 此处无需为单个信号配置导出器 —
.UseOtlpExporter(); // UseOtlpExporter可覆盖所有三类信号
// UseOtlpExporter替代了为单个信号调用AddOtlpExporter的方式。切勿将两者混用
// 混用会抛出NotSupportedException(详见反模式章节)。OTLP端点默认值为(gRPC)。可通过以下方式覆盖:
http://localhost:4317OTEL_EXPORTER_OTLP_ENDPOINT=http://collector:4317
OTEL_SERVICE_NAME=MyApp.ApiCustom Metrics with IMeterFactory
基于IMeterFactory的自定义指标
Register a metrics class as a singleton. handles disposal through DI.
IMeterFactoryMetercsharp
public sealed class OrderMetrics
{
private readonly Counter<int> _ordersCreated;
private readonly Histogram<double> _orderDuration;
private readonly UpDownCounter<int> _activeOrders;
private readonly Gauge<double> _queueDepth;
public OrderMetrics(IMeterFactory meterFactory)
{
var meter = meterFactory.Create("MyApp.Orders");
_ordersCreated = meter.CreateCounter<int>(
"myapp.orders.created", "{orders}", "Number of orders created");
_orderDuration = meter.CreateHistogram<double>(
"myapp.orders.duration", "s", "Order processing duration",
advice: new InstrumentAdvice<double>
{
HistogramBucketBoundaries = [0.01, 0.05, 0.1, 0.5, 1, 5, 10]
});
_activeOrders = meter.CreateUpDownCounter<int>(
"myapp.orders.active", "{orders}", "Currently active orders");
_queueDepth = meter.CreateGauge<double>(
"myapp.orders.queue_depth", "{items}", "Current queue depth");
}
public void OrderCreated() => _ordersCreated.Add(1);
public void RecordDuration(double seconds) => _orderDuration.Record(seconds);
public void OrderStarted() => _activeOrders.Add(1);
public void OrderCompleted() => _activeOrders.Add(-1);
public void SetQueueDepth(double depth) => _queueDepth.Record(depth);
}
// Registration
builder.Services.AddSingleton<OrderMetrics>();将指标类注册为单例。通过DI处理的销毁。
IMeterFactoryMetercsharp
public sealed class OrderMetrics
{
private readonly Counter<int> _ordersCreated;
private readonly Histogram<double> _orderDuration;
private readonly UpDownCounter<int> _activeOrders;
private readonly Gauge<double> _queueDepth;
public OrderMetrics(IMeterFactory meterFactory)
{
var meter = meterFactory.Create("MyApp.Orders");
_ordersCreated = meter.CreateCounter<int>(
"myapp.orders.created", "{orders}", "Number of orders created");
_orderDuration = meter.CreateHistogram<double>(
"myapp.orders.duration", "s", "Order processing duration",
advice: new InstrumentAdvice<double>
{
HistogramBucketBoundaries = [0.01, 0.05, 0.1, 0.5, 1, 5, 10]
});
_activeOrders = meter.CreateUpDownCounter<int>(
"myapp.orders.active", "{orders}", "Currently active orders");
_queueDepth = meter.CreateGauge<double>(
"myapp.orders.queue_depth", "{items}", "Current queue depth");
}
public void OrderCreated() => _ordersCreated.Add(1);
public void RecordDuration(double seconds) => _orderDuration.Record(seconds);
public void OrderStarted() => _activeOrders.Add(1);
public void OrderCompleted() => _activeOrders.Add(-1);
public void SetQueueDepth(double depth) => _queueDepth.Record(depth);
}
// 注册
builder.Services.AddSingleton<OrderMetrics>();Multi-Dimensional Metric Tags
多维指标标签
Three or fewer tags are allocation-free. For more, use .
TagListcsharp
// Allocation-free (3 or fewer tags)
_ordersCreated.Add(1,
new KeyValuePair<string, object?>("order.type", "standard"),
new KeyValuePair<string, object?>("payment.method", "credit_card"));
// 4+ tags — use TagList to avoid allocations
var tags = new TagList
{
{ "order.type", "standard" },
{ "payment.method", "credit_card" },
{ "region", "us-east" },
{ "priority", "high" }
};
_ordersCreated.Add(1, tags);三个及以下标签无需分配内存。超过三个时,请使用。
TagListcsharp
// 无内存分配(3个及以下标签)
_ordersCreated.Add(1,
new KeyValuePair<string, object?>("order.type", "standard"),
new KeyValuePair<string, object?>("payment.method", "credit_card"));
// 4个及以上标签 — 使用TagList避免内存分配
var tags = new TagList
{
{ "order.type", "standard" },
{ "payment.method", "credit_card" },
{ "region", "us-east" },
{ "priority", "high" }
};
_ordersCreated.Add(1, tags);Custom ActivitySource for Distributed Tracing
用于分布式追踪的自定义ActivitySource
csharp
public sealed class OrderService(ILogger<OrderService> logger)
{
private static readonly ActivitySource Source = new("MyApp.Orders");
public async Task<Order> ProcessOrderAsync(CreateOrderRequest request, CancellationToken ct)
{
using var activity = Source.StartActivity("ProcessOrder", ActivityKind.Internal);
activity?.SetTag("order.customer_id", request.CustomerId);
try
{
await ValidateOrder(request, ct);
activity?.AddEvent(new ActivityEvent("OrderValidated"));
var order = await SaveOrder(request, ct);
activity?.SetTag("order.id", order.Id.ToString());
activity?.SetStatus(ActivityStatusCode.Ok);
return order;
}
catch (Exception ex)
{
activity?.SetStatus(ActivityStatusCode.Error, ex.Message);
activity?.RecordException(ex);
throw;
}
}
}Register the source: in the tracing builder.
.AddSource("MyApp.Orders")csharp
public sealed class OrderService(ILogger<OrderService> logger)
{
private static readonly ActivitySource Source = new("MyApp.Orders");
public async Task<Order> ProcessOrderAsync(CreateOrderRequest request, CancellationToken ct)
{
using var activity = Source.StartActivity("ProcessOrder", ActivityKind.Internal);
activity?.SetTag("order.customer_id", request.CustomerId);
try
{
await ValidateOrder(request, ct);
activity?.AddEvent(new ActivityEvent("OrderValidated"));
var order = await SaveOrder(request, ct);
activity?.SetTag("order.id", order.Id.ToString());
activity?.SetStatus(ActivityStatusCode.Ok);
return order;
}
catch (Exception ex)
{
activity?.SetStatus(ActivityStatusCode.Error, ex.Message);
activity?.RecordException(ex);
throw;
}
}
}在追踪构建器中注册源:。
.AddSource("MyApp.Orders")Aspire Dashboard for Local Development
用于本地开发的Aspire Dashboard
Run the standalone Aspire Dashboard without Aspire orchestration:
bash
docker run --rm -it -p 18888:18888 -p 4317:18889 \
mcr.microsoft.com/dotnet/aspire-dashboard:latestThen point your app at it:
OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317Dashboard UI is at .
http://localhost:18888无需Aspire编排即可运行独立的Aspire Dashboard:
bash
docker run --rm -it -p 18888:18888 -p 4317:18889 \
mcr.microsoft.com/dotnet/aspire-dashboard:latest随后将应用指向该地址:
OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317Dashboard UI地址为。
http://localhost:18888Source-Generated Logging with OTel
结合OTel的源代码生成式日志
For maximum performance, use — eliminates boxing and allocations.
[LoggerMessage]csharp
public partial class OrderService(ILogger<OrderService> logger)
{
[LoggerMessage(Level = LogLevel.Information,
Message = "Processing order {OrderId} for customer {CustomerId}")]
partial void LogOrderProcessing(Guid orderId, Guid customerId);
}OpenTelemetry logging automatically includes and when an is current.
TraceIdSpanIdActivity为追求极致性能,使用 — 消除装箱操作与内存分配。
[LoggerMessage]csharp
public partial class OrderService(ILogger<OrderService> logger)
{
[LoggerMessage(Level = LogLevel.Information,
Message = "Processing order {OrderId} for customer {CustomerId}")]
partial void LogOrderProcessing(Guid orderId, Guid customerId);
}当存在当前时,OpenTelemetry日志会自动包含和。
ActivityTraceIdSpanIdAnti-patterns
反模式
Don't Create Meters Per Request
切勿为每个请求创建Meter
csharp
// BAD — new Meter per request causes memory leaks
public void HandleRequest()
{
var meter = new Meter("MyApp");
meter.CreateCounter<int>("requests").Add(1);
}
// GOOD — singleton via IMeterFactory
public class MyMetrics(IMeterFactory meterFactory)
{
private readonly Counter<int> _requests =
meterFactory.Create("MyApp").CreateCounter<int>("myapp.requests");
public void RequestHandled() => _requests.Add(1);
}csharp
// 错误 — 每个请求创建新Meter会导致内存泄漏
public void HandleRequest()
{
var meter = new Meter("MyApp");
meter.CreateCounter<int>("requests").Add(1);
}
// 正确 — 通过IMeterFactory实现单例
public class MyMetrics(IMeterFactory meterFactory)
{
private readonly Counter<int> _requests =
meterFactory.Create("MyApp").CreateCounter<int>("myapp.requests");
public void RequestHandled() => _requests.Add(1);
}Don't Skip Null Checks on Activity
切勿跳过Activity的空值检查
csharp
// BAD — NullReferenceException when no listener is attached
using var activity = source.StartActivity("Work");
activity.SetTag("key", "value");
// GOOD — null-safe
activity?.SetTag("key", "value");csharp
// 错误 — 无监听器附着时会抛出NullReferenceException
using var activity = source.StartActivity("Work");
activity.SetTag("key", "value");
// 正确 — 空安全写法
activity?.SetTag("key", "value");Don't Use High-Cardinality Metric Tags
切勿使用高基数指标标签
csharp
// BAD — unbounded cardinality causes memory explosion in collectors
_counter.Add(1, new("request.id", Guid.NewGuid().ToString()));
_counter.Add(1, new("user.id", userId));
// GOOD — low-cardinality dimensions only
_counter.Add(1, new("http.method", "GET"), new("http.status_code", 200));csharp
// 错误 — 无限基数会导致收集器内存激增
_counter.Add(1, new("request.id", Guid.NewGuid().ToString()));
_counter.Add(1, new("user.id", userId));
// 正确 — 仅使用低基数维度
_counter.Add(1, new("http.method", "GET"), new("http.status_code", 200));Don't Mix UseOtlpExporter with AddOtlpExporter
切勿混用UseOtlpExporter与AddOtlpExporter
csharp
// BAD — throws NotSupportedException at runtime
builder.Services.AddOpenTelemetry()
.UseOtlpExporter()
.WithTracing(t => t.AddOtlpExporter());
// GOOD — use one approach
builder.Services.AddOpenTelemetry().UseOtlpExporter();csharp
// 错误 — 运行时会抛出NotSupportedException
builder.Services.AddOpenTelemetry()
.UseOtlpExporter()
.WithTracing(t => t.AddOtlpExporter());
// 正确 — 使用其中一种方式
builder.Services.AddOpenTelemetry().UseOtlpExporter();Don't Forget to Register Custom Sources
切勿忘记注册自定义源
csharp
// BAD — activities silently dropped (no listener registered)
var source = new ActivitySource("MyApp.Custom");
using var activity = source.StartActivity("Work"); // null!
// GOOD — register in the tracing builder
otel.WithTracing(t => t.AddSource("MyApp.Custom"));
otel.WithMetrics(m => m.AddMeter("MyApp.Custom"));csharp
// 错误 — Activity会被静默丢弃(未注册监听器)
var source = new ActivitySource("MyApp.Custom");
using var activity = source.StartActivity("Work"); // 返回null!
// 正确 — 在追踪构建器中注册
otel.WithTracing(t => t.AddSource("MyApp.Custom"));
otel.WithMetrics(m => m.AddMeter("MyApp.Custom"));Decision Guide
决策指南
| Scenario | Recommendation |
|---|---|
| Full observability setup | |
| Custom business metrics | |
| Custom trace spans | |
| Local development backend | Aspire Dashboard standalone container |
| Production backend | OTel Collector as intermediary to Grafana/Datadog/etc. |
| Sampling in production | |
| High-performance logging | |
| Metric tag cardinality | Max ~1000 combinations per instrument |
| Environment configuration | |
| 场景 | 推荐方案 |
|---|---|
| 完整可观测性配置 | |
| 自定义业务指标 | |
| 自定义追踪Span | |
| 本地开发后端 | 独立容器运行Aspire Dashboard |
| 生产环境后端 | 使用OTel Collector作为中间件对接Grafana/Datadog等平台 |
| 生产环境采样 | 设置 |
| 高性能日志 | 使用 |
| 指标标签基数 | 每个指标工具最多保留约1000种标签组合 |
| 环境配置 | 使用 |