Introducing ASP.NET HealthChecks.Extensions - Conditional Health Checks
Get link
Facebook
X
Pinterest
Email
Other Apps
ASP.NET Core offers the Health Checks Middleware, in order to check the health state of your .NET Core API. However is not always desirable to run all the registered Health Checks in every context.
For example, in environments like the development one, some dependencies might not be available. Other dependencies might be used later during the application lifetime, maybe when their configuration is finished, or after a feature flag is enabled.
Would be nice to decide when to run a health check. One possible way is to write the condition in the Startup class, as bellow:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
The Redis health check is added only when the configuration setting has the expected value. If the configuration is changed during the application lifetime, then the only way to re-add the Redis health check is by restarting the API, in order to call again the ConfigureServices method.
Another good reason to switch on or off a health check is when a feature uses one or maybe more dependencies. If that feature is not yet enabled then there is no reason to check the health of those dependencies and to report an overall unhealthy status although all the other features are just fine. Once the feature is enabled, then the health checks should report as expected. This cannot be implemented in the Startup class like above, as runtime changes aren't detected and re-evaluated.
Conditional Health Checks are part of this package and leverage the possibility to decide when a health check should be actually performed. They encapsulate a boolean result, based on which the health check proceeds or not. If the health check does not proceed, then its tags list will contain a specific tag and the health result will be Healthy.
With Conditional Health Checks the condition could be expressed in several ways: as a booleanvalue, as a predicate, or using a strongly typed policy. The checking condition could be associated with one or more Health Checks.
The simplest use case is for a dependency that is available in a certain environment. Environments don't change during the application lifetime, so we can use Conditional Health Checks to enable a Health Check with a boolean value like this:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
The Redis health check is added using the AddRedis(AspNetCore.Diagnostics.HealthChecks). The CheckOnlyWhen(HealthChecks.Extensions) configures the Redis health check to run on an environment other than the development one. CheckOnlyWhen knows about the Redis health check registration via the Registrations.Redis string, the actual name used by AspNetCore.Diagnostics.HealthChecks when it implemented the Redis Health Checks. CheckOnlyWhen finds this health check registration by this name and decorates it with the given condition, so if the condition is true it will run the actual Redis health check, otherwise, it won't run it.
A different use case is when it is desired to switch the health check on configuration changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
The health check on MyHealthCheck happens only when the value of the configuration setting MyHealthCheck:Setting is updated with an actual string. The API doesn't have to be restarted, as the predicate is evaluated every time before the actual execution of the health check code.
Another use case is when the predicate result is provided by a service. A service that resolves features flags, answering a question like if a feature flag is set or not, can be used by CheckOnlyWhen as it has an overload to support this scenario:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This is possible because the ServiceProvider is accessible via the sp argument. The HealthCheckContext of the current health check policy can also be used, and an optional CancellationToken to pass it down the stream, a practice that I strongly suggest.
The conditions could complex and often need to be reused across different health checks configurations. For this scenario consider using the strongly typed health checks. A strongly typed health check is a class that implements the IConditionalHealthCheckPolicy interface.
The policies can be designed as any other class, with no restrictions. They can have collaborators, as all are resolved by the ServiceProvider itself. Even more, the policies can have extra arguments in their constructor.
Reusing the same feature flags scenario, one could implement a solution like the following
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This feature flags implementation is not very smart, as all are disabled for now. But what matters here is we can use this to create a conditional health check policy:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
The FeatureFlagsPolicy's constructor receives a flag name and the required dependencies to fulfill its functionality. The name argument is used by the IFeatureFlags collaborator to see if the corresponding flag is set or not, so the Evalute method will return this result.
What's left is to register the FeatureFlagsPolicy to work with the desired health check.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This works because the FeatureFlagsPolicy objects are created with the help of the ServiceProvider, so all its dependencies must be first registered in the services collection. What ServiceProvider cannot solve is the name of the flag, so this is why it is conveniently configured by the developer using the conditionalHealthCheckPolicyCtorArgs argument. The experience is very similar to registering a health check itself, actually, this feature is shamelessly borrowed from here. There is also an overload to create it yourself and in those overloads you are passed the ServiceProvider and the HealthCheckContext.
There is also the option to associate multiple health checks with one single condition:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Both Redis and RabitMQ health checks are executed only when the BlackFridayPromotion is active.
If you override your own ResponseWriter, like I did in the samples project supporting this package, in order to write all the necessary details when you access the /health, then a possible result could be the following one:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
"description": "Health check on `redis` will not be evaluated as its checking condition is not met. This does not mean your dependency is healthy, but the health check operation on this dependency is not configured to be executed yet."
Redis was not run here, and we can tell because the Redis entry contains the NotChecked tag. This is useful for observability so based on it other systems could take different decisions. The health check status, as I mentioned before, is by default Healthy, but both the status and the tag could be configured to be based on your needs. This is done by providing an options argument with the CheckOnlyWhen call:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
When the RabitMQ health check is disabled then the tags list contains the NotActive tag and the status is HealthStatus.Degraded.
Health checks are an essential feature for observability, but I feel like there also they should be non-intrusive, non-invasive, and flexible. Currently, there is no built in support to switch on/off the ASP.NET Core health checks, but with Conditional Health Checks from the HealthChecks.Extensions there is now a flexible way to add more value in this direction.
Having an website with HTTP and HTTPS bindings, running on my local IIS 7.5 server, I encountered this error with Chrome, Error 101 (net::ERR_CONNECTION_RESET) while trying to access it with HTTPS protocol. I checked the IIS and the bindings were fine, both protocols were set and self-signed certificate didn't expire yet. Since the website it wasn't accessible on the HTTPS protocol and everything seems ok at the IIS level, I suspected there was something which it can be fixed with the netsh command. The first thing to do was to list the SSL server certificate bindings: netsh http show sslcert As I expected, there was nothing shown. To "add a new SSL server certificate binding and corresponding client certificate policies for an IP address and port" (from help), netsh can be used with some basic parameters: ipport, certhash and appid. The certhash value can be read either from ISS Manager (Manage server\Server Certificates, select a certificate, click View in the ri...
Moq is by far the most used mocking library for .NET. It provides objects to either mimic real services implementations or to verify if they were called in a certain way by the Service Under Test. Often teams work hard to have meaningful logs in order to help with investigations, to have the possibility to define alarms based on the content of the logs, or even to help with instrumentation. If there is a log entry that resembles as an exception, then a notification might be sent to a Teams/Slack channel, an email, or even to randomly start the alarms on your team's members' cars (joking, don't do that, you know everyone ignores that Slack channel). There are many situations when an investigation ends up with no resolution, and with a promise to the client that the missing logging lines will soon be added. This it translates into development costs, reputation loss, and maybe most importantly having a missed opportunity to learn about the system's behavior in the p...
In this article I will show how to model a table per hierarchy, a bit more complex than I found in books or other articles. I'm sure this model is present in real lif,e but I really didn't have the luck to find an example on the web when I needed it most. The complexity of this model is given by the Foreign Keys ( the Associations) which need to be placed in the derived entities types and the fact the discriminator depends on more than one column. The first problem I could fix, but the next one, with the discriminator based on two or three nullable columns I couldn't do it, unless if I use a single discriminator column (ie ItemType or something). The database is very simple. It is designed for an eshop which sells products, services and also has some nice packages, that combines products and services and offers them for good prices compared with if they would be bought alone. The products and services are very different in terms of how data describes them, so the decision ...
Comments
Post a Comment