Created
February 25, 2025 20:53
-
-
Save JerryNixon/cc591f6dd35dd40c0d869eee7644dfea to your computer and use it in GitHub Desktop.
Create a health check for REST web api
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System.Text.Json; | |
using Health; | |
using Microsoft.AspNetCore.Diagnostics.HealthChecks; | |
using Microsoft.Extensions.Diagnostics.HealthChecks; | |
using System.Diagnostics; | |
internal class Program | |
{ | |
private static void Main(string[] args) | |
{ | |
WebApplicationBuilder builder = WebApplication.CreateBuilder(args); | |
builder.Services.AddOpenApi(); | |
builder.Services.AddHttpClient(); // Register HttpClient | |
builder.Services.AddHealthChecks() | |
.AddRestCheck("one_check", "http://localhost:5000/one", 1000) | |
.AddRestCheck("two_check", "http://localhost:5000/two", 1000) | |
.AddRestCheck("three_check", "http://localhost:5000/three", 1000); | |
WebApplication app = builder.Build(); | |
app.MapOpenApi(); | |
app.UseHttpsRedirection(); | |
app.MapHealthChecks("/health", new HealthCheckOptions | |
{ | |
ResponseWriter = HealthCheckResponseWriter.WriteResponse | |
}); | |
app.MapGet("/one", () => "one"); | |
app.MapGet("/two", () => "two"); | |
app.MapGet("/three", () => "three"); | |
app.Run(); | |
} | |
} | |
namespace Health | |
{ | |
public static class HealthCheckResponseWriter | |
{ | |
public static IHealthChecksBuilder AddRestCheck(this IHealthChecksBuilder builder, string name, string url, int thresholdMs) | |
{ | |
return builder.Add(new HealthCheckRegistration( | |
name: name, | |
factory: sp => new RestCheck(sp.GetRequiredService<HttpClient>(), new() { Url = url, ThresholdMs = thresholdMs }), | |
failureStatus: default, | |
tags: default)); | |
} | |
private readonly static JsonSerializerOptions jsonSerializationOptions = new() | |
{ | |
WriteIndented = true, | |
PropertyNamingPolicy = JsonNamingPolicy.CamelCase | |
}; | |
public static async Task WriteResponse(HttpContext context, HealthReport report) | |
{ | |
object payload = new | |
{ | |
status = report.Status.ToString(), | |
checks = report.Entries.Select(BuildCheckResult) | |
}; | |
string json = JsonSerializer.Serialize(value: payload, options: jsonSerializationOptions); | |
context.Response.ContentType = "application/json"; | |
await context.Response.WriteAsync(json); | |
static Dictionary<string, object> BuildCheckResult(KeyValuePair<string, HealthReportEntry> entry) | |
{ | |
Dictionary<string, object> result = new Dictionary<string, object> | |
{ | |
{ "name", entry.Key }, | |
{ "status", entry.Value.Status.ToString() }, | |
{ "data", entry.Value.Data.ToDictionary(x => x.Key, x => x.Value) } | |
}; | |
if (entry.Value.Status != HealthStatus.Healthy || entry.Value.Exception != null) | |
{ | |
result.Add("exception", entry.Value.Exception?.Message ?? "null"); | |
} | |
return result; | |
} | |
} | |
} | |
public class RestCheck(HttpClient httpClient, RestCheckOptions options) : IHealthCheck | |
{ | |
public async Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default) | |
{ | |
try | |
{ | |
(HttpResponseMessage response, long duration) = await GetAsync(cancellationToken); | |
Dictionary<string, object> result = new Dictionary<string, object> | |
{ | |
{ "threshold-ms", options.ThresholdMs }, | |
{ "duration-ms", duration }, | |
{ "endpoint-url", options.Url } | |
}; | |
return response.IsSuccessStatusCode switch | |
{ | |
true => HealthCheckResult.Healthy("Endpoint is healthy.", result), | |
false when duration > options.ThresholdMs => HealthCheckResult.Degraded("Endpoint is degraded.", default, result), | |
false => HealthCheckResult.Unhealthy("Endpoint is unhealthy.", default, result) | |
}; | |
} | |
catch (Exception ex) | |
{ | |
return HealthCheckResult.Unhealthy($"Endpoint {options.Url} is unreachable.", ex); | |
} | |
async Task<(HttpResponseMessage response, long duration)> GetAsync(CancellationToken cancellationToken) | |
{ | |
Stopwatch stopwatch = Stopwatch.StartNew(); | |
HttpResponseMessage response = await httpClient.GetAsync(options.Url, cancellationToken); | |
stopwatch.Stop(); | |
return (response, stopwatch.ElapsedMilliseconds); | |
} | |
} | |
} | |
public class RestCheckOptions | |
{ | |
public string Url { get; set; } = string.Empty; | |
public int ThresholdMs { get; set; } = 1000; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment