Skip to content

Commit 1aaaff7

Browse files
Moving health checks details to extensions class
1 parent fd0d544 commit 1aaaff7

3 files changed

Lines changed: 65 additions & 91 deletions

File tree

src/Dotnet6.GraphQL4.Store.Repositories/DependencyInjection/Extensions/ServiceCollectionExtensions.cs

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,16 @@
33
using Microsoft.EntityFrameworkCore;
44
using Microsoft.Extensions.Configuration;
55
using Microsoft.Extensions.DependencyInjection;
6+
using Microsoft.Extensions.Diagnostics.HealthChecks;
67
using Microsoft.Extensions.Options;
78

89
namespace Dotnet6.GraphQL4.Store.Repositories.DependencyInjection.Extensions
910
{
1011
public static class ServiceCollectionExtensions
1112
{
13+
private static readonly string[] ReadinessTags = {"ready"};
14+
private static readonly string[] LivenessTags = {"live"};
15+
1216
public static IServiceCollection AddApplicationDbContext(this IServiceCollection services)
1317
=> services
1418
.AddScoped<DbContext, StoreDbContext>()
@@ -18,8 +22,20 @@ public static OptionsBuilder<SqlServerRetryingOptions> ConfigureSqlServerRetryin
1822
=> services
1923
.AddOptions<SqlServerRetryingOptions>()
2024
.Bind(section)
21-
.Validate(
22-
validation: options => options.MaxRetryCount <= 10 || options.MaxSecondsRetryDelay <= 10,
23-
failureMessage: "Max value for Retry or Delay must be 10.");
25+
.ValidateDataAnnotations()
26+
.ValidateOnStart();
27+
28+
public static IHealthChecksBuilder AddDbContextHealthChecks(this IServiceCollection services)
29+
=> services.AddHealthChecks()
30+
.AddDbContextCheck<DbContext>(
31+
name: "Sql Server (Live)",
32+
failureStatus: HealthStatus.Degraded,
33+
tags: LivenessTags)
34+
.AddDbContextCheck<StoreDbContext>(
35+
name: "Sql Server (Ready)",
36+
failureStatus: HealthStatus.Unhealthy,
37+
tags: ReadinessTags,
38+
customTestQuery: (dbContext, cancellationToken)
39+
=> dbContext.Products.AnyAsync(cancellationToken));
2440
}
2541
}
Lines changed: 25 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
11
using System;
2-
using System.Collections.Generic;
32
using System.Linq;
4-
using System.Text.Json;
5-
using System.Text.Json.Serialization;
6-
using System.Threading.Tasks;
3+
using HealthChecks.UI.Client;
74
using Microsoft.AspNetCore.Builder;
85
using Microsoft.AspNetCore.Http;
96
using Microsoft.AspNetCore.Routing;
@@ -13,16 +10,36 @@ namespace Dotnet6.GraphQL4.Store.WebAPI.Extensions.EndpointRouteBuilders
1310
{
1411
public static class HealthChecksEndpointRouteBuilderExtensions
1512
{
16-
private static readonly JsonSerializerOptions SerializerOptions = new() {DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, WriteIndented = true};
17-
private static readonly HealthCheck HealthCheck = new();
13+
private static readonly string[] ReadinessTags = {"ready"};
14+
private static readonly string[] LivenessTags = {"live"};
1815

19-
public static void MapApplicationHealthChecks(this IEndpointRouteBuilder endpoints, string pattern, Func<HealthCheckRegistration, bool> predicate = default)
16+
public static void MapLivenessHealthCheck(this IEndpointRouteBuilder endpoints, string pattern)
17+
=> endpoints.MapHealthChecks(
18+
pattern: pattern,
19+
predicate: registration
20+
=> registration.Tags.Any(item
21+
=> LivenessTags.Contains(item)));
22+
23+
public static void MapReadinessHealthCheck(this IEndpointRouteBuilder endpoints, string pattern)
24+
=> endpoints.MapHealthChecks(
25+
pattern: pattern,
26+
predicate: registration
27+
=> registration.Tags.Any(item
28+
=> ReadinessTags.Contains(item)));
29+
30+
public static void MapHealthCheck(this IEndpointRouteBuilder endpoints, string pattern)
31+
=> endpoints.MapHealthChecks(
32+
pattern: pattern,
33+
predicate: registration
34+
=> registration.Tags.Any() is false);
35+
36+
private static void MapHealthChecks(this IEndpointRouteBuilder endpoints, string pattern, Func<HealthCheckRegistration, bool> predicate = default)
2037
=> endpoints.MapHealthChecks(
2138
pattern: pattern,
2239
options: new()
2340
{
2441
AllowCachingResponses = false,
25-
ResponseWriter = WriteHealthCheckResponseAsync,
42+
ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse,
2643
Predicate = predicate,
2744
ResultStatusCodes =
2845
{
@@ -31,40 +48,5 @@ public static void MapApplicationHealthChecks(this IEndpointRouteBuilder endpoin
3148
[HealthStatus.Unhealthy] = StatusCodes.Status503ServiceUnavailable
3249
}
3350
});
34-
35-
private static Task WriteHealthCheckResponseAsync(HttpContext httpContext, HealthReport healthReport)
36-
{
37-
httpContext.Response.ContentType = "application/json";
38-
39-
HealthCheck.OverallStatus = healthReport.Status.ToString();
40-
HealthCheck.TotalCheckDuration = healthReport.TotalDuration.TotalSeconds.ToString("00:00:00.000");
41-
HealthCheck.DependencyHealthChecks = healthReport.Entries.Any()
42-
? healthReport.Entries.Select(dependency
43-
=> new DependencyHealthCheck
44-
{
45-
Name = dependency.Key,
46-
Status = dependency.Value.Status.ToString(),
47-
Duration = dependency.Value.Duration.TotalSeconds.ToString("00:00:00.000"),
48-
Error = dependency.Value.Exception?.Message
49-
})
50-
: default;
51-
52-
return httpContext.Response.WriteAsync(JsonSerializer.Serialize(HealthCheck, SerializerOptions));
53-
}
54-
}
55-
56-
internal class HealthCheck
57-
{
58-
public string OverallStatus { get; set; }
59-
public string TotalCheckDuration { get; set; }
60-
public IEnumerable<DependencyHealthCheck> DependencyHealthChecks { get; set; }
61-
}
62-
63-
internal record DependencyHealthCheck
64-
{
65-
public string Name { get; init; }
66-
public string Status { get; init; }
67-
public string Duration { get; init; }
68-
public string Error { get; init; }
6951
}
7052
}
Lines changed: 21 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
1-
using System.Linq;
21
using Dotnet6.GraphQL4.CrossCutting.DependencyInjection.Extensions;
32
using Dotnet6.GraphQL4.Domain.Abstractions.DependencyInjection.Extensions;
4-
using Dotnet6.GraphQL4.Store.Repositories.Contexts;
53
using Dotnet6.GraphQL4.Store.WebAPI.Extensions.EndpointRouteBuilders;
64
using Dotnet6.GraphQL4.Repositories.Abstractions.DependencyInjection.Extensions;
75
using Dotnet6.GraphQL4.Repositories.Abstractions.DependencyInjection.Options;
@@ -10,14 +8,12 @@
108
using Dotnet6.GraphQL4.Store.Repositories.DependencyInjection.Options;
119
using Dotnet6.GraphQL4.Store.WebAPI.DependencyInjection.Extensions;
1210
using Dotnet6.GraphQL4.Store.WebAPI.Graphs;
13-
using HealthChecks.UI.Client;
1411
using Microsoft.AspNetCore.Builder;
1512
using Microsoft.AspNetCore.Hosting;
13+
using Microsoft.AspNetCore.HttpLogging;
1614
using Microsoft.AspNetCore.Server.Kestrel.Core;
17-
using Microsoft.EntityFrameworkCore;
1815
using Microsoft.Extensions.Configuration;
1916
using Microsoft.Extensions.DependencyInjection;
20-
using Microsoft.Extensions.Diagnostics.HealthChecks;
2117
using Microsoft.Extensions.Hosting;
2218
using Microsoft.Extensions.Logging;
2319
using Serilog;
@@ -28,8 +24,6 @@ public class Startup
2824
{
2925
private readonly IConfiguration _configuration;
3026
private readonly IWebHostEnvironment _env;
31-
private readonly string[] _readinessTags = {"ready"};
32-
private readonly string[] _livenessTags = {"live"};
3327

3428
public Startup(IConfiguration configuration, IWebHostEnvironment env)
3529
{
@@ -43,10 +37,10 @@ public void Configure(IApplicationBuilder app , ILoggerFactory loggerFactory)
4337
app.UseDeveloperExceptionPage();
4438

4539
loggerFactory.AddSerilog();
46-
47-
app.UseApplicationExceptionHandler();
48-
49-
app.UseSerilogRequestLogging()
40+
41+
app.UseHttpLogging()
42+
.UseApplicationExceptionHandler()
43+
.UseSerilogRequestLogging()
5044
.UseApplicationGraphQL<StoreSchema>()
5145
.UseRouting()
5246
.UseEndpoints(
@@ -59,36 +53,29 @@ public void Configure(IApplicationBuilder app , ILoggerFactory loggerFactory)
5953
configurationRoot: _configuration as IConfigurationRoot,
6054
isProduction: _env.IsProduction());
6155

62-
endpoints.MapApplicationHealthChecks(
63-
pattern: _configuration["HealthChecksPatterns:Health"],
64-
predicate: registration
65-
=> registration.Tags.Any() is false);
66-
67-
endpoints.MapApplicationHealthChecks(
68-
pattern: _configuration["HealthChecksPatterns:Liveness"],
69-
predicate: registration
70-
=> registration.Tags.Any(item
71-
=> _livenessTags.Contains(item)));
56+
endpoints.MapHealthCheck(
57+
pattern: _configuration["HealthChecksPatterns:Health"]);
7258

73-
endpoints.MapApplicationHealthChecks(
74-
pattern: _configuration["HealthChecksPatterns:Readiness"],
75-
predicate: registration
76-
=> registration.Tags.Any(item
77-
=> _readinessTags.Contains(item)));
59+
endpoints.MapLivenessHealthCheck(
60+
pattern: _configuration["HealthChecksPatterns:Liveness"]);
7861

79-
endpoints.MapHealthChecks(
80-
pattern: _configuration["HealthChecksPatterns:UI"],
81-
options: new() {ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse});
62+
endpoints.MapReadinessHealthCheck(
63+
pattern: _configuration["HealthChecksPatterns:Readiness"]);
8264
});
8365
}
8466

8567
public void ConfigureServices(IServiceCollection services)
8668
{
87-
services.ConfigureTransactionOptions(_configuration.GetSection(nameof(TransactionOptions)));
88-
services.ConfigureSqlServerRetryingOptions(_configuration.GetSection(nameof(SqlServerRetryingOptions)));
69+
services.ConfigureTransactionOptions(
70+
section: _configuration.GetSection(nameof(TransactionOptions)));
71+
72+
services.ConfigureSqlServerRetryingOptions(
73+
section: _configuration.GetSection(nameof(SqlServerRetryingOptions)));
74+
75+
services.AddHttpLogging(options
76+
=> options.LoggingFields = HttpLoggingFields.RequestProperties);
8977

90-
services
91-
.AddLogging()
78+
services.AddLogging()
9279
.AddBuilders()
9380
.AddRepositories()
9481
.AddUnitOfWork()
@@ -100,22 +87,11 @@ public void ConfigureServices(IServiceCollection services)
10087
.AddApplicationDbContext()
10188
.AddControllers();
10289

90+
services.AddDbContextHealthChecks();
10391
services.AddApplicationGraphQL();
10492

10593
services.Configure<KestrelServerOptions>(options
10694
=> options.AllowSynchronousIO = true);
107-
108-
services.AddHealthChecks()
109-
.AddDbContextCheck<DbContext>(
110-
name: "Sql Server (Live)",
111-
failureStatus: HealthStatus.Degraded,
112-
tags: _livenessTags)
113-
.AddDbContextCheck<StoreDbContext>(
114-
name: "Sql Server (Ready)",
115-
failureStatus: HealthStatus.Unhealthy,
116-
tags: _readinessTags,
117-
customTestQuery: (dbContext, cancellationToken)
118-
=> dbContext.Products.AnyAsync(cancellationToken));
11995
}
12096
}
12197
}

0 commit comments

Comments
 (0)