Skip to content

Commit 888310c

Browse files
authored
FUND-2217 Implemented Advanced Healthchecks (#115)
1 parent 9ecf9b0 commit 888310c

File tree

25 files changed

+1206
-30
lines changed

25 files changed

+1206
-30
lines changed

src/OneGround.ZGW.Autorisaties.Web/Startup.cs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
using OneGround.ZGW.Common.Web;
2121
using OneGround.ZGW.Common.Web.Extensions.ApplicationBuilder;
2222
using OneGround.ZGW.Common.Web.Extensions.ServiceCollection;
23+
using OneGround.ZGW.Common.Web.HealthChecks;
2324
using OneGround.ZGW.Common.Web.Logging;
2425
using OneGround.ZGW.Common.Web.Middleware;
2526
using OneGround.ZGW.Common.Web.Services;
@@ -69,6 +70,8 @@ public void ConfigureServices(IServiceCollection services)
6970
}
7071
);
7172

73+
services.AddOneGroundHealthChecks().AddRedisCheck();
74+
7275
services.AddMassTransit(x =>
7376
{
7477
x.DisableUsageTelemetry();
@@ -123,12 +126,13 @@ public void ConfigureServices(IServiceCollection services)
123126
services.Replace(ServiceDescriptor.Singleton<IHttpMessageHandlerBuilderFilter, HttpLoggingFilter>());
124127
}
125128

126-
public static void Configure(IApplicationBuilder app, IWebHostEnvironment env)
129+
public static void Configure(WebApplication app, IWebHostEnvironment env)
127130
{
128131
app.UseCorrelationId();
129132
app.UseBatchId();
130133

131-
app.ConfigureZGWApi(env);
134+
app.ConfigureZgwApi(env);
132135
app.ConfigureZgwSwagger();
136+
app.MapOneGroundHealthChecks();
133137
}
134138
}

src/OneGround.ZGW.Besluiten.Web/Startup.cs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
using OneGround.ZGW.Common.Web;
2525
using OneGround.ZGW.Common.Web.Extensions.ApplicationBuilder;
2626
using OneGround.ZGW.Common.Web.Extensions.ServiceCollection;
27+
using OneGround.ZGW.Common.Web.HealthChecks;
2728
using OneGround.ZGW.Common.Web.Logging;
2829
using OneGround.ZGW.Common.Web.Middleware;
2930
using OneGround.ZGW.Common.Web.Services;
@@ -77,6 +78,8 @@ public void ConfigureServices(IServiceCollection services)
7778
}
7879
);
7980

81+
services.AddOneGroundHealthChecks().AddRedisCheck();
82+
8083
services.AddZGWAuditTrail<BrcDbContext>();
8184

8285
services.AddAutorisatiesServiceAgent(Configuration);
@@ -145,12 +148,13 @@ public void ConfigureServices(IServiceCollection services)
145148
services.Replace(ServiceDescriptor.Singleton<IHttpMessageHandlerBuilderFilter, HttpLoggingFilter>());
146149
}
147150

148-
public static void Configure(IApplicationBuilder app, IWebHostEnvironment env)
151+
public static void Configure(WebApplication app, IWebHostEnvironment env)
149152
{
150153
app.UseCorrelationId();
151154
app.UseBatchId();
152155

153-
app.ConfigureZGWApi(env);
156+
app.ConfigureZgwApi(env);
154157
app.ConfigureZgwSwagger();
158+
app.MapOneGroundHealthChecks();
155159
}
156160
}

src/OneGround.ZGW.Catalogi.Web/Startup.cs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
using OneGround.ZGW.Common.Web;
2121
using OneGround.ZGW.Common.Web.Extensions.ApplicationBuilder;
2222
using OneGround.ZGW.Common.Web.Extensions.ServiceCollection;
23+
using OneGround.ZGW.Common.Web.HealthChecks;
2324
using OneGround.ZGW.Common.Web.Logging;
2425
using OneGround.ZGW.Common.Web.Middleware;
2526
using OneGround.ZGW.Common.Web.Services;
@@ -71,6 +72,8 @@ public void ConfigureServices(IServiceCollection services)
7172
}
7273
);
7374

75+
services.AddOneGroundHealthChecks().AddRedisCheck();
76+
7477
services.AddZGWAuditTrail<ZtcDbContext>();
7578

7679
services.AddTransient<ICatalogEventService, CatalogEventService>();
@@ -146,12 +149,13 @@ public void ConfigureServices(IServiceCollection services)
146149
services.Replace(ServiceDescriptor.Singleton<IHttpMessageHandlerBuilderFilter, HttpLoggingFilter>());
147150
}
148151

149-
public static void Configure(IApplicationBuilder app, IWebHostEnvironment env)
152+
public static void Configure(WebApplication app, IWebHostEnvironment env)
150153
{
151154
app.UseCorrelationId();
152155
app.UseBatchId();
153156

154-
app.ConfigureZGWApi(env);
157+
app.ConfigureZgwApi(env);
155158
app.ConfigureZgwSwagger();
159+
app.MapOneGroundHealthChecks();
156160
}
157161
}

src/OneGround.ZGW.Common.Web/Extensions/ApplicationBuilder/ZGWApiApplicationBuilderExtensions.cs

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ namespace OneGround.ZGW.Common.Web.Extensions.ApplicationBuilder;
99

1010
public static class ZGWApiApplicationBuilderExtensions
1111
{
12-
public static void ConfigureZGWApi(
13-
this IApplicationBuilder app,
12+
public static void ConfigureZgwApi(
13+
this WebApplication app,
1414
IWebHostEnvironment env,
1515
bool dontRegisterLogBadRequestMiddleware = false,
1616
Action<IApplicationBuilder> registerMiddleware = null
@@ -48,10 +48,6 @@ public static void ConfigureZGWApi(
4848
// Register additional (API specific) Middleware. Should be done after Authentication, Authorization and Logging but before mapping the controllers, etc!
4949
registerMiddleware?.Invoke(app);
5050

51-
app.UseEndpoints(endpoints =>
52-
{
53-
endpoints.MapHealthChecks("/health");
54-
endpoints.MapControllers();
55-
});
51+
app.MapControllers();
5652
}
5753
}

src/OneGround.ZGW.Common.Web/Extensions/ServiceCollection/ZGWApiServiceCollectionExtensions.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
using OneGround.ZGW.Common.Web.ErrorHandling;
1414
using OneGround.ZGW.Common.Web.Extensions.ServiceCollection.ZGWApiExtensions;
1515
using OneGround.ZGW.Common.Web.Filters;
16+
using OneGround.ZGW.Common.Web.HealthChecks;
17+
using OneGround.ZGW.Common.Web.HealthChecks.Builder;
1618
using OneGround.ZGW.Common.Web.Middleware;
1719
using OneGround.ZGW.Common.Web.Validations;
1820
using OneGround.ZGW.Common.Web.Versioning;
@@ -53,8 +55,6 @@ public static void AddZGWApi(
5355

5456
var callingAssembly = Assembly.GetCallingAssembly();
5557

56-
services.AddHealthChecks();
57-
5858
services.AddMediator(callingAssembly, zgwApiOptions.ApiServiceSettings);
5959

6060
services.AddAutoMapper(callingAssembly);
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
using System;
2+
using Microsoft.Extensions.DependencyInjection;
3+
using Microsoft.Extensions.Diagnostics.HealthChecks;
4+
using Microsoft.Extensions.Options;
5+
using OneGround.ZGW.Common.Web.HealthChecks.Checks;
6+
using OneGround.ZGW.Common.Web.HealthChecks.Options;
7+
8+
namespace OneGround.ZGW.Common.Web.HealthChecks.Builder;
9+
10+
public class OneGroundHealthCheckBuilder(IServiceCollection services, IHealthChecksBuilder healthChecksBuilder)
11+
{
12+
private readonly OptionsBuilder<OneGroundHealthChecksOptions> _optionsBuilder = services.AddOptions<OneGroundHealthChecksOptions>();
13+
14+
public OneGroundHealthCheckBuilder AddRedisCheck()
15+
{
16+
var healthCheckName = RedisHealthCheck.HealthCheckName;
17+
healthChecksBuilder.AddCheck<RedisHealthCheck>(healthCheckName);
18+
AddRegisteredHealthCheck(healthCheckName);
19+
return this;
20+
}
21+
22+
public OneGroundHealthCheckBuilder AddEventBusCheck()
23+
{
24+
var healthCheckName = EventBusHealthCheck.HealthCheckName;
25+
healthChecksBuilder.AddCheck<EventBusHealthCheck>(healthCheckName);
26+
AddRegisteredHealthCheck(healthCheckName);
27+
return this;
28+
}
29+
30+
public OneGroundHealthCheckBuilder AddCheck<T>(string healthCheckName)
31+
where T : class, IHealthCheck
32+
{
33+
healthChecksBuilder.AddCheck<T>(healthCheckName);
34+
AddRegisteredHealthCheck(healthCheckName);
35+
return this;
36+
}
37+
38+
public OneGroundHealthCheckBuilder Configure(Action<OneGroundHealthChecksOptions> configureOptions)
39+
{
40+
_optionsBuilder.Configure(configureOptions);
41+
return this;
42+
}
43+
44+
private void AddRegisteredHealthCheck(string healthCheckName)
45+
{
46+
_optionsBuilder.Configure(x =>
47+
{
48+
if (x.RegisteredHealthChecks.Contains(healthCheckName))
49+
{
50+
throw new InvalidOperationException($"HealthCheck with name {healthCheckName} is already registered");
51+
}
52+
x.RegisteredHealthChecks.Add(healthCheckName);
53+
});
54+
}
55+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Threading;
4+
using System.Threading.Tasks;
5+
using MassTransit;
6+
using Microsoft.Extensions.Diagnostics.HealthChecks;
7+
8+
namespace OneGround.ZGW.Common.Web.HealthChecks.Checks;
9+
10+
public class EventBusHealthCheck(IBusControl busControl) : IHealthCheck
11+
{
12+
public const string HealthCheckName = "EventBus";
13+
14+
private readonly IBusControl _busControl = busControl ?? throw new ArgumentNullException(nameof(busControl));
15+
16+
public Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default)
17+
{
18+
try
19+
{
20+
var healthResult = _busControl.CheckHealth();
21+
22+
var data = new Dictionary<string, object> { { "status", healthResult.Status.ToString() }, { "description", healthResult.Description } };
23+
24+
var result = healthResult.Status switch
25+
{
26+
BusHealthStatus.Healthy => HealthCheckResult.Healthy("Event bus is healthy", data),
27+
BusHealthStatus.Degraded => HealthCheckResult.Degraded($"Event bus is degraded: {healthResult.Description}", null, data),
28+
BusHealthStatus.Unhealthy => HealthCheckResult.Unhealthy($"Event bus is unhealthy: {healthResult.Description}", null, data),
29+
_ => HealthCheckResult.Unhealthy("Event bus status is unknown", null, data),
30+
};
31+
32+
return Task.FromResult(result);
33+
}
34+
catch (Exception ex)
35+
{
36+
return Task.FromResult(HealthCheckResult.Unhealthy("Event bus health check failed", ex));
37+
}
38+
}
39+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Threading;
5+
using System.Threading.Tasks;
6+
using Microsoft.Extensions.Diagnostics.HealthChecks;
7+
using StackExchange.Redis;
8+
9+
namespace OneGround.ZGW.Common.Web.HealthChecks.Checks;
10+
11+
public class RedisHealthCheck(IConnectionMultiplexer redis) : IHealthCheck
12+
{
13+
public const string HealthCheckName = "Redis";
14+
15+
private readonly IConnectionMultiplexer _redis = redis ?? throw new ArgumentNullException(nameof(redis));
16+
17+
public async Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default)
18+
{
19+
try
20+
{
21+
if (!_redis.IsConnected)
22+
{
23+
return HealthCheckResult.Unhealthy("Redis connection is not established");
24+
}
25+
26+
var database = _redis.GetDatabase();
27+
var pingResult = await database.PingAsync();
28+
29+
if (pingResult.TotalMilliseconds > 1000)
30+
{
31+
return HealthCheckResult.Degraded(
32+
$"Redis is responding slowly: {pingResult.TotalMilliseconds:F2}ms",
33+
data: new Dictionary<string, object>
34+
{
35+
{ "ping_ms", pingResult.TotalMilliseconds },
36+
{ "endpoints", string.Join(", ", _redis.GetEndPoints().Select(e => e.ToString())) },
37+
}
38+
);
39+
}
40+
41+
return HealthCheckResult.Healthy(
42+
$"Redis is healthy (ping: {pingResult.TotalMilliseconds:F2}ms)",
43+
data: new Dictionary<string, object>
44+
{
45+
{ "ping_ms", pingResult.TotalMilliseconds },
46+
{ "endpoints", string.Join(", ", _redis.GetEndPoints().Select(e => e.ToString())) },
47+
}
48+
);
49+
}
50+
catch (Exception ex)
51+
{
52+
return HealthCheckResult.Unhealthy(
53+
"Redis health check failed",
54+
exception: ex,
55+
data: new Dictionary<string, object> { { "error", ex.Message } }
56+
);
57+
}
58+
}
59+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
using Microsoft.Extensions.Diagnostics.HealthChecks;
2+
3+
namespace OneGround.ZGW.Common.Web.HealthChecks.Models;
4+
5+
public class OneGroundHealthChecksDetail
6+
{
7+
public required string Name { get; set; }
8+
public required HealthStatus Status { get; set; }
9+
public string Reason { get; set; } = string.Empty;
10+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
using System.Collections.Generic;
2+
using Microsoft.Extensions.Diagnostics.HealthChecks;
3+
4+
namespace OneGround.ZGW.Common.Web.HealthChecks.Models;
5+
6+
public class OneGroundHealthChecksResult
7+
{
8+
public required HealthStatus Status { get; set; }
9+
public string Impact { get; set; } = null;
10+
public string Reason { get; set; } = null;
11+
public List<OneGroundHealthChecksDetail> Details { get; set; } = [];
12+
}

0 commit comments

Comments
 (0)