Using the Microsoft.AspNetCore.HealthChecks Package

Updated 7/17/2018: This package will be released in ASP.NET Core 2.2 which is going to come out in Q4 2018 currently.

CAVEAT: This is pre-released software.  There isn’t even a NuGet package for it yet.  The GitHub repo lives here – https://github.com/dotnet-architecture/HealthChecks that I cloned and manually added a project reference to a new demo app.  There is a plan for it to get made into a NuGet package, but that hasn’t happened yet.

With that said – I will keep this post updated as I see changes being made, and will eventually remove the Caveat above.

tldr;

Register your Health Checks in your ConfigureServices method.


public void ConfigureServices(IServiceCollection services)
{
services.AddHealthChecks(checks =>
{
checks.AddUrlCheck("https://github.com");
checks.AddSqlCheck("Local DB Check", "Server=(localdb)\\mssqllocaldb;Database=aspnet-WebApplication1;Trusted_Connection=True;MultipleActiveResultSets=true");
});
services.AddMvc();
}

view raw

Startup.cs

hosted with ❤ by GitHub

Inject in an IHealthCheckService and call CheckHealthAsync to run all your Health Checks, or another method like RunCheckAsync or RunGroupAsync to run a subset of the Health Checks you registered in ConfigureServices.


[Route("api/[controller]")]
public class HealthCheckController : Controller
{
private readonly IHealthCheckService _healthCheckService;
public HealthCheckController(IHealthCheckService healthCheckService)
{
_healthCheckService = healthCheckService;
}
[HttpGet]
public async Task<IActionResult> Get()
{
// Can also call RunCheckAsync to run a single Heatlh Check or RunGroupAsync to run a group of Health Checks
CompositeHealthCheckResult healthCheckResult = await _healthCheckService.CheckHealthAsync();
bool somethingIsWrong = healthCheckResult.CheckStatus != CheckStatus.Healthy;
if (somethingIsWrong)
{
// healthCheckResult has a .Description property, but that shows the description of all health checks.
// Including the successful ones, so let's filter those out
var failedHealthCheckDescriptions = healthCheckResult.Results.Where(r => r.Value.CheckStatus != CheckStatus.Healthy)
.Select(r => r.Value.Description)
.ToList();
// return a 500 with JSON containing the Results of the Health Check
return new JsonResult(new {Errors = failedHealthCheckDescriptions}){ StatusCode = StatusCodes.Status500InternalServerError };
}
return Ok();
}
}

What is a Health Check?

Health Checks are pretty much what the name implies.  They are a way of checking to see if your application is healthy.  Health Checks have become especially critical as more and more applications are moving to a microservice-style architecture.  While microservice architectures have many benefits, one of the downsides is there is a higher operations overhead to ensuring all of these services are running.  Rather than monitoring the health of one majestic monolith, you need to monitor the status of many different services, which are usually responsible for one thing and one thing only.  Health Checks are usually used in combination with a service discovery tool such as Consul that monitor your microservices for when they become healthy and unhealthy.  If you use Consul for service discovery as well, Consul will automatically route traffic away from your unhealthy microservices and only serve traffic to your healthy microservices… which is awesome.

 

How do I implement a Health Check?

There are a few different ways to do Health Checks, but the most common way is exposing an HTTP endpoint to your application dedicated to doing Health Checks.  Typically you will return a status code of 200 if everything is good, and any non-2xx code means something went wrong.  For example, you might return a 500 if something went wrong along with a JSON payload of what exactly went wrong.

 

Common scenarios to Health Check

What you Health Check will be based on what your application/microservice does, but some common things:

  • Can my service connect to a database?
  • Can my service query a 3rd party API?
    • Likely making some read-only call
  • Can my service access the file system?
  • Is the Memory and/or CPU above a certain threshold?

 

Looking at the Microsoft.AspNetCore.HealthChecks package

Microsoft is on the verge of shipping a set of Health Check packages to help you solve this problem in a consistent way.  If you look in the GitHub repo you will notice there is also a package for ASP.NET 4.x as well under the Microsoft.AspNet.HealthChecks namespace.  There is a samples folder on that GitHub repo that contains how to wire that up if you’re interested in ASP.NET 4.x.  I’m going to focus on the ASP.NET Core package for this blog post.

The Microsoft.AspNetCore.HealthChecks package targets netcoreapp1.0, but I suspect this will change to be either netcoreapp2.0 or netstandard2.0 by the time this RTM’s.  The ASP.NET 4 project targets net461, and all the other libraries target netstandard1.3which works with both .NET Core and Full Framework.

 

Getting Started

The basic flow is that you register your health checks in your IoC container of choice (such as the built-in Microsoft one, although I prefer SimpleInjector due to the fantastic feature set, blazing speed, and ridiculously good documentation, but I’ll just use the built-in one for these demos).  You register these Health Checks via a fluent HealthCheckBuilder API in your Startup‘s ConfigureServices method.  This HealthCheckBuilder will build a HealthCheckService and register it as an IHealthCheckService​ in your IoC container.

That looks something like this:


public void ConfigureServices(IServiceCollection services)
{
// Creates a HealthCheckBuilder and registers an IHealthCheckService in the IServiceCollection/IoC Container
services.AddHealthChecks(checks =>
{
// Register health checks here
});
services.AddMvc();
}

view raw

Startup.cs

hosted with ❤ by GitHub

 

So to run your Health Checks you inject in an IHealthCheckService into your Controller and then call CheckHealthAsync.


[Route("api/[controller]")]
public class HealthCheckController : Controller
{
private readonly IHealthCheckService _healthCheckService;
public HealthCheckController(IHealthCheckService healthCheckService)
{
_healthCheckService = healthCheckService;
}
[HttpGet]
public async Task<IActionResult> Get()
{
// Can also call RunCheckAsync to run a single Heatlh Check or RunGroupAsync to run a group of Health Checks
CompositeHealthCheckResult healthCheckResult = await _healthCheckService.CheckHealthAsync();
bool somethingIsWrong = healthCheckResult.CheckStatus != CheckStatus.Healthy;
if (somethingIsWrong)
{
// healthCheckResult has a .Description property, but that shows the description of all health checks.
// Including the successful ones, so let's filter those out
var failedHealthCheckDescriptions = healthCheckResult.Results.Where(r => r.Value.CheckStatus != CheckStatus.Healthy)
.Select(r => r.Value.Description)
.ToList();
// return a 500 with JSON containing the Results of the Health Check
return new JsonResult(new {Errors = failedHealthCheckDescriptions}){ StatusCode = StatusCodes.Status500InternalServerError };
}
return Ok();
}
}

 

There are a few things to note here:

  1. You get back a CompositeHealthCheckResult which is a summary of all of your health checks that you registered in your AddHealthChecks method in your Startup class.
  2. That CompositeHealthCheckResult class has a CheckStatus property which is an enum.  That enum has 4 options – Healthy, Unhealthy, Warning, and Unknown. You can determine what you want to do with each of those.  In my simple example above, I consider anything other than Healthy to be a problem and return a 500 if it’s not Healthy.

 

You can also loop over the results of the CompositeHealthCheckResult by looking at the Results property and get even more detail about what exactly happened.

You can optionally run a single Health Check by calling RunCheckAsync and supplying the name of the Health Check that you registered in your ConfigureServices method (more on that later).

 

Out of the box Health Checks

Microsoft ships quite a few Health Checks out of the box that fit into the Common Scenarios section above.  They are:

  • URL Health Check via AddUrlCheck
  • SQL Server Health Check via AddSqlCheck
  • PrivateMemorySizeCheck via AddPrivateMemorySizeCheck
  • VirtualMemorySizeCheck via AddVirtualMemorySizeCheck
  • WorkingSetCheck via AddWorkingSetCheck
  • A few Azure Health Checks (such as BLOB Storage, Table Storage, File Storage, and Queue storage).

Let’s take a look at the URL Health Check and the SQL Server Health Check.

 

URL Health Checks

The URL Health Check lets you specify a URL and then it will execute a GET to that URL and see if the URL returns a Success Status Code or not (any 2xx Status Code like 200).

You can register the URL Health Check by adding this.


public void ConfigureServices(IServiceCollection services)
{
services.AddHealthChecks(checks =>
{
// First parameter is the URL you want to check
// The second parameter is the CacheDuration of how long you want to hold onto the HealthCheckResult
// The default is 5 minutes, and in my case, I don't really want any cache at all
// because I don't want to wait up to 5 minutes if my service goes down to be notified about it
checks.AddUrlCheck("https://github.com&quot;, TimeSpan.FromMilliseconds(1));
});
services.AddMvc();
}

view raw

Startup.cs

hosted with ❤ by GitHub

Then you inject your IHealthCheckService and call CheckHealthAsync as shown above.  If you want to just run this single Health Check, and not others you may have registered, you’ll need to know that the name is not configurable.  The name will be UrlCheck(https://github.com)​.  So you would run that single check with  RunCheckAsync("UrlCheck(https://github.com)").

Another thing to note, that second parameter where I’m passing TimeSpan.FromMilliseconds(1) is the CacheDuration of the HealthCheckResult.  The default is 5 minutes.  So if you have some other service (like Consul) pinging your Health Check endpoint every minute, the HealthCheckResult will be the same for 5 minutes until the CacheDuration expires.  To me, that doesn’t make a ton of sense, and I don’t want to risk an up to 5 minute delay on being notified when my service becomes unhealthy.  So by only adding a 1 millisecond cache, I’m effectively adding no caching at all.

There is also another parameter to the AddUrlCheck method where you can pass a Func to the URL Checker.  This is nice in scenarios such as:

  • You want to execute something other than a GET.
  • You need to do something special with the HttpRequest in general such as add Auth Headers or something.
  • You want to validate the response’s Content contains some specific words or HTML.

So the URL Check should satisfy just about any Web check you could possibly want to do with that flexibility.

 

Built-in SQL Server Health Checks

The SQL Check lets you specify a name and a connection string to connect to.


public void ConfigureServices(IServiceCollection services)
{
services.AddHealthChecks(checks =>
{
// Local DB Check is just the name of the Health Check. You can call it whatever you want.
checks.AddSqlCheck("SQL DB Check", "Server=(localdb)\\mssqllocaldb;Database=aspnet-WebApplication1;Trusted_Connection=True;MultipleActiveResultSets=true");
});
services.AddMvc();
}

view raw

Startup.cs

hosted with ❤ by GitHub

The first parameter, “SQL DB Check”, is just the name I chose.  You can make it whatever makes sense to you.  To run this check, as mentioned above, you would call this from IHealthCheckService.

 

Making your own custom Health Check

You can of course make your own custom Health Check.  For me, most of my use cases are solved by the built-in ones, as I’m usually checking if an API is available (which I could do with the URL Check and overriding the checkFunc parameter) or I’m checking to see if a SQL Server is available.  But you could implement your own if you are missing some functionality that you need such as checking if another DB store is available or how much free space a drive has.

To do that, derive from IHealthCheck and implement the interface.  Below is an example of one that checks to make sure the C drive has at least 1 GB of free space.


public class CDriveHasMoreThan1GbFreeHealthCheck : IHealthCheck
{
public ValueTask<IHealthCheckResult> CheckAsync(CancellationToken cancellationToken = default(CancellationToken))
{
long freeSpaceinGb = GetTotalFreeSpaceInGb(@"C:\");
CheckStatus status = freeSpaceinGb > 1 ? CheckStatus.Healthy : CheckStatus.Unhealthy;
return new ValueTask<IHealthCheckResult>(HealthCheckResult.FromStatus(status, $"Free Space in GB: {freeSpaceinGb}"));
}
private long GetTotalFreeSpaceInGb(string driveName)
{
foreach (DriveInfo drive in DriveInfo.GetDrives())
{
if (drive.IsReady && drive.Name == driveName)
{
return drive.TotalFreeSpace / 1024 / 1024 / 1024;
}
}
throw new ArgumentException($"Invalid Drive Name {driveName}");
}
}

Then in your ConfigureServices method, register the custom Health Check with the lifestyle that makes sense for the Health Check (Singleton, Scoped, Transient, etc.) and then add it to the AddHealthChecks registration that we’ve done before.


public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<CDriveHasMoreThan1GbFreeHealthCheck>();
services.AddHealthChecks(checks =>
{
checks.AddCheck<CDriveHasMoreThan1GbFreeHealthCheck>("C Drive has more than 1 GB Free");
});
services.AddMvc();
}

view raw

Startup.cs

hosted with ❤ by GitHub

That’s it!

 

Group your health checks together

You can group your health checks together in a HealthCheckGroup if you want (such as all performance checks like CPU, Memory, Disk Space, etc. go under a group called “performance”) or you can let them live on their own and mix and match.


public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<CDriveHasMoreThan1GbFreeHealthCheck>();
services.AddHealthChecks(checks =>
{
checks.AddUrlCheck("https://github.com&quot;)
.AddSqlCheck("Local DB Check", "Server=(localdb)\\mssqllocaldb;Database=aspnet-WebApplication1;Trusted_Connection=True;MultipleActiveResultSets=true")
.AddHealthCheckGroup("performance",
group => group.AddPrivateMemorySizeCheck(1000)
.AddVirtualMemorySizeCheck(1000)
.AddWorkingSetCheck(1000)
.AddCheck<CDriveHasMoreThan1GbFreeHealthCheck>("C Drive has more than 1 GB Free"),
CheckStatus.Unhealthy
);
});
services.AddMvc();
}

view raw

Startup.cs

hosted with ❤ by GitHub

This enables you to do things like only call that Group of Health Checks via the RunGroupAsync method off of IHealthCheckService.

 

Security

Reminder – this is demo code.  Some flaws include that my Health Check endpoint is unsecured and anyone can hit the endpoint.  You will likely want to secure your Health Check endpoint, especially if it is on the Internet, so someone doesn’t spam your Health Check endpoint.  There are many ways to do this, but are outside the scope of this blog post.

 

Some Feedback on the Design

Overall I think this abstraction is really useful, and I will use it myself once it RTM’s.  The built-in health checks are nice, so that you don’t have to write that logic yourself.  I’m all about punting as much logic onto someone else as possible.

There are some little things I wish were a little easier though.

  1. It seems like the HealthCheckResult.CheckStatus == CheckStatus.Healthy code  is going to be extremely common.  It’d be nice if there was a helper prop off of HealthCheckResult like HealthCheckResult.IsHealthy which does that computation for you.  Much like HttpResponseMessage has a IsSuccessStatusCode property which is super useful.  Although, I understand that “Healthy” is a relative term that’s tough to globally define.  Some people might think that the CheckStatus.Warning would qualify as being Healthy and others wouldn’t.  Ubiquitous languages are hard.
  2. I wish there was a way you could override the name for the built-in Health Checks.  Like the URL Health Check automatically takes on the Name UrlCheck(yourUrlHere) such as UrlCheck(http://google.com).  You’ll need this name if you want to pull out the specific results of a Health Check.  I had expected to be able to specify the name of each Health Check and store it in something like a HealthCheckConstants class for easily retrieval.  Instead, I need to follow this convention when using the constants class, which isn’t the end of the world, but being able to override the name would be nice.
  3. When calling the RunGroupAsync method, I wish you could just specify the group name rather than the HealthCheckGroup instance and let the RunGroupAsync method handle getting the HealthCheckGroup instance.
  4. There should be no Cache Duration on the URL Check.  5 minutes is just too long of a default, and IMO there should be no Cache Duration at all on the URL Check.  I control the frequency of how often my Health Check monitoring service hits my health check endpoint.  If I want it to check my service every minute, then I’d probably expect the Health Check result to be fresh every time and not be cached.

 

Overall, I really like this package, and it seems like it’s going to be really useful.   I plan on using this when it RTM’s, so I’ll keep this post up to date when I see they make changes to this package.

13 thoughts on “Using the Microsoft.AspNetCore.HealthChecks Package

  1. Hi
    From this blog I could see that those services that needs to be watched for health check needs to be registered at startup. Can that be modified such that I will be able to take those services from repository just before the health check happens and perform the health check.

    This features helps me to dynamically add more services to be watched. Under the above stated implementation (in the URL) after adding a new service for health check watch I need to restart my app.

    • Thanks for the comment, Venkatesh!

      Currently – I don’t see a way to do that from the IHealthCheckService. It doesn’t look like there’s a method similar to the one in middleware that lets you “inline” a method to run like the app.Use or app.Run methods.

      I think about the best you could do is to call individual health checks via RunCheckAsync or running groups of checks via RunGroupAsync based on whatever conditional logic you’re thinking of doing. If you want to dynamically change the threshold of what’s being tested without relaunching the app, I would probably try to create a custom Health Check and inject in my settings from Configuration (via IOptions). For instance, if you did a memory check and you wanted to switch it from a 1GB limit to 2GB limit, you should be able to just change your config (whether that’s environment or a config file) and have it just work. Again – that’s in theory, I haven’t tested that myself. 🙂

      I was going to suggest opening up a GitHub issue about it – but it looks like you’ve already done that. You’re one step ahead of me! 🙂

      • Hey thanks. My idea was also in same lines. Infact this feature is going to add lots of fire power. I’m just extropolating the same into a full fledged service discovery app like spring boot eureka service

    • Can you please confirm compare your implementation with what is up coming with. Net core

      • I’m not sure what else is up coming in the dotnet core implementation by MS, there seems to be a fair amount of pre-written checks being implemented such as SQL and Azure checks for example, App.Metrics won’t include things like this as there are just too many possible things to check and they aren’t difficult to write. It’s not possible at the moment to group health checks in App.Metrics either, however I don’t really see there ever being enough health checks in an application to worry about grouping.

        In addition to the MS implementation, App.Metrics:

        – Reports health check results as gauge metrics so that we can view results overtime in the chosen metrics backend.
        – Allows checks on metrics recorded at runtime so we can implement SLA type checks e.g. an api’s requests 98th percentile should be less than 50ms.
        – As well as registering health checks fluently App.Metrics allows health checks to be implemented as a stand alone class which supports dependency injection, these health check implemenations are automatically registered on start up.

        Reading through “Some Feedback on the Design” in this article, App.Metrics already implements points 1, 2, and 4.

      • I found that App Metrics does not contain built-in Sql health checker, while Standatd asp.net Health checker has it 🙂

  2. Check this documentation that I wrote using the Alpha version of this library, too, related to HealthChecks:
    https://docs.microsoft.com/en-us/dotnet/standard/microservices-architecture/implement-resilient-applications/monitor-app-health

    This library will be officially published as NuGet package, as part of ASP.NET Core 2.1, soon.

    Note that the following NuGet package has NOT been published by Microsoft but by @seven:
    https://www.nuget.org/packages/Microsoft.AspNetCore.HealthChecks/1.0.0

  3. Can you please know how to implement single check using RunCheckAsync ?

    I have many URLs . I want check single by single.
    So I can judge each URL will take time.

Leave a Reply