[官方手册] 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(), },
})
}