.net 健康度检查

[官方手册] https://learn.microsoft.com/zh-cn/aspnet/core/host-and-deploy/health-checks?view=aspnetcore-9.0

[一个第三方库,包括各种服务的检测] https://github.com/Xabaril/AspNetCore.Diagnostics.HealthChecks

加入 IHealthCheck 接口实现

  • 实现 IHealthCheck 接口的 CheckHealthAsync 函数, 检查服务,并返回 HealthCheckResult, 使用 AddCheck 注入实现
  • 可以直接调用 接口 ,或是 IHealthCheckPublisher 服务定期调用 或是 使用 UI 组件进行显示
// 如果要单例,可以先注入服务

// 添加 IHealthCheck 服务 
builder.Services.AddHealthChecks()            
    .AddCheck<CustomHealthCheck>("default") // 自定义健康检查, 可指定 tags
    // 调用 AddTypeActivatedCheck 可指定初始化参数
    .AddRedis("localhost:6379", failureStatus: HealthStatus.Degraded) // 第三方Redis健康检查    
    .AddResourceUtilizationHealthCheck() // 内存、CPU 资源利用率健康检查, 微软官方库
            ;
# 一个实现的例子

// <summary>
///     自定义健康检查例子
/// </summary>
/// <param name="logger"></param>
public class CustomHealthCheck(ILogger<CustomHealthCheck> logger) : IHealthCheck
{
    /// <summary>
    ///     生成一个模拟的健康状态
    /// </summary>
    /// <returns></returns>
    private static HealthCheckResult GetHealthResult()
    {
        return Random.Shared.Next(0, 3) switch
        {
            0 => HealthCheckResult.Healthy("Custom health check is healthy."),
            1 => HealthCheckResult.Degraded("Custom health check is degraded."),
            _ => HealthCheckResult.Unhealthy("Custom health check is unhealthy."),
        };
    }

    /// <summary>
    ///     返回健康状态
    /// </summary>
    /// <param name="context"></param>
    /// <param name="cancellationToken"></param>
    /// <returns></returns>
    public Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context,
        CancellationToken cancellationToken = default)
    {
        var result = GetHealthResult();

        logger.LogInformation($"Custom health check is running. : {result.Status}");

        return Task.FromResult(result);
    }
}

对外调用服务

调用 1 app.UseHealthChecks

指定检测服务的调用地址 ,通过接口或是浏览器调用

  • 传入 options 的 ResultStatusCodes 可以指定不同的 HealthCheckResult 返回不同的 HTTP 状态码

  • 传入 options 的 ResponseWriter 可以重写返回的 html 内容

  • 传入 options 的 Predicate 指定调用哪些 IHealthCheck 实现, 比如注入 IHealthCheck 时指定 tag , 在 Predicate 中过滤

  • 传入 options 的 AllowCachingResponses = false (默认值) 让浏览器不缓存检测结果

  • 还可以使用 app.MapHealthChecks

    app.UseHealthChecks("/health", options); // 注册到主管道,每个请求都会被调用, 
    app.MapHealthChecks("/health", options); // 注册一个分支管道,只有请求配置到才会被调用

    可链式调用 .RequireHost("*:5001").RequireAuthorization();

  • 重写输出结果的例子

    // 设置重写输出结果
    app.MapHealthChecks("/health", options => options.ResponseWriter = WriteResponse  ); 
    
    // 简单的将结果格式化为 json 结构
    public static Task WriteResponse(HttpContext httpContext, HealthReport report)
      {
          var status = report.Entries.Select(it =>
              new
              {
                  it.Key,
                  it.Value.Status,
                  value = Enum.GetName(typeof(HealthStatus), it.Value.Status),
                  it.Value.Description,
              });
    
          httpContext.Response.ContentType = "application/json";
          return httpContext.Response.WriteAsJsonAsync(status);
      }

调用 2 注入 IHealthCheckPublisher

应该类似于后台服务,系统后台定期调用注入的 IHealthCheck 生成一个 report, 然后调用 IHealthCheckPublisher.PublishAsync 传入 report , IHealthCheckPublisher 将数据保存到后台系统(比如日志系统中)

  • 可以传入参数,指定检测的时间间隔

    builder.Services.Configure(options =>
    {
      options.Delay = TimeSpan.FromSeconds(2); // 指定
      options.Predicate = healthCheck => healthCheck.Tags.Contains("sample"); // 指定参数针对哪些 IHealthCheckPublisher 
    });
  • 下面的第三方服务 将结果保存至 seq 服务器

    这个第三方服务有一个问题,它在处理 report 时,简单的转为 json 结构,然后保存至 seq 服务器。但是如果 IHealthCheck 调用检测发生过异常,异常中的信息无法被 json 序列化,会报一个异常,导致无法正常工作。我的简单处理方案是从 github 中下载代码,重写 json 序列化部分 (将 exception 的序列化使用自定义代码重写)。见附件代码中的 XSeqPublisher.cs, 它序列化时使用了 ExceptionConverter

    builder.Services.AddHealthChecks()      
       .AddSeqPublisher(seqOptions => // IHealthCheckPublisher 实现,自动定期调用, 这个 seq 实现是第三方 
              {
                  seqOptions.Endpoint = "http://logs.xxxx.fun:803";
                  seqOptions.ApiKey = "xxxxxx";
              });  
    

调用 3 UI 使用三方库

  • 引用 AspNetCore.HealthChecks.UI

  • 引用 AspnetCore.healthChecks.ui.client (需要重写 IHealthCheck 的输出结果)

  • 引用 HealthChecks.UI.InMemory.Storage 将数据保存至内存中,(也可以使用其它组件保存至数据库)

1. 添加健康检查UI
        builder.Services.AddHealthChecksUI(setup =>
            {
                // 调用的 API 结点,如果是本地的话,可以使用相对路径
                setup.AddHealthCheckEndpoint("local_health", "/health");
            })
            .AddInMemoryStorage(); // 将数据保存至内存中

2. 如果是使用本地的 API 结点,需要重写输出结果
        var options = new HealthCheckOptions();
        options.ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse // 重写响应结果
        app.MapHealthChecks("/health", options).ShortCircuit(); 

3.  启用 健康检查UI 并设置访问路径,如果不设置路径,默认为 /healthchecks-ui
app.UseHealthChecksUI(it => it.UIPath = "/my-health-ui");            

附件例子

修改了第三方的 SeqPublisher , 因为它不支持传和的 HealthReport 中带有异常信息的情况。重定 HealthReport json 序列化, 加入转换器 ExceptionConverter

{
  "RawReport", JsonSerializer.Serialize(report, new JsonSerializerOptions
    {
      WriteIndented = true, 
      Converters = { new ExceptionConverter(), },
    })
}

IHealthCheckPublisher例子

上一篇
下一篇