Sam Ware
Sam Ware

Reputation: 127

How do you add health checks to a .net 6 service through dependency injection?

I am trying to add multiple health checks to a background service. I don't want the core application that adds the background service to have know the details of or have to add all the health checks at the core application level. I could add a IHealthChecksBuilder at the service constructor and add a IHealthCheck class for each check to the background service class. But how do I reference to the objects or configuration of the background service. I have only written a couple of CLEAN Architecture projects so I am still trying to get the hang pushing the logic deeper and keeping the components totally separated.

Things that I am trying to avoid in my Program.cs:

        services.AddHostedService<ETL>();
        services.AddHealthChecks()
            .ForwardToPrometheus()
            .AddCheck<StartupHealthCheck>("ETL");

Which I want to be handled by a simple.

        services.AddHostedService<ETL>();
        services.AddHealthChecks()
            .ForwardToPrometheus();
        

Thank for the help

Upvotes: 0

Views: 2400

Answers (1)

ProgrammingLlama
ProgrammingLlama

Reputation: 38860

You could add an extension method to automatically find and register checks in the assembly:

public static class HealthChecksExtensions
{
    public static IHealthChecksBuilder AddAssemblyChecks(this IHealthChecksBuilder checksBuilder, params Assembly[] assemblies)
    {
        var checks = assemblies
            .SelectMany(a => a.GetTypes())
            .Where(a => !a.IsAbstract && !a.IsInterface && !a.IsGenericTypeDefinition && a.IsAssignableTo(typeof(IHealthCheck)));

        foreach (var check in checks)
        {
            var registration = new HealthCheckRegistration(
                check.GetType().Name,
                sp => (IHealthCheck)ActivatorUtilities.GetServiceOrCreateInstance(sp, check),
                null,
                null
            );
            checksBuilder.Add(registration);
        }
        return checksBuilder;
    }
}

Usage:

services.AddHealthChecks()
    .ForwardToPrometheus()
    .AddAssemblyChecks(Assembly.GetEntryAssembly());

Or if the health checks are in another assembly (library) that you add to a project, you could possibly add an extension method there that doesn't take params Assembly[] assemblies and instead use Assembly.GetExecutingAssembly() in the method, which would find any in the assembly that the extension method belongs to:

public static class HealthChecksExtensions
{
    public static IHealthChecksBuilder AddAssemblyChecks(this IHealthChecksBuilder checksBuilder)
    {
        var checks = Assembly.GetExecutingAssembly()
            .GetTypes()
            .Where(a => !a.IsAbstract && !a.IsInterface && !a.IsGenericTypeDefinition && a.IsAssignableTo(typeof(IHealthCheck)));

        foreach (var check in checks)
        {
            var registration = new HealthCheckRegistration(
                check.GetType().Name,
                sp => (IHealthCheck)ActivatorUtilities.GetServiceOrCreateInstance(sp, check),
                null,
                null
            );
            checksBuilder.Add(registration);
        }
        return checksBuilder;
    }
}

Or if you're OK registering the checks manually, but elsewhere:

public static class HealthCheckExtensions
{
    public static IHealthChecksBuilder AddMyChecks(this IHealthChecksBuilder checksBuilder)
    {
        checksBuilder.AddCheck<StartupHealthCheck>("ETL");
        return checksBuilder;
    }
}

Usage:

services.AddHealthChecks()
    .ForwardToPrometheus()
    .AddMyChecks();

Upvotes: 1

Related Questions