Health checks are a set of checks (duh) that you perform in order to tell whether an application/service is up, running & healthy or not. It’s usually one or more endpoints that reports the status, the response differs from language/framework to an other.
Health checks are very useful especially when your application depends on other things like a database or even other services. Usually, you’ll find the generated endpoint(s) used by an Orchestrator to figure out whether the app is still up or not (liveness).
Today we’ll see how we can add health checks to an ASP.NET Core API.
All the code is available in this GitHub repository.
Built-in health checks
In ASP.NET Core, the package Microsoft.AspNetCore.Diagnostics.HealthChecks
is used to add health checks to your application.
This means that in every project, you have the ability to add health checks out of the box.
Adding health checks is straightforward:
public void ConfigureServices(IServiceCollection services)
{
services.AddHealthChecks();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
endpoints.MapHealthChecks("/health");
});
}
This will inject a middleware that will, for now since we didn’t configure any checks, report that the app is healthy.
Healthy
Custom checks
The package doesn’t come with any built-in checks, it only provides the base. In order to add your own checks, there are two ways:
services.AddHealthChecks()
.AddCheck("AlwaysHealthy", () => HealthCheckResult.Healthy())
.AddCheck<MyCustomCheck>("My Custom Check");
AddCheck(string name, Func<HealthCheckResult> check)
, which takes no arguments and returns a status.AddCheck<IHealthCheck>(string name)
, which takes a class that implements the interfaceIHealthCheck
, where you can put your logic.
This lets you perform all possible checks, but you’ll have to write them manually.
Another downside is that the endpoint’s response doesn’t provide more information by default, maybe one day you’ll want to know which checks fail and you’ll have to write your own ResponseWriter
.
Filtering checks
Imagine you have multiple custom checks:
services.AddHealthChecks()
.AddCheck("AlwaysHealthy", () => HealthCheckResult.Healthy(), tags: new[] { "Tag1" })
.AddCheck("AlwaysHealthyToo", () => HealthCheckResult.Healthy(), tags: new[] { "Tag1" })
.AddCheck("AlwaysUnhealthy", () => HealthCheckResult.Unhealthy(), tags: new[] { "Tag2" });
We can create two endpoints, each one handling a tag:
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
endpoints.MapHealthChecks("/health1", new HealthCheckOptions()
{
Predicate = (check) => check.Tags.Contains("Tag1")
});
endpoints.MapHealthChecks("/health2", new HealthCheckOptions()
{
Predicate = (check) => check.Tags.Contains("Tag2")
});
});
If you visit /health1
, it’ll return Healthy
and if you visit /health2
, it’ll return Unhealthy
.
This is very useful if you want different health endpoints to check different things.
Advanced health checks
Instead of writing every check ourselves, the package AspNetCore.Diagnostics.HealthChecks comes to the rescue! It’s an ASP.NET Core package that plugs into the existing health checks base and adds many custom checks, including:
- SQL Server
- MySQL
- SQLite
- RabbitMQ
- Elasticsearch
- Redis
- System: Disk Storage, Private Memory, Virtual Memory, Process, Windows Service
- Azure Storage: Blob, Queue and Table
- Amazon S3
- Network: Ftp, SFtp, DNS, TCP port, Smtp, Imap
- MongoDB
- Kafka
- Kubernetes
- …
The full list, which is a lot bigger, can be found in the repositories' README.
What’s even better is that it contains a UI too!
Database checks
AspNetCore.HealthChecks
contains checks for the most popular database providers.
Today we’ll try the SQL Server one by installing the package AspNetCore.HealthChecks.SqlServer
.
services.AddHealthChecks()
.AddSqlServer(Configuration["ConnectionString"]); // Your database connection string
It’s as simple as this. If you configured your DbContext
and your SQL Server is running, the /health
endpoint should return the status Healthy
.
System checks
AspNetCore.HealthChecks.System contains many system related checks, let’s look at a few of them:
services.AddHealthChecks()
.AddSqlServer(Configuration["ConnectionString"]) // Your database connection string
.AddDiskStorageHealthCheck(s => s.AddDrive("C:\\", 1024)) // 1024 MB (1 GB) free minimum
.AddProcessAllocatedMemoryHealthCheck(512) // 512 MB max allocated memory
.AddProcessHealthCheck("ProcessName", p => p.Length > 0) // check if process is running
.AddWindowsServiceHealthCheck("someservice", s => s.Status == ServiceControllerStatus.Running); // check if a windows service is running
As you can see, there is a lot of useful checks! It would’ve been better if the Storage check checked the storage used instead of free, it can be very useful when working with containers.
URL checks
Imagine your app’s health depends on an external API, you’re using one of its endpoints in one of your functionalities. There is a health check for that scenario too!
By installing the package AspNetCore.HealthChecks.Uris, you can use:
services.AddHealthChecks()
.AddSqlServer(Configuration["ConnectionString"]) // Your database connection string
.AddDiskStorageHealthCheck(s => s.AddDrive("C:\\", 1024)) // 1024 MB (1 GB) free minimum
.AddProcessAllocatedMemoryHealthCheck(512) // 512 MB max allocated memory
.AddProcessHealthCheck("ProcessName", p => p.Length > 0) // check if process is running
.AddWindowsServiceHealthCheck("someservice", s => s.Status == ServiceControllerStatus.Running) // check if a windows service is running
.AddUrlGroup(new Uri("https://localhost:44318/weatherforecast"), "Example endpoint"); // should return status code 200
You can configure much more using the provided UriHealthCheckOptions
, for example what status codes are considered good or bad, more than one URLs, etc…
UI
There is also a package that adds a monitoring UI that shows you the status of all the checks you added, as well as their history.
First, let’s install the packages:
- AspNetCore.HealthChecks.UI which adds the UI.
- AspNetCore.HealthChecks.UI.Client which turns our old response (e.g.
Healthy
) into a more detailed response. - AspNetCore.HealthChecks.UI.InMemory.Storage which saves the results in memory for the UI to use.
Then let’s register the UI:
public void ConfigureServices(IServiceCollection services)
{
// ...
services
.AddHealthChecksUI(s =>
{
s.AddHealthCheckEndpoint("endpoint1", "https://localhost:44318/health");
})
.AddInMemoryStorage();
// ...
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
// ...
pp.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
endpoints.MapHealthChecksUI();
endpoints.MapHealthChecks("/health", new HealthCheckOptions()
{
Predicate = _ => true,
ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse
});
});
// ...
}
Here’s what you’ll find when you visit the /health
endpoint:
{
"status": "Unhealthy",
"totalDuration": "00:00:00.0531378",
"entries": {
"diskstorage": {
"data": {},
"duration": "00:00:00.0003344",
"status": "Healthy"
},
"process_allocated_memory": {
"data": {},
"description": "Allocated megabytes in memory: 11 mb",
"duration": "00:00:00.0002289",
"status": "Healthy"
},
"process": {
"data": {},
"duration": "00:00:00.0162908",
"status": "Unhealthy"
},
"windowsservice": {
"data": {},
"duration": "00:00:00.0001123",
"status": "Healthy"
},
"Example endpoint": {
"data": {},
"duration": "00:00:00.0514370",
"status": "Healthy"
},
"sqlserver": {
"data": {},
"duration": "00:00:00.0319841",
"status": "Healthy"
}
}
}
And if you visit /healthchecks-ui
, which is the default URL for the UI:
We can also tell the UI to save the results in an actual database, let’s try the SQLite one available in the package AspNetCore.HealthChecks.UI.SQLite.Storage.
All we have to do is replace the in memory call:
services
.AddHealthChecksUI(s =>
{
s.AddHealthCheckEndpoint("endpoint1", "https://localhost:44318/health");
})
.AddSqliteStorage("Data Source = healthchecks.db");
Now every time the UI fetches the health checks at /health
, it will also use the SQLite database to save a bunch of information.
Conclusion
In this post, we saw what ASP.NET Core provides by default regarding health checks. Microsoft made a very extensible system where other developers can create all sorts of checks while keeping the same infrastructure.
We also saw some of the AspNetCore.HealthChecks
packages that plug on top of ASP.NET Core’s health checks to add the most popular checks anyone would ever need. Not only that, but also a monitoring UI that is customizable when you provide it your CSS file.
What we didn’t see in this post is the ability to have hooks (e.g. Slack) that listen on the application’s health, which AspNetCore.HealthChecks
also provides.
As a reminder, all the code is available in this GitHub repository. I hope this post was useful, see you around!