Skip to content

Instantly share code, notes, and snippets.

@JerryNixon
Created February 25, 2025 20:53
Show Gist options
  • Save JerryNixon/cc591f6dd35dd40c0d869eee7644dfea to your computer and use it in GitHub Desktop.
Save JerryNixon/cc591f6dd35dd40c0d869eee7644dfea to your computer and use it in GitHub Desktop.
Create a health check for REST web api
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