关于 dotnet-observability
面向 .NET 应用开发者的可观测性实践指南,适用于生产监控和诊断。内容整合 OpenTelemetry 跟踪与指标、结构化日志及健康检查模式。
主要功能
- 01配置并导出 OpenTelemetry 数据
- 02分布式跟踪埋点
- 03内置指标与自定义指标
- 04结构化日志实践
- 05健康检查模式
适用场景
- 01为 HTTP 服务添加跟踪和指标
- 02记录结构化应用日志
- 03通过健康检查判断服务是否可接收流量
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ wshaddix/ dotnet-skills --skill dotnet-observability安装后在 Claude Code 中输入/dotnet-observability确认已加载。
详细介绍
dotnet-observability
使用 OpenTelemetry、结构化日志记录、运行状况检查和自定义指标,为 .NET 应用程序提供现代化的可观测性。涵盖可观测性的三大支柱(追踪、指标、日志)、与 Microsoft.Extensions.Diagnostics 和 System.Diagnostics 的集成,以及适用于生产环境的运行状况检查模式。
不在范围内: DI 容器机制和服务生命周期——参见 [skill:dotnet-csharp-dependency-injection]。Async/await 模式——参见 [skill:dotnet-csharp-async-patterns]。测试可观测性输出——参见 [skill:dotnet-integration-testing],了解如何在集成测试中验证遥测数据。遥测数据收集的 CI/CD 管道集成——参见 [skill:dotnet-gha-patterns] 和 [skill:dotnet-ado-patterns]。中间件管道模式(请求日志记录中间件、异常处理中间件)——参见 [skill:dotnet-middleware-patterns]。
交叉引用:[skill:dotnet-csharp-dependency-injection] 介绍服务注册,[skill:dotnet-csharp-async-patterns] 介绍后台导出器中的异步模式,[skill:dotnet-resilience] 介绍 Polly 遥测集成,[skill:dotnet-middleware-patterns] 介绍请求/异常日志记录中间件。
OpenTelemetry 设置
OpenTelemetry 是 .NET 中的标准可观测性框架。.NET SDK 原生支持 System.Diagnostics.Activity(追踪)和 System.Diagnostics.Metrics(指标),OpenTelemetry 会收集并导出这些数据。
包概览
| 包 | 用途 |
|---|---|
OpenTelemetry.Extensions.Hosting | 主机集成、生命周期管理 |
OpenTelemetry.Instrumentation.AspNetCore | 自动检测 HTTP 服务端追踪/指标 |
OpenTelemetry.Instrumentation.Http | 自动检测 HttpClient 追踪/指标 |
OpenTelemetry.Instrumentation.Runtime | GC、线程池、程序集指标 |
OpenTelemetry.Exporter.OpenTelemetryProtocol | 用于收集器的 OTLP 导出器(gRPC/HTTP) |
OpenTelemetry.Exporter.Console | 用于本地开发的控制台导出器 |
安装核心组件:
<PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.*" />
<PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.*" />
<PackageReference Include="OpenTelemetry.Instrumentation.Http" Version="1.*" />
<PackageReference Include="OpenTelemetry.Instrumentation.Runtime" Version="1.*" />
<PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.*" />
Aspire 服务默认配置集成
如果使用 .NET Aspire,ServiceDefaults 项目会自动配置 OpenTelemetry。这是 Aspire 应用的推荐方式——请勿手动重复此配置:
// ServiceDefaults/Extensions.cs (generated by Aspire)
public static IHostApplicationBuilder AddServiceDefaults(
this IHostApplicationBuilder builder)
{
builder.ConfigureOpenTelemetry();
builder.AddDefaultHealthChecks();
// ... other defaults
return builder;
}
对于非 Aspire 应用,请按下文所示显式配置 OpenTelemetry。
完整配置(非 Aspire)
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddOpenTelemetry()
.ConfigureResource(resource => resource
.AddService(
serviceName: builder.Environment.ApplicationName,
serviceVersion: typeof(Program).Assembly
.GetCustomAttribute<AssemblyInformationalVersionAttribute>()
?.InformationalVersion ?? "unknown"))
.WithTracing(tracing => tracing
.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation()
.AddSource("MyApp.*") // Custom ActivitySources
.AddOtlpExporter())
.WithMetrics(metrics => metrics
.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation()
.AddRuntimeInstrumentation()
.AddMeter("MyApp.*") // Custom Meters
.AddOtlpExporter());
通过环境变量配置 OTLP
OTLP 导出器会读取标准环境变量,因此不同环境之间无需更改代码:
# Collector endpoint (gRPC default)
OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317
# Or HTTP/protobuf
OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf
OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318
# Resource attributes
OTEL_RESOURCE_ATTRIBUTES=deployment.environment=production,service.namespace=myapp
# Service name (overrides code-based configuration)
OTEL_SERVICE_NAME=order-api
分布式追踪
.NET 追踪的工作原理
.NET 使用 System.Diagnostics.Activity 作为原生追踪基元。OpenTelemetry 会将其映射为 Span:
| .NET 概念 | OpenTelemetry 概念 |
|---|---|
ActivitySource | Tracer |
Activity | Span |
Activity.SetTag | Span 属性 |
Activity.AddEvent | Span 事件 |
Activity.SetStatus | Span 状态 |
自定义追踪
public sealed class OrderService
{
// One ActivitySource per logical component, named after the namespace
private static readonly ActivitySource s_activitySource = new("MyApp.Orders");
public async Task<Order> CreateOrderAsync(
CreateOrderRequest request,
CancellationToken ct)
{
using var activity = s_activitySource.StartActivity(
"CreateOrder",
ActivityKind.Internal);
activity?.SetTag("order.customer_id", request.CustomerId);
activity?.SetTag("order.line_count", request.Lines.Count);
var order = new Order { /* ... */ };
activity?.AddEvent(new ActivityEvent("OrderValidated"));
await _db.Orders.AddAsync(order, ct);
await _db.SaveChangesAsync(ct);
activity?.SetTag("order.id", order.Id);
activity?.SetStatus(ActivityStatusCode.Ok);
return order;
}
}
追踪上下文传播
W3C Trace Context 是 .NET 中默认的传播格式。它可通过 HttpClient 自动跨 HTTP 边界传播:
// Trace context is automatically propagated via traceparent/tracestate headers
// when using HttpClient with OpenTelemetry.Instrumentation.Http.
// No manual propagation needed for HTTP-based communication.
对于基于消息的通信(队列、事件总线),请显式传播上下文:
// Producer: inject context into message headers
var propagator = Propagators.DefaultTextMapPropagator;
var carrier = new Dictionary<string, string>();
var currentActivity = Activity.Current;
if (currentActivity is not null)
{
propagator.Inject(
new PropagationContext(currentActivity.Context, Baggage.Current),
carrier,
(dict, key, value) => dict[key] = value);
}
// Attach carrier as message headers
// Consumer: extract context from message headers
var parentContext = propagator.Extract(
default,
messageHeaders,
(headers, key) => headers.TryGetValue(key, out var value)
? [value] : []);
using var activity = s_activitySource.StartActivity(
"ProcessMessage",
ActivityKind.Consumer,
parentContext.ActivityContext);
指标
内置指标
配置 OpenTelemetry 检测后,ASP.NET Core 和 HttpClient 会自动发出指标:
| Meter | 关键指标 |
|---|---|
Microsoft.AspNetCore.Hosting | http.server.request.duration、http.server.active_requests |
Microsoft.AspNetCore.Routing | aspnetcore.routing.match_attempts |
System.Net.Http | http.client.request.duration、http.client.active_requests |
System.Runtime | process.runtime.dotnet.gc.collections.count、process.runtime.dotnet.threadpool.threads.count |
自定义指标
使用 System.Diagnostics.Metrics 创建应用特定的指标:
public sealed class OrderMetrics
{
// One Meter per logical component
private readonly Counter<long> _ordersCreated;
private readonly Histogram<double> _orderProcessingDuration;
private readonly UpDownCounter<long> _activeOrders;
public OrderMetrics(IMeterFactory meterFactory)
{
var meter = meterFactory.Create("MyApp.Orders");
_ordersCreated = meter.CreateCounter<long>(
"myapp.orders.created",
unit: "{order}",
description: "Number of orders created");
_orderProcessingDuration = meter.CreateHistogram<double>(
"myapp.orders.processing_duration",
unit: "s",
description: "Time to process an order");
_activeOrders = meter.CreateUpDownCounter<long>(
"myapp.orders.active",
unit: "{order}",
description: "Number of orders currently being processed");
}
public void RecordOrderCreated(string region)
{
_ordersCreated.Add(1, new KeyValuePair<string, object?>("region", region));
}
public void RecordProcessingDuration(double seconds)
{
_orderProcessingDuration.Record(seconds);
}
public void IncrementActiveOrders() => _activeOrders.Add(1);
public void DecrementActiveOrders() => _activeOrders.Add(-1);
}
在 DI 中注册指标类:
builder.Services.AddSingleton<OrderMetrics>();
指标命名约定
遵循 OpenTelemetry 语义约定:
- 使用小写字母,并以点分隔:
myapp.orders.created - 使用规范规定的单位:
s(秒)、ms(毫秒)、By(字节)、{request}(无量纲) - 使用应用/服务名称作为前缀:
myapp.* - 在各项指标中使用一致的标签名称:
region、status、order.type
结构化日志
Microsoft.Extensions.Logging(内置)
内置日志框架原生支持结构化日志。使用编译时源生成器实现高性能日志记录:
public static partial class Log
{
[LoggerMessage(
Level = LogLevel.Information,
Message = "Order {OrderId} created for customer {CustomerId} with {LineCount} items, total {Total:C}")]
public static partial void OrderCreated(
this ILogger logger,
string orderId,
string customerId,
int lineCount,
decimal total);
[LoggerMessage(
Level = LogLevel.Warning,
Message = "Order {OrderId} processing exceeded threshold: {Duration}ms")]
public static partial void OrderProcessingSlow(
this ILogger logger,
string orderId,
double duration);
[LoggerMessage(
Level = LogLevel.Error,
Message = "Failed to process order {OrderId}")]
public static partial void OrderProcessingFailed(
this ILogger logger,
Exception exception,
string orderId);
}
// Usage
logger.OrderCreated(order.Id, order.CustomerId, order.Lines.Count, order.Total);
使用源生成日志记录的原因
- 对已禁用的日志级别实现 零分配(在调用点检查)
- 对消息模板和参数进行 编译时验证
- 默认采用 结构化格式 —— 参数会成为日志事件中的命名属性
LoggerMessage.Define(旧版 / .NET 6 之前)
在源生成器推出之前(.NET 5 及更早版本),可使用 LoggerMessage.Define 实现同样的零分配优势。此方法在现代 .NET 中仍然有效,适用于非 partial 类或面向较旧框架的场景:
public static class LogMessages
{
private static readonly Action<ILogger, string, int, Exception?> s_orderCreated =
LoggerMessage.Define<string, int>(
LogLevel.Information,
new EventId(1, nameof(OrderCreated)),
"Order {OrderId} created with {LineCount} items");
public static void OrderCreated(
ILogger logger, string orderId, int lineCount)
=> s_orderCreated(logger, orderId, lineCount, null);
private static readonly Action<ILogger, string, Exception?> s_orderFailed =
LoggerMessage.Define<string>(
LogLevel.Error,
new EventId(2, nameof(OrderFailed)),
"Failed to process order {OrderId}");
public static void OrderFailed(
ILogger logger, string orderId, Exception exception)
=> s_orderFailed(logger, orderId, exception);
}
面向 .NET 6+ 的新代码优先使用 [LoggerMessage] 源生成器。仅在无法使用源生成器时使用 LoggerMessage.Define。
消息模板:应做与不应做
消息模板使用命名占位符,这些占位符会成为结构化属性。这是结构化日志的基础——违反此规则会导致无法为日志编制索引和进行搜索。
// CORRECT: structured message template with named placeholders
logger.LogInformation("Order {OrderId} shipped to {City}", orderId, city);
// WRONG: string interpolation -- bypasses structured logging entirely
logger.LogInformation($"Order {orderId} shipped to {city}");
// WRONG: string concatenation -- same problem
logger.LogInformation("Order " + orderId + " shipped to " + city);
// WRONG: ToString() in template -- loses type information
logger.LogInformation("Order {OrderId} shipped at {Time}",
orderId, DateTime.UtcNow.ToString("o")); // pass DateTime directly
// CORRECT: pass objects directly, let the formatter handle rendering
logger.LogInformation("Order {OrderId} shipped at {ShippedAt}",
orderId, DateTime.UtcNow);
日志级别最佳实践
| 级别 | 使用场景 | 示例 |
|---|---|---|
Trace | 详细的诊断信息(方法进入/退出、变量值) | Entering ProcessOrder with {OrderId} |
Debug | 开发期间有用的应用内部状态 | Cache hit for product {ProductId} |
Information | 正常的应用流程、业务事件 | Order {OrderId} created successfully |
Warning | 不会阻止操作的意外情况 | Retry {Attempt} for external API call |
Error | 影响当前操作的故障 | Failed to save order {OrderId} |
Critical | 需要立即处理的应用级故障 | Database connection pool exhausted |
日志筛选(Microsoft.Extensions.Logging)
在 appsettings.json 中配置日志级别筛选,在保留所需级别的应用日志的同时,抑制过于频繁的框架日志:
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning",
"Microsoft.AspNetCore.HttpLogging": "Information",
"Microsoft.EntityFrameworkCore.Database.Command": "Warning",
"System.Net.Http.HttpClient": "Warning",
"MyApp": "Debug"
},
"Console": {
"LogLevel": {
"Default": "Warning"
}
}
}
}
主要筛选规则:
- 匹配最具体的类别优先 —— 若没有更具体的覆盖项,
MyApp.Orders将匹配MyApp - 提供程序级覆盖 —— 上方的
Console部分仅覆盖控制台提供程序的默认设置 - 环境覆盖 —— 使用
appsettings.Development.json在本地启用Debug/Trace,而不影响生产环境
用于关联的日志作用域
public async Task<Order> ProcessOrderAsync(
string orderId,
CancellationToken ct)
{
using var scope = _logger.BeginScope(
new Dictionary<string, object>
{
["OrderId"] = orderId,
["CorrelationId"] = Activity.Current?.TraceId.ToString() ?? ""
});
// All log messages within this scope include OrderId and CorrelationId
_logger.LogInformation("Starting order processing");
// ...
}
Serilog 集成
对于高级接收器(Elasticsearch、Seq、Datadog),Serilog 是标准的结构化日志库。
| 包 | 用途 |
|---|---|
Serilog.AspNetCore | UseSerilog() 主机集成 + UseSerilogRequestLogging() |
Serilog.Settings.Configuration | ReadFrom.Configuration(),用于绑定 appsettings.json |
Serilog.Sinks.OpenTelemetry | WriteTo.OpenTelemetry() OTLP 接收器 |
Serilog.Formatting.Compact | 用于结构化控制台输出的 RenderedCompactJsonFormatter |
Serilog.Enrichers.Environment | Enrich.WithMachineName() 和 Enrich.WithEnvironmentName() |
// Program.cs
builder.Host.UseSerilog((context, loggerConfiguration) =>
{
loggerConfiguration
.ReadFrom.Configuration(context.Configuration)
.Enrich.FromLogContext()
.Enrich.WithMachineName()
.Enrich.WithEnvironmentName()
.WriteTo.Console(new RenderedCompactJsonFormatter())
.WriteTo.OpenTelemetry(options =>
{
options.Endpoint = context.Configuration["OTEL_EXPORTER_OTLP_ENDPOINT"]
?? "http://localhost:4317";
options.Protocol = OtlpProtocol.Grpc;
});
});
// Use Serilog request logging instead of the built-in one
app.UseSerilogRequestLogging(options =>
{
options.EnrichDiagnosticContext = (diagnosticContext, httpContext) =>
{
diagnosticContext.Set("RequestHost", httpContext.Request.Host.Value);
diagnosticContext.Set("UserAgent", httpContext.Request.Headers.UserAgent.ToString());
};
});
通过 appsettings.json 配置:
{
"Serilog": {
"MinimumLevel": {
"Default": "Information",
"Override": {
"Microsoft.AspNetCore": "Warning",
"Microsoft.EntityFrameworkCore.Database.Command": "Warning",
"System.Net.Http.HttpClient": "Warning"
}
}
}
}
如何在 MS.Extensions.Logging 和 Serilog 之间选择
| 场景 | 建议 |
|---|---|
| 仅需控制台 + OTLP 导出 | Microsoft.Extensions.Logging + OpenTelemetry 导出器 |
| 需要 Elasticsearch、Seq 或 Datadog 接收器 | Serilog |
| .NET Aspire 应用 | 使用内置日志记录(Aspire 会自动配置 OTLP) |
| 高吞吐量、最小化分配 | 源生成的 LoggerMessage(两者均支持) |
健康检查
健康检查可让编排器(Kubernetes、Docker、负载均衡器)判断应用是否已准备好接收流量。
健康检查包
内置的 Microsoft.Extensions.Diagnostics.HealthChecks 包提供核心框架。来自 Xabaril/AspNetCore.Diagnostics.HealthChecks 的社区包则添加了针对特定提供程序的检查:
| 包 | 扩展方法 |
|---|---|
AspNetCore.HealthChecks.Npgsql | .AddNpgSql() |
AspNetCore.HealthChecks.Redis | .AddRedis() |
AspNetCore.HealthChecks.Uris | .AddUrlGroup() |
AspNetCore.HealthChecks.UI.Client | UIResponseWriter.WriteHealthCheckUIResponse |
基本健康检查
builder.Services.AddHealthChecks()
.AddCheck("self", () => HealthCheckResult.Healthy(), tags: ["live"])
.AddNpgSql(
builder.Configuration.GetConnectionString("DefaultConnection")!,
name: "database",
tags: ["ready"])
.AddRedis(
builder.Configuration.GetConnectionString("Redis")!,
name: "redis",
tags: ["ready"])
.AddUrlGroup(
new Uri("https://api.external.com/health"),
name: "external-api",
tags: ["ready"]);
var app = builder.Build();
// Liveness: is the process running? (don't check dependencies)
app.MapHealthChecks("/health/live", new HealthCheckOptions
{
Predicate = check => check.Tags.Contains("live")
});
// Readiness: can the process serve traffic? (check dependencies)
app.MapHealthChecks("/health/ready", new HealthCheckOptions
{
Predicate = check => check.Tags.Contains("ready"),
ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse
});
自定义健康检查
public sealed class DiskSpaceHealthCheck(
IOptions<DiskSpaceOptions> options) : IHealthCheck
{
public Task<HealthCheckResult> CheckHealthAsync(
HealthCheckContext context,
CancellationToken ct = default)
{
var drive = new DriveInfo(options.Value.DrivePath);
var freeSpaceMb = drive.AvailableFreeSpace / (1024 * 1024);
var data = new Dictionary<string, object>
{
["FreeSpaceMB"] = freeSpaceMb,
["DrivePath"] = options.Value.DrivePath
};
if (freeSpaceMb < options.Value.MinimumFreeSpaceMb)
{
return Task.FromResult(HealthCheckResult.Unhealthy(
$"Low disk space: {freeSpaceMb}MB remaining", data: data));
}
return Task.FromResult(HealthCheckResult.Healthy(
$"Disk space OK: {freeSpaceMb}MB free", data: data));
}
}
// Registration
builder.Services.AddHealthChecks()
.AddCheck<DiskSpaceHealthCheck>("disk-space", tags: ["ready"]);
存活检查与就绪检查
| 检查 | 目的 | 失败时的操作 | 示例 |
|---|---|---|---|
存活检查(/health/live) | 进程是否健康? | 重启容器 | 自检、死锁检测 |
就绪检查(/health/ready) | 进程能否接收流量? | 从负载均衡器中移除 | 数据库、Redis、外部 API |
重要: 存活检查不应包含依赖项检查。如果数据库宕机,重启应用也无法修复数据库。因依赖项问题而失败的存活检查会导致级联重启。
发布健康检查结果
HealthCheckPublisherOptions 控制定期评估的计划。要将结果推送到监控系统,请注册一个 IHealthCheckPublisher 实现:
builder.Services.AddHealthChecks()
.AddCheck("self", () => HealthCheckResult.Healthy());
// Configure periodic evaluation schedule
builder.Services.Configure<HealthCheckPublisherOptions>(options =>
{
options.Delay = TimeSpan.FromSeconds(5); // Initial delay before first run
options.Period = TimeSpan.FromSeconds(30); // Interval between evaluations
});
// Register a publisher to push results (e.g., to logs, metrics, or external systems)
builder.Services.AddSingleton<IHealthCheckPublisher, LoggingHealthCheckPublisher>();
记录健康状态的最简发布器:
public sealed class LoggingHealthCheckPublisher(
ILogger<LoggingHealthCheckPublisher> logger) : IHealthCheckPublisher
{
public Task PublishAsync(
HealthReport report, CancellationToken ct)
{
logger.LogInformation(
"Health check: {Status} ({TotalDuration}ms)",
report.Status,
report.TotalDuration.TotalMilliseconds);
return Task.CompletedTask;
}
}
综合应用:生产环境配置
适用于生产环境 .NET API 的完整可观测性配置:
var builder = WebApplication.CreateBuilder(args);
// 1. OpenTelemetry -- traces, metrics, logs
builder.Services.AddOpenTelemetry()
.ConfigureResource(resource => resource
.AddService(builder.Environment.ApplicationName))
.WithTracing(tracing => tracing
.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation()
.AddSource("MyApp.*")
.AddOtlpExporter())
.WithMetrics(metrics => metrics
.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation()
.AddRuntimeInstrumentation()
.AddMeter("MyApp.*")
.AddOtlpExporter());
// 2. Structured logging with OpenTelemetry export
builder.Logging.AddOpenTelemetry(logging =>
{
logging.IncludeScopes = true;
logging.IncludeFormattedMessage = true;
logging.AddOtlpExporter();
});
// 3. Health checks
builder.Services.AddHealthChecks()
.AddCheck("self", () => HealthCheckResult.Healthy(), tags: ["live"])
.AddNpgSql(
builder.Configuration.GetConnectionString("DefaultConnection")!,
name: "database",
tags: ["ready"]);
// 4. Custom application metrics
builder.Services.AddSingleton<OrderMetrics>();
var app = builder.Build();
app.MapHealthChecks("/health/live", new HealthCheckOptions
{
Predicate = check => check.Tags.Contains("live")
});
app.MapHealthChecks("/health/ready", new HealthCheckOptions
{
Predicate = check => check.Tags.Contains("ready")
});
app.Run();
核心原则
- 将 OpenTelemetry 用作标准——它提供与厂商无关的检测,可与任何后端配合使用(Prometheus、Grafana、Datadog、Azure Monitor、AWS X-Ray)
- 使用来自 DI 的
IMeterFactory——不要直接创建Meter实例;该工厂会与 DI 生命周期和 OpenTelemetry 注册集成 - 在高频路径中使用源生成的
LoggerMessage——日志级别禁用时可实现零分配 - 将存活检查与就绪检查分开——存活检查不应包含依赖项健康状况;就绪检查则应包含
- 通过环境变量进行配置——OTLP 端点、服务名称和资源属性不应硬编码
- 使用跟踪上下文丰富日志——包含
TraceId和SpanId的结构化日志可关联日志与跟踪 - 遵循 OpenTelemetry 语义约定来命名指标和跨度
Agent 注意事项
- 在已注册到 DI 的服务中,不要使用
new创建Meter或ActivitySource,而不使用IMeterFactory——在工厂之外创建的检测项不会被 OpenTelemetry SDK 收集。使用IMeterFactory.Create()创建Meter实例。ActivitySource是静态的,并通过.AddSource()注册。 - 不要在存活检查端点中添加依赖项检查——数据库故障不应导致应用重启。只有就绪检查端点才应检查依赖项。
- 不要使用
ILogger.LogInformation("message: " + value)或字符串插值$"message: {value}"——请使用结构化日志模板:ILogger.LogInformation("message: {Value}", value)。字符串拼接和插值会绕过结构化日志,并妨碍日志索引。 - 不要在生产环境中于代码内配置 OTLP 端点——请使用环境变量(
OTEL_EXPORTER_OTLP_ENDPOINT),确保同一映像可用于不同环境。 - 不要忘记使用
.AddSource("MyApp.*")注册自定义ActivitySource名称——未注册的来源会被静默忽略,不会生成跟踪。
参考资料
- OpenTelemetry .NET 文档
- 使用 OpenTelemetry 实现 .NET 可观测性
- ASP.NET Core 健康检查
- System.Diagnostics.Metrics
- .NET 中的高性能日志记录
- .NET 中的日志记录:日志筛选
- Serilog OpenTelemetry 接收器
归属说明
改编自 Aaronontheweb/dotnet-skills(MIT 许可证)。