در زمان توسعه اپلیکیشن بر روی سیستم خودمان به راحتی میتوانیم سرویس‌های خارجی و یا دیتابیس‌هایی که با آنها کار کنیم را تست کنیم که آیا میتوانیم به دیتابیس و یا سرویس خارجی دسترسی داشته باشیم یا نه. اما این کار بعد از آپلود بر روی سرورهای اصلی کمی دشوار است. برای همین بهتر است از یک ابزاری برای بررسی وضعیت در دسترس بودن سرویس‌های خارجی و یا دیتابیس و یا هر سرویس دیگری استفاده کنیم. برای این کار میتوانیم از پکیج HealthChecks استفاده کنیم.HealthChecks این امکان را به ما می‌دهد که با فراخوانی یک API از وضعیت تمامی سرویس‌هایی که به آنها نیاز داریم با خبر شویم. در این مطلب به پیاده سازی HealthCheck برای دیتابیس‌های Redis و Sql و بررسی دسترسی به یک سرویس خارجی می‌پردازیم. ابتدا یک پروژه Asp.Net Core پیاده سازی می‌کنیم و پکیج‌های زیر را نصب می‌کنیم:

سپس یک کلاس به نام HealthCheckExtension ایجاد می‌کنیم و HealthCheckهای مربوط به redis, sql و یک سرویس خارجی را پیاده سازی می‌کنیم:

public static class HealthCheckExtension
{
    public static void AddAdvancedHealthCheck(
        this IServiceCollection services,
        IConfiguration configuration)
    {
        var sqlConnection = configuration["ConnectionStrings:SqlServer"];
        var redisConnection = configuration["ConnectionStrings:Redis"];
        var externalServiceUri = configuration["someService:uri"];

        services
            .AddHealthChecks()
            .AddRedis(redisConnection, "redis")
            .AddSqlServer(new SqlServerHealthCheckOptions
            {
                ConnectionString = sqlConnection,
            })
            .AddTcpHealthCheck(option =>
            {
                var uri = new Uri(externalServiceUri);
                option.AddHost(uri.Host, uri.Port);
            });
    }
}

در قسمت اول ابتدا آدرس‌های موردنظر را از appsettings.json با استفاده از IConfiguration خوانده‌ایم و سپس HealthCheck مربوط به هرکدام از سرویس‌ها را اضافه کرده ایم. سپس باید middleware مربوط به HealthCheck را اضافه کنیم:

app.UseHealthChecks("/health");

اکنون اگر برنامه را اجرا کنید و به آدرس health/ بروید، باید مقدار Healthy و یا Degraded را دریافت کنید.

 اما این متن برای مشخص کردن اینکه کدام یکی از سرویس‌ها مشکل دارد کافی نیست. برای رفع این مشکل می‌توانیم خروجی را با توجه به نیاز خودمان تغییر دهیم. برای اینکار میتوانیم یک متد برای تغییر خروجی health ایجاد کنیم و آن را به middleware اضافه نماییم.

public static Task WriteResponse(
    HttpContext context,
    HealthReport report)
{
    var jsonSerializerOptions = new JsonSerializerOptions
    {
        WriteIndented = false,
        PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
        DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
    };

    string json = JsonSerializer.Serialize(
        new
        {
            Status = report.Status.ToString(),
            Duration = report.TotalDuration,
            Info = report.Entries
                .Select(e =>
                    new
                    {
                        Key = e.Key,
                        Description = e.Value.Description,
                        Duration = e.Value.Duration,
                        Status = Enum.GetName(
                            typeof(HealthStatus),
                            e.Value.Status),
                        Error = e.Value.Exception?.Message,
                        Data = e.Value.Data
                    })
                .ToList()
        },
        jsonSerializerOptions);

    context.Response.ContentType = MediaTypeNames.Application.Json;
    return context.Response.WriteAsync(json);
}

سپس باید متد ایجاد شده را به صورت زیر به middleware اضافه نمایید:

app.UseHealthChecks("/health", new HealthCheckOptions()
{
    ResponseWriter = HealthCheckExtension.WriteResponse
});

اکنون اگر به آدرس health/ بروید، تمامی سرویس‌هایی که مشخص کرده‌اید در قالب json به شما نمایش داده می‌شود.

{
    "status": "Healthy",
    "duration": "00:00:00.5887094",
    "info":
    [
        {
            "key": "redis",
            "duration": "00:00:00.5152658",
            "status": "Healthy",
            "data":
            {}
        },
        {
            "key": "sqlserver",
            "duration": "00:00:00.5718492",
            "status": "Healthy",
            "data":
            {}
        },
        {
            "key": "tcp",
            "duration": "00:00:00.1394934",
            "status": "Healthy",
            "data":
            {}
        }
    ]
}

اگر یکی از سرویس‌ها مشکل داشته باشد و نتوانید به آن متصل شوید، خروجی به صورت زیر خواهد شد:

{
    "status": "Unhealthy",
    "duration": "00:00:13.4584684",
    "info":
    [
        {
            "key": "redis",
            "duration": "00:00:13.4060867",
            "status": "Unhealthy",
            "error": "The message timed out in the backlog attempting to send because no connection became available (5000ms) - Last Connection Exception: It was not possible to connect to the redis server(s). ConnectTimeout, command=PING, timeout: 5000, inst: 0, qu: 0, qs: 0, aw: False, bw: CheckingForTimeout, rs: NotStarted, ws: Initializing, in: 0, last-in: 0, cur-in: 0, sync-ops: 0, async-ops: 1, serverEndpoint: localhost:6479, conn-sec: n/a, aoc: 0, mc: 1/1/0, mgr: 10 of 10 available, clientName: DESKTOP-QR3CNC3(SE.Redis-v2.7.4.20928), IOCP: (Busy=0,Free=1000,Min=1,Max=1000), WORKER: (Busy=0,Free=32767,Min=20,Max=32767), POOL: (Threads=7,QueuedItems=0,CompletedItems=164,Timers=7), v: 2.7.4.20928 (Please take a look at this article for some common client-side issues that can cause timeouts: https://stackexchange.github.io/StackExchange.Redis/Timeouts)",
            "data":
            {}
        },
        {
            "key": "sqlserver",
            "duration": "00:00:00.2903827",
            "status": "Healthy",
            "data":
            {}
        },
        {
            "key": "tcp",
            "duration": "00:00:00.1094343",
            "status": "Healthy",
            "data":
            {}
        }
    ]
}

همچنین می‌توانید یک HealtCheck سفارشی برای خودتان ایجاد کنید. برای اینکار باید اینترفیس IHealthCheck را ایمپلیمنت کنید و آن را با استفاده از متد AddCheck به سرویس های HealtCeck اضافه نمایید. برای مثال:

public class UserApiHealthCheck : IHealthCheck
{
    private readonly IHttpClientFactory _httpClientFactory;

    public UserApiHealthCheck(IHttpClientFactory httpClientFactory)
    {
        _httpClientFactory = httpClientFactory;
    }

    public async Task<HealthCheckResult> CheckHealthAsync(
        HealthCheckContext context, 
        CancellationToken cancellationToken = default)
    {
        var client = _httpClientFactory.CreateClient();

        var response = await client.GetAsync("/api/v1.0/users");

        if (response.StatusCode == System.Net.HttpStatusCode.OK)
        {
            return HealthCheckResult.Healthy();
        }

        return HealthCheckResult.Degraded();

    }
}

و اضافه کردن آن به سرویس‌های HealthCheck:

services
    .AddHealthChecks()
    .AddRedis(redisConnection, "redis")
    .AddSqlServer(new SqlServerHealthCheckOptions
    {
        ConnectionString = sqlConnection,
    })
    .AddTcpHealthCheck(option =>
    {
        var uri = new Uri(externalServiceUri);
        option.AddHost(uri.Host, uri.Port);
    })
    .AddCheck<UserApiHealthCheck>("UserApi");

فایل appsettings.json:

{
  "ConnectionStrings": {
    "Redis": "localhost:6479,abortConnect=false",
    "SqlServer": "Data Source=127.0.0.1,1433; Initial Catalog=master; User Id=sa; Password=example_123; TrustServerCertificate=True;"
  },
  "SomeService": {
    "Uri": "https://google.com"
  }
}

کدهای این مطلب را می‌توانید از گیتهاب دانلود کنید.
منابع استفاده شده:

;)

Powered by Froala Editor

نظرات