mcpField
D
dotnet-observability

dotnet-observability

github.com/wshaddix云与 DevOps8680 次安装35 周前更新
代码仓库

关于 dotnet-observability

面向 .NET 应用开发者的可观测性实践指南,适用于生产监控和诊断。内容整合 OpenTelemetry 跟踪与指标、结构化日志及健康检查模式。

主要功能

  1. 01配置并导出 OpenTelemetry 数据
  2. 02分布式跟踪埋点
  3. 03内置指标与自定义指标
  4. 04结构化日志实践
  5. 05健康检查模式

适用场景

  1. 01为 HTTP 服务添加跟踪和指标
  2. 02记录结构化应用日志
  3. 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.RuntimeGC、线程池、程序集指标
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 概念
ActivitySourceTracer
ActivitySpan
Activity.SetTagSpan 属性
Activity.AddEventSpan 事件
Activity.SetStatusSpan 状态
自定义追踪
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.Hostinghttp.server.request.duration、http.server.active_requests
Microsoft.AspNetCore.Routingaspnetcore.routing.match_attempts
System.Net.Httphttp.client.request.duration、http.client.active_requests
System.Runtimeprocess.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.AspNetCoreUseSerilog() 主机集成 + UseSerilogRequestLogging()
Serilog.Settings.ConfigurationReadFrom.Configuration(),用于绑定 appsettings.json
Serilog.Sinks.OpenTelemetryWriteTo.OpenTelemetry() OTLP 接收器
Serilog.Formatting.Compact用于结构化控制台输出的 RenderedCompactJsonFormatter
Serilog.Enrichers.EnvironmentEnrich.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.ClientUIResponseWriter.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 注意事项

  1. 在已注册到 DI 的服务中,不要使用 new 创建 Meter 或 ActivitySource,而不使用 IMeterFactory——在工厂之外创建的检测项不会被 OpenTelemetry SDK 收集。使用 IMeterFactory.Create() 创建 Meter 实例。ActivitySource 是静态的,并通过 .AddSource() 注册。
  2. 不要在存活检查端点中添加依赖项检查——数据库故障不应导致应用重启。只有就绪检查端点才应检查依赖项。
  3. 不要使用 ILogger.LogInformation("message: " + value) 或字符串插值 $"message: {value}"——请使用结构化日志模板:ILogger.LogInformation("message: {Value}", value)。字符串拼接和插值会绕过结构化日志,并妨碍日志索引。
  4. 不要在生产环境中于代码内配置 OTLP 端点——请使用环境变量(OTEL_EXPORTER_OTLP_ENDPOINT),确保同一映像可用于不同环境。
  5. 不要忘记使用 .AddSource("MyApp.*") 注册自定义 ActivitySource 名称——未注册的来源会被静默忽略,不会生成跟踪。

参考资料


归属说明

改编自 Aaronontheweb/dotnet-skills(MIT 许可证)。

评论

支持 Markdown