ASP.NET Core 健康檢查

Health checks in ASP.NET Core, healthz,

引言

ASP.NET Core 提供健康情況檢查中介軟體和程式庫,用來報告應用程式基礎結構元件的健康情況。

健康狀態檢查是藉由實作 IHealthCheck 介面來建立。 CheckHealthAsync 方法會傳回指出狀態為 HealthyDegraded(性能下降)Unhealthy

參考文件

The Best Way to Add Health Checks in Any .NET App

實作方案

跳過實作過程,就實作成果來就是多一個專用的 url path: "/healthz",專用來顯示系統現況態是否健康。

也就是說實作方式不用遵行標準答案,只要有 "/healthz" Page 專用來探測系統現在健康狀態,如:資料庫連線、記憶體、網路等等資源用量是否處在正常狀態,這樣也滿足『健康檢查』的定義。

不過本例還是用標準答案來健檢。

開發環境

執行平台: .NET6 IDE: Visual Stuido 2022 語言: C# 10+ 框架: Blazor Server/WASM App

安裝套件 (optional)

第三方開發。用於健檢 SqlServer 連線狀態。 可以在 NuGet 套件管理找到名為 AspNetCore.HealthChecks.* 開頭的健檢資源。

NuGet\Install-Package AspNetCore.HealthChecks.SqlServer

關鍵源碼紀錄

Program.cs
var builder = WebApplication.CreateBuilder(args);

[...略...]

//## for 健康狀態檢查
builder.Services.AddHealthChecks()
       .AddSqlServer(new HealthChecks.SqlServer.SqlServerHealthCheckOptions
       {
         // 第三方開發。用於健檢 SqlServer 連線狀態。       
         ConnectionString = ConnectionStringProvider.Get("MainDB"),
         CommandText = "SELECT 1;"
       }, name: "MainDB")
       .AddCheck<SimpleHealthCheck>(nameof(SimpleHealthCheck));

var app = builder.Build(); //------ ------ ------

[...略...]

//## for 健康狀態檢查
app.MapHealthChecks("/healthz", new Microsoft.AspNetCore.Diagnostics.HealthChecks.HealthCheckOptions
{
  ResponseWriter = SimpleHealthCheck.WriteHealthCheckUIResponse
});

//## Endpoints
app.MapControllers(); // for enable WebApi
app.MapBlazorHub();
app.MapFallbackToPage("/_Host");

app.Run();

自訂健康檢查類別 SimpleHealthCheck 。

SimpleHealthCheck.cs
using Microsoft.Extensions.Diagnostics.HealthChecks;
using System.Text.Json;
using System.Text;

namespace YourProject.Models;

internal class SimpleHealthCheck : IHealthCheck
{
  Task<HealthCheckResult> IHealthCheck.CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken)
  {
    try
    {
      return Task.FromResult(HealthCheckResult.Healthy("我很好。"));
    }
    catch (Exception ex)
    {
      return Task.FromResult(HealthCheckResult.Unhealthy(description: ex.Message, exception: ex));
    }
  }

  /// <summary>
  /// 專用資源,用於 HealthCheck 輸出。 
  /// 參考:[ASP.NET Core 中的健康狀態檢查-自訂輸出](https://learn.microsoft.com/zh-tw/aspnet/core/host-and-deploy/health-checks?view=aspnetcore-6.0#customize-output)
  /// </summary>
  internal static Task WriteHealthCheckUIResponse(HttpContext context, HealthReport healthReport)
  {
    context.Response.ContentType = "application/json; charset=utf-8";

    var options = new JsonWriterOptions { 
      Indented = true, 
      Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping 
    };

    using var memoryStream = new MemoryStream();
    using (var jsonWriter = new Utf8JsonWriter(memoryStream, options))
    {
      jsonWriter.WriteStartObject();
      jsonWriter.WriteString("status", healthReport.Status.ToString());
      jsonWriter.WriteStartObject("results");

      foreach (var healthReportEntry in healthReport.Entries)
      {
        jsonWriter.WriteStartObject(healthReportEntry.Key);
        jsonWriter.WriteString("status",
            healthReportEntry.Value.Status.ToString());
        jsonWriter.WriteString("description",
            healthReportEntry.Value.Description);
        jsonWriter.WriteStartObject("data");

        foreach (var item in healthReportEntry.Value.Data)
        {
          jsonWriter.WritePropertyName(item.Key);

          JsonSerializer.Serialize(jsonWriter, item.Value,
              item.Value?.GetType() ?? typeof(object));
        }

        jsonWriter.WriteEndObject();
        jsonWriter.WriteEndObject();
      }

      jsonWriter.WriteEndObject();
      jsonWriter.WriteEndObject();
    }

    return context.Response.WriteAsync(
       Encoding.UTF8.GetString(memoryStream.ToArray()));
  }
}

第二種實作方式 補充 on 24-01-30

前述 app.MapHealthChecks("/healthz",...是 miniapi 的實作方式,這方法無法被 Swagger 詮釋成文件。另一種方法是實作正統的 WebAPI 。此方法才能被 Swagger 詮譯成文件。

其中 HealthCheckService 會執行在 Services.AddHealthChecks() 段落註冊的所有健檢項目。

標準的寫法大概如下:

HealthCheckController.cs
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Diagnostics.HealthChecks;
using System.Net;

namespace YourProject.Controllers;

/// <summary>
/// for 健康狀態檢查
/// </summary>
[Route("healthz")]
[ApiController]
[AllowAnonymous]
public class HealthCheckController(HealthCheckService healthCheckService)
  : ControllerBase
{
  [HttpGet]
  public async Task<ActionResult> Get()
  {
    ///註:HealthCheckService 會執行在 Services.AddHealthChecks() 段落註冊的所有健檢項目。
    HealthReport report = await healthCheckService.CheckHealthAsync();
    var result = new
    {
      status = report.Status.ToString(),
      errors = report.Entries.Select(e => new
      {
        name = e.Key,
        status = e.Value.Status.ToString(),
        description = e.Value.Description,
        data = e.Value.Data
      }),
    };

    return report.Status == HealthStatus.Healthy
      ? this.Ok(result)
      : this.StatusCode((int)HttpStatusCode.ServiceUnavailable, result);
  }
}


沒圖沒真象

Healthz

Swagger + Rin + Healthz

Swagger + Rin + Healthz

完整原始碼

(EOF)

Last updated