Implementing Health Check for your application
Senthil Kumaran
Architect | AI | .NET | GCP | AWS | Azure | Data | DevOps | System Design | Solution-Oriented
Here we are going to see about the health check for an application, especially why it is required and how it is useful.
Typically, in an application, we use various components that involve disk I/O ops, 3rd party API calls, data sync, CRON, etc., and the most prominent one is the database.
Either microservice or a monolithic application, it is critical to monitor the status of all the components and their operations periodically and create alerts based on the status.
The common scenarios to monitor in a typical application as follows:
How do we implement using .Net Core 3.1?
It is quite easy and straightforward, just add "AddHealthChecks()" in the ConfigureServices method and end-point (/health) in Configure method.
The Microsoft documentation explains in detail.
We usually look for a quick & practical and easy way to start doing it.
Let's jump into the action. Take a look at the /health end-point result as JSON:
The results vary by data on the images. Check out the environment variables under the data object.
Let's see how the result would be when it is Unhealthy with an exception thrown:
Let's see how it looks when SQL connection doesn't exist:
All right, the health reports seem self-explanatory, now jump into making.
Let's create HealthInspector class that implements "IHealthCheck" and must have CheckHealthAsync method.
public class HealthInspector : IHealthCheck { private readonly IConfiguration configuration; public HealthInspector(IConfiguration configuration) { this.configuration = configuration; } public Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default(CancellationToken)) { return DoHealthCheck(); } private async Task<HealthCheckResult> DoHealthCheck() { var healthCheckData = new Dictionary<string, object>(); // Check File IO Ops string filePath = @$"C:\Temp\healthcheck.txt"; try { var file = File.Create(filePath); file.Close(); healthCheckData.Add("File IO", "Healthy"); } catch (Exception ex) { healthCheckData.Add("File IO", $"Unhealthy - Err: {ex.Message}"); } finally { if (File.Exists(filePath)) File.Delete(filePath); } // Check 3rd Party APIs try { using (HttpClient client = new HttpClient()) { await client.GetStringAsync("https://www.google.com/"); healthCheckData.Add("3rd Party API", "Healthy"); } } catch (Exception ex) { healthCheckData.Add("3rd Party API", $"Unhealthy - Err: {ex.Message}"); } // Check the App Settings try { var appSetting = configuration.GetValue<string>("MyAppVariable"); healthCheckData.Add("App Settings", string.IsNullOrEmpty(appSetting) ? "Unhealthy" : "Healthy"); } catch (Exception ex) { healthCheckData.Add("App Settings", $"Unhealth - Err: {ex.Message}"); } // Check the Environment variables try { string myAppEnv = Environment.GetEnvironmentVariable("MyApp", EnvironmentVariableTarget.User); healthCheckData.Add("Environment Variables", string.IsNullOrEmpty(myAppEnv) ? "Unhealthy" : "Healthy"); } catch (Exception ex) { healthCheckData.Add("Environment Variables", $"Unhealth - Err: {ex.Message}"); } var healthResult = new HealthCheckResult(HealthStatus.Healthy, "Application health", null, healthCheckData); return healthResult; } }
Let's quickly discuss this class.
The health inspector checks all the components in a dedicated Try/Catch block and captures the result in a Dictionary<Key, Value> object and that's called HealthCheck Data.
The example just shows some ideas and various checks.
In Startup.cs >> ConfigureServices:
services.AddHealthChecks() .AddCheck<HealthInspector>("MyApp Health") .AddSqlServer( @"Server=.\sqlexpress,1433;Database=HealthCheckDB;Integrated Security=False;User Id=sa;Password=YourStrongPassword;MultipleActiveResultSets=True", // Following are the optional parameters "Select 1", // Default query that perform on the Server "SQL Health", HealthStatus.Unhealthy, null, TimeSpan.FromSeconds(30));
In Configure:
app.UseEndpoints(endpoints => { endpoints.MapControllers(); endpoints.MapHealthChecks("/health", new HealthCheckOptions() { ResponseWriter = WriteResponse }); });
"WriteResponse" is a Func delegate which we need to write as follows:
private static Task WriteResponse(HttpContext context, HealthReport result) { context.Response.ContentType = "application/json"; JObject json; if (result.Status == HealthStatus.Healthy) { if (result.Entries.Count > 0) { json = 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("data", new JObject(pair.Value.Data.Select( p => new JProperty(p.Key, p.Value)))))))))); } else { json = new JObject(new JProperty("status", result.Status.ToString())); } } else { json = 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("data", new JObject(pair.Value.Data.Select( p => new JProperty(p.Key, p.Value))))))))), new JProperty("exception", new JObject(result.Entries.Select(pair => new JProperty(pair.Key, new JObject( new JProperty("message", (pair.Value.Exception == null) ? string.Empty : pair.Value.Exception.Message), new JProperty("stackTrace", (pair.Value.Exception == null) ? string.Empty : pair.Value.Exception.StackTrace)))))) ); } return context.Response.WriteAsync( json.ToString(Formatting.Indented));
}
That's it. We are done with health monitoring.
The complete project is available on GitHub.
The improvements we can add like creating email alerts or raising an event but usually this end-point will be called from DevOps/Site-Reliability-Engineering teams on their platform and they will create alerts that notify appropriate teams.
It is vital to add all critical components involved in the project to this health monitoring to prevent any issues which we can sense upfront and take action.
Appreciate your interest and time.
Happy coding!