健康检查和可观测性

日志告诉我们应用发生了什么,健康检查帮助宿主判断实例是否可用,指标和分布式追踪则用于观察趋势和定位跨服务问题。本篇笔记介绍ASP.NET Core健康检查以及OpenTelemetry的基本接入方式。NLog和日志级别等基础内容可参考软件工程/dotNet/dotNet扩展库/Logging-日志框架和本系列日志章节。

注册健康检查

最简单的健康检查只需要注册服务并映射端点:

builder.Services.AddHealthChecks();

var app = builder.Build();

app.MapHealthChecks("/health");
app.Run();

默认情况下,健康端点会执行所有已注册的检查,并返回Healthy、Degraded或Unhealthy对应的结果。实际项目通常需要区分两类探针:

  • Liveness(存活):进程是否仍然需要被重启,通常只检查进程自身,不应因数据库等外部依赖短暂故障而重启应用。
  • Readiness(就绪):实例当前是否适合接收流量,可以检查数据库、必要配置和关键依赖。

下面使用ready标签筛选就绪检查,并让存活端点不执行外部依赖检查:

using Microsoft.AspNetCore.Diagnostics.HealthChecks;
using Microsoft.Extensions.Diagnostics.HealthChecks;

builder.Services.AddHealthChecks()
    .AddCheck(
        "self",
        () => HealthCheckResult.Healthy("应用正在运行"),
        tags: new[] { "live" })
    .AddCheck(
        "database",
        () => HealthCheckResult.Healthy("数据库可用"),
        tags: new[] { "ready" });

var app = builder.Build();

app.MapHealthChecks("/health/ready", new HealthCheckOptions
{
    Predicate = check => check.Tags.Contains("ready")
});

app.MapHealthChecks("/health/live", new HealthCheckOptions
{
    Predicate = _ => false
});

app.Run();

这里的数据库检查只是展示注册方式,真实项目应执行有超时控制的数据库探测,而不是固定返回健康结果。

自定义健康检查

实现IHealthCheck可以封装依赖检查:

using Microsoft.Extensions.Diagnostics.HealthChecks;

public class ExternalApiHealthCheck : IHealthCheck
{
    private readonly HttpClient _httpClient;

    public ExternalApiHealthCheck(HttpClient httpClient)
    {
        _httpClient = httpClient;
    }

    public async Task<HealthCheckResult> CheckHealthAsync(
        HealthCheckContext context,
        CancellationToken cancellationToken = default)
    {
        try
        {
            using HttpResponseMessage response = await _httpClient.GetAsync(
                "/health", cancellationToken);
            return response.IsSuccessStatusCode
                ? HealthCheckResult.Healthy()
                : HealthCheckResult.Unhealthy($"HTTP {(int)response.StatusCode}");
        }
        catch (Exception ex) when (ex is HttpRequestException or TaskCanceledException)
        {
            return HealthCheckResult.Unhealthy("外部服务不可用", ex);
        }
    }
}

注册检查时可以同时注册其HttpClient:

builder.Services.AddHttpClient<ExternalApiHealthCheck>(client =>
{
    client.BaseAddress = new Uri("https://example.com");
    client.Timeout = TimeSpan.FromSeconds(3);
});
builder.Services.AddHealthChecks()
    .AddCheck<ExternalApiHealthCheck>("external-api", tags: new[] { "ready" });

EF Core、Redis等依赖也有相应的健康检查扩展包。引入时应核对目标框架和提供程序版本,并设置合理超时,避免健康端点本身长时间占用资源。

健康端点可能暴露依赖和拓扑信息,生产环境应根据部署方式限制访问来源,避免在响应中输出连接串和异常堆栈。Kubernetes可以将readiness和liveness探针分别映射到上述端点,具体探针配置可参考笔记库的Kubernetes相关章节。

结构化日志和请求关联

日志应使用消息模板而不是字符串插值:

_logger.LogInformation(
    "查询学生 {StudentId},追踪ID {TraceId}",
    studentId,
    Activity.Current?.TraceId.ToString() ?? HttpContext.TraceIdentifier);

Activity.TraceId是W3C分布式追踪上下文中的追踪ID,HttpContext.TraceIdentifier是当前ASP.NET Core请求的标识,两者不是同一个属性。使用Activity和OpenTelemetry时,可以在日志Scope中输出TraceId、SpanId,将日志与追踪关联起来。密码、令牌、连接串和个人敏感信息不应直接写入日志。

使用OpenTelemetry收集追踪和指标

OpenTelemetry提供统一的追踪、指标和日志接入规范。ASP.NET Core常用组件包括:

  • OpenTelemetry.Extensions.Hosting
  • OpenTelemetry.Instrumentation.AspNetCore
  • OpenTelemetry.Instrumentation.Http
  • OpenTelemetry.Instrumentation.Runtime
  • 根据导出目标选择OpenTelemetry.Exporter.Prometheus.AspNetCore或OpenTelemetry.Exporter.OpenTelemetryProtocol

下面是一个基础配置示例,具体包版本应与目标框架和所选OpenTelemetry版本匹配:

using OpenTelemetry.Metrics;
using OpenTelemetry.Trace;

builder.Services.AddOpenTelemetry()
    .WithTracing(tracing => tracing
        .AddAspNetCoreInstrumentation()
        .AddHttpClientInstrumentation()
        .AddOtlpExporter())
    .WithMetrics(metrics => metrics
        .AddAspNetCoreInstrumentation()
        .AddHttpClientInstrumentation()
        .AddRuntimeInstrumentation()
        .AddPrometheusExporter());

var app = builder.Build();

app.MapPrometheusScrapingEndpoint();
app.Run();

OTLP Exporter通常将数据发送到OpenTelemetry Collector,再由Collector转发到Jaeger、SkyWalking或其他后端;Prometheus Exporter则暴露指标抓取端点。生产环境需要配置导出地址、采样率、资源名称和端点访问控制,不能无差别记录所有高基数标签。

自定义业务追踪可以创建ActivitySource:

using System.Diagnostics;

public static class DemoTelemetry
{
    public static readonly ActivitySource Source = new("DemoWebAPI");
}

using Activity? activity = DemoTelemetry.Source.StartActivity("SyncStudents");
activity?.SetTag("students.count", students.Count);

同时在追踪配置中添加.AddSource(DemoTelemetry.Source.Name),自定义Activity才会被收集。指标则可以使用System.Diagnostics.Metrics.Meter创建Counter、Histogram等仪器。

可观测性实施建议

优先建立少量高价值信号:请求成功率、延迟、错误率、依赖可用性和Trace ID关联。随后再逐步增加业务指标和详细Span。无限制地记录所有请求体、所有SQL参数或高基数用户标签,既可能泄露敏感信息,也会显著增加存储和计算成本。

作者:Gacfox
版权声明:本网站为非盈利性质,文章如非特殊说明均为原创,版权遵循知识共享协议CC BY-NC-ND 4.0进行授权,转载必须署名,禁止用于商业目的或演绎修改后转载。