Skip to content

HealthCheck Extensions Project#529

Merged
Havret merged 5 commits into
Havret:masterfrom
s4ndr0ne:healtcheck
Jul 20, 2025
Merged

HealthCheck Extensions Project#529
Havret merged 5 commits into
Havret:masterfrom
s4ndr0ne:healtcheck

Conversation

@s4ndr0ne

Copy link
Copy Markdown
Contributor

Hi @Havret,

Starting from the discussion (#528), this is the implementation.

As suggested, I created a separate project containing the healthcheck service and reported its use in the application.

Tell me what to improve.

Best,
Alessandro

@Havret Havret self-requested a review July 19, 2025 19:25

@Havret Havret left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@s4ndr0ne As I mentioned in our discussion, this is an excellent idea. 🙂 I left some comments with hints on how we can use the existing building blocks to polish it a bit.


public class ArtemisHealthCheckService : IHealthCheck
{
private static bool IsOpen = true;

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This state should not be static. It may fall apart if we have more than one connection in a single application (which is the case in most production use cases I've seen). Instead, we should register this service per individual ActiveMQ connection.

/// <param name="name">The name of the ActiveMQ connection to check.</param>
/// <param name="factory">Optional factory function to create custom connection for health check.</param>
/// <returns>The <see cref="IHealthChecksBuilder"/> for further configuration.</returns>
public static IHealthChecksBuilder AddActiveMqHealthCheck(this IHealthChecksBuilder builder, string name, string[] tags = null)

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
public static IHealthChecksBuilder AddActiveMqHealthCheck(this IHealthChecksBuilder builder, string name, string[] tags = null)
public static IHealthChecksBuilder AddActiveMq(this IHealthChecksBuilder builder, string name, string[] tags = null)

/// <param name="name">The name of the ActiveMQ connection to check.</param>
/// <param name="factory">Optional factory function to create custom connection for health check.</param>
/// <returns>The <see cref="IHealthChecksBuilder"/> for further configuration.</returns>
public static IHealthChecksBuilder AddActiveMqHealthCheck(this IHealthChecksBuilder builder, string name, string[] tags = null)

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd also suggest slightly changing the signature. If we could pass not only the health check name but also the IActiveMqBuilder (the same one we're using in the DI package) we would gain access to the connection or its events.

So this could like sth like this:

public static IHealthChecksBuilder AddActiveMqHealthCheck(
    this IHealthChecksBuilder builder,
    string name,
    IActiveMqBuilder activeMqBuilder,
    string[] tags = null)
{
    // Register a connection callback to wire up health check event handlers
    activeMqBuilder.ConfigureConnection((provider, connection) =>
    {
        var healthCheckService = provider.GetRequiredKeyedService<ArtemisHealthCheckService>(activeMqBuilder.Name);

        connection.ConnectionRecovered += healthCheckService.ConnectionRecovered;
        connection.ConnectionClosed += healthCheckService.ConnectionClosed;
        connection.ConnectionRecoveryError += healthCheckService.ConnectionRecoveryError;
    });

    // Ensure the ArtemisHealthCheckService is registered as a keyed singleton
    builder.Services.AddKeyedSingleton<ArtemisHealthCheckService>(activeMqBuilder.Name);

    // Register the health check using the same keyed instance to ensure consistency
    builder.Add(new HealthCheckRegistration(
        name,
        provider => provider.GetRequiredKeyedService<ArtemisHealthCheckService>(activeMqBuilder.Name),
        HealthStatus.Unhealthy,
        tags));

    return builder;
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you think if I directly extend IActionBuilder like this to enable it?


        public static IActiveMqBuilder EnableHealthChecks(this IActiveMqBuilder builder)
        {
            return builder.ConfigureConnection((provider, connection) =>
            {
                var healthCheckService = provider.GetRequiredKeyedService<ArtemisHealthCheckService>(builder.Name);

                healthCheckService.ConnectionOpen();

                // Attach event handlers to the connection for health check updates

                connection.ConnectionClosed += (sender, args) => healthCheckService.ConnectionClosed(sender, args);
                connection.ConnectionRecovered += (sender, args) => healthCheckService.ConnectionRecovered(sender, args);
                connection.ConnectionRecoveryError += (sender, args) => healthCheckService.ConnectionRecoveryError(sender, args);
            });
        }

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not a fan of this kind of indirect reference. It may be problematic and not easy for users to figure out.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok reverted to previous version with parameter

// Add health checks for ActiveMQ Artemis
            services
             .AddHealthChecks()
             .AddActiveMq(name: "my-artemis-cluster", activeMqBuilder: activeMqBuilder, tags: new[] { "activemq" });

builder.Services.AddSingleton(sp => new ArtemisHealthCheckService());

// Register the health check using the typed approach
return builder.AddTypeActivatedCheck<ArtemisHealthCheckService>(name,

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I'm not mistaken, this registration will create its own instance of ArtemisHealthCheckService, regardless of what you registered above as a singleton.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you are right by registering it this way we can have it for the necessary instances

services.AddActiveMq(name: "my-artemis-cluster" ...
   .EnableQueueDeclaration()                 
   .EnableHealthChecks(); // Enable health check integration
services
             .AddHealthChecks()
             .AddActiveMq("my-artemis-cluster", tags: new[] { "activemq" });

tags: tags ?? new[] { "activemq" });
}

public static void ConfigureConnection(IServiceProvider _, IConnection connection)

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then we wouldn't need this method.


public class ArtemisHealthCheckService : IHealthCheck
{
private static bool IsOpen = true;

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should, in fact, be false. When the connection opens, it should trigger a change in state.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add method

public void ConnectionOpen() { IsOpen = true; Description = "Connection is open"; }

for the connected state change

/// <summary>
/// Service for managing ActiveMQ Artemis health checks.
/// </summary>
public class ArtemisHealthCheckService : IHealthCheck

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please make this internal.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

@Havret Havret merged commit 7aed63a into Havret:master Jul 20, 2025
2 checks passed
@Havret

Havret commented Jul 20, 2025

Copy link
Copy Markdown
Owner

Thanks @s4ndr0ne, merged!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants