Adding multiple health check endpoint for dotnet core application



Imagine the scenario when we need to expose one health check endpoint for checking application liveliness and another endpoint for checking application readiness.

Lets assume, liveliness endpoint is responsible to see if the application is healthy and ready to receive traffic. On the other hand, readiness endpoint is responsible to check if the application is responsive, if not - the application will be restarted by some way to fix that.

We can use the Health Checks provided by ASP.NET Core to achieve this with filtering by tags.
Lets assume that we have a "/ping" endpoint in our application that responses with "pong". We can add a health check to hit the ping endpoint to see if our application is responsive or not.

public class ApiHealthCheck : IHealthCheck
{
private readonly HttpContext _httpContext;
public ApiHealthCheck(IHttpContextAccessor contextAccessor)
{
_httpContext = contextAccessor?.HttpContext;
}
public async Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default)
{
try
{
using (var httpClient = new HttpClient())
{
var url = new Uri($"{_httpContext.Request.Scheme}://{_httpContext.Request.Host}/ping");
var response = await httpClient.GetAsync(url);
if (!response.IsSuccessStatusCode)
{
return HealthCheckResult.Unhealthy($"Didn't receive OK response from /ping endpoint. Response Phrase: {response.ReasonPhrase}.");
}
}
}
catch (Exception ex)
{
return HealthCheckResult.Unhealthy($"Error ocurred while connecting to /ping endpoint.", ex);
}
return HealthCheckResult.Healthy("API is ready.");
}
}
I am also assuming that the application relies on ElasticSearch for processing API requests. So, I am adding another health check for ElasticSearch.

public class EsHealthCheck : IHealthCheck
{
private readonly IElasticSearchDataProvider _dataProvider;
public EsHealthCheck(IElasticSearchDataProvider dataProvider)
{
_dataProvider = dataProvider;
}
public async Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default)
{
var healthCheckResultHealthy = await _dataProvider.HealthCheck();
if (healthCheckResultHealthy)
{
return HealthCheckResult.Healthy("Ok.");
}
return HealthCheckResult.Unhealthy("ES cluster returned unhealthy status.");
}
}
Now we need to register those health checks:

public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
// Using a custom extension for adding health check endpoints
app.UseHealthChecks();
// Do other stuffs
}
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
// Add other services
services.AddHealthChecks()
.AddCheck<EsHealthCheck>("ElasticSearch", tags: new[] { "Live" })
.AddCheck<ApiHealthCheck>("API", tags: new[] { "Ready" });
}
}
view raw Startup.cs hosted with ❤ by GitHub
And here is the implementation of the extension method for adding health check endpoints:

public static class ApplicationBuilderExtensions
{
public static IApplicationBuilder UseHealthChecks(this IApplicationBuilder builder)
{
builder.UseHealthChecks("/health", NewHealthCheckOptions("Live"));
builder.UseHealthChecks("/ready", NewHealthCheckOptions("Ready"));
return builder;
}
private static HealthCheckOptions NewHealthCheckOptions(string filterByTag)
{
return new HealthCheckOptions()
{
Predicate = (x) => x.Tags.Contains(filterByTag),
ResponseWriter = (httpContext, report) =>
{
httpContext.Response.ContentType = "application/json";
return httpContext.Response.WriteAsync(report.ToJObject().ToString(Formatting.Indented));
}
};
}
private static JObject ToJObject(this HealthReport result)
{
if (result == null)
{
return default;
}
return new JObject(
new JProperty("status", result.Status.ToString()),
new JProperty("results", new JObject(result.Entries.Select(pair =>
new JProperty(pair.Key, new JObject(
new JProperty("status", pair.Value.Status.ToString()),
new JProperty("description", pair.Value.Description),
new JProperty("error", pair.Value.Exception?.Message)))))));
}
}
This will expose following endpoints for health checks:
  • localhost:5000/health - this will check health status of underlying services
  • localhost:5000/ready - this will check application responsiveness



HAPPY CODING 👌

Comments

Popular posts from this blog

Adding security headers to prevent XSS and MiTM attacks in .Net Core

Microsoft.IdentityModel.Protocols.OpenIdConnectProtocolInvalidNonceException

Executing synchronous methods asynchronously