diff --git a/src/OrchardCore.Modules/OrchardCore.Redis/HealthChecks/Extensions/RedisHealthCheckExtensions.cs b/src/OrchardCore.Modules/OrchardCore.Redis/HealthChecks/Extensions/RedisHealthCheckExtensions.cs new file mode 100644 index 00000000000..7eaab93add0 --- /dev/null +++ b/src/OrchardCore.Modules/OrchardCore.Redis/HealthChecks/Extensions/RedisHealthCheckExtensions.cs @@ -0,0 +1,9 @@ +using OrchardCore.Redis.HealthChecks; + +namespace Microsoft.Extensions.DependencyInjection; + +public static class RedisHealthCheckExtensions +{ + public static IHealthChecksBuilder AddRedisCheck(this IHealthChecksBuilder healthChecksBuilder) + => healthChecksBuilder.AddCheck("Redis Health Check"); +} diff --git a/src/OrchardCore.Modules/OrchardCore.Redis/HealthChecks/RedisHealthCheck.cs b/src/OrchardCore.Modules/OrchardCore.Redis/HealthChecks/RedisHealthCheck.cs new file mode 100644 index 00000000000..e220b3763fe --- /dev/null +++ b/src/OrchardCore.Modules/OrchardCore.Redis/HealthChecks/RedisHealthCheck.cs @@ -0,0 +1,54 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Diagnostics.HealthChecks; + +namespace OrchardCore.Redis.HealthChecks; + +public class RedisHealthCheck : IHealthCheck +{ + private const int Timeout = 30; + + private readonly IServiceProvider _serviceProvider; + + public RedisHealthCheck(IServiceProvider serviceProvider) => _serviceProvider = serviceProvider; + + public async Task CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default) + { + try + { + var redisService = _serviceProvider.GetService(); + if (redisService == null) + { + return HealthCheckResult.Unhealthy(description: $"The service '{nameof(IRedisService)}' isn't registered."); + } + + if (redisService.Connection == null) + { + await redisService.ConnectAsync(); + } + + if (redisService.Connection.IsConnected) + { + var time = await redisService.Database.PingAsync(); + if (time > TimeSpan.FromSeconds(Timeout)) + { + return HealthCheckResult.Unhealthy(description: $"The Redis server couldn't be reached within {Timeout} seconds and might be offline or have degraded performance."); + } + else + { + return HealthCheckResult.Healthy(); + } + } + else + { + return HealthCheckResult.Unhealthy(description: "Couldn't connect to the Redis server."); + } + } + catch (Exception ex) + { + return HealthCheckResult.Unhealthy("Retrieving the status of the Redis service failed.", ex); + } + } +} diff --git a/src/OrchardCore.Modules/OrchardCore.Redis/HealthChecks/Startup.cs b/src/OrchardCore.Modules/OrchardCore.Redis/HealthChecks/Startup.cs new file mode 100644 index 00000000000..c5a81553976 --- /dev/null +++ b/src/OrchardCore.Modules/OrchardCore.Redis/HealthChecks/Startup.cs @@ -0,0 +1,19 @@ +using Microsoft.Extensions.DependencyInjection; +using OrchardCore.Modules; + +namespace OrchardCore.Redis.HealthChecks; + +[RequireFeatures("OrchardCore.HealthChecks")] +public class Startup : StartupBase +{ + // The order of this startup configuration should be greater than zero to register the Redis check early, + // so the health check can be reported alongside with other health checks in the system. + public override int Order => 100; + + public override void ConfigureServices(IServiceCollection services) + { + services + .AddHealthChecks() + .AddRedisCheck(); + } +} diff --git a/src/docs/reference/modules/Redis/README.md b/src/docs/reference/modules/Redis/README.md index 3d6b38bf880..e8dfdeff496 100644 --- a/src/docs/reference/modules/Redis/README.md +++ b/src/docs/reference/modules/Redis/README.md @@ -8,6 +8,10 @@ Integrates Redis into Orchard Core. Provides the following features: - Redis Lock: Distributed Lock using Redis. - Redis DataProtection: Distributed DataProtection using Redis. +## Health Checks + +This module provides a health check to report the status for the Redis server. Refer also to the [Health Checks Section](../HealthChecks/README.md). + ## Video