-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathHealthCheckController.cs
More file actions
191 lines (168 loc) · 7.24 KB
/
HealthCheckController.cs
File metadata and controls
191 lines (168 loc) · 7.24 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
using Microsoft.ApplicationInsights;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using MX.InvisionCommunity.Api.Abstractions;
using XtremeIdiots.Portal.Repository.Abstractions.Constants.V1;
namespace XtremeIdiots.Portal.Web.ApiControllers;
[AllowAnonymous]
[Route("api/[controller]")]
public class HealthCheckController : BaseApiController
{
private readonly List<HealthCheckComponent> healthCheckComponents = [];
private readonly IInvisionApiClient forumsClient;
/// <summary>
/// Initializes a new instance of the HealthCheckController
/// </summary>
/// <param name="forumsClient">The forums API client for health checking the Invision Community integration</param>
/// <param name="telemetryClient">Application Insights telemetry client for tracking health check events</param>
/// <param name="logger">Logger instance for health check operations</param>
/// <param name="configuration">Application configuration</param>
/// <exception cref="ArgumentNullException">Thrown when forumsClient is null</exception>
public HealthCheckController(
IInvisionApiClient forumsClient,
TelemetryClient telemetryClient,
ILogger<HealthCheckController> logger,
IConfiguration configuration)
: base(telemetryClient, logger, configuration)
{
ArgumentNullException.ThrowIfNull(forumsClient);
this.forumsClient = forumsClient;
healthCheckComponents.Add(new HealthCheckComponent
{
Name = "forums-api",
Critical = true,
HealthFunc = async () =>
{
try
{
var response = await this.forumsClient.Core.GetCoreHello().ConfigureAwait(false);
var checkResponse = response?.Result?.Data?.CommunityUrl == "https://www.xtremeidiots.com/";
return new Tuple<bool, string>(checkResponse, checkResponse ? "OK" : "Unexpected or missing CommunityUrl in forums API response");
}
catch (Exception ex)
{
return User.HasClaim(claim => claim.Type == UserProfileClaimType.SeniorAdmin)
? new Tuple<bool, string>(false, ex.Message)
: new Tuple<bool, string>(false, "Failed to establish connection to the forums API");
}
}
});
}
/// <summary>
/// Gets the current health status of all system components
/// </summary>
/// <param name="cancellationToken">Cancellation token for the async operation</param>
/// <returns>Health check status for all monitored components</returns>
[HttpGet("status")]
public async Task<IActionResult> Status(CancellationToken cancellationToken = default)
{
return await ExecuteWithErrorHandlingAsync(async () =>
{
Logger.LogInformation("Health check status requested");
var result = new HealthCheckResponse();
foreach (var healthCheckComponent in healthCheckComponents)
{
if (healthCheckComponent.HealthFunc is not null)
{
var (isHealthy, additionalData) = await healthCheckComponent.HealthFunc.Invoke().ConfigureAwait(false);
result.Components.Add(new HealthCheckComponentStatus
{
Name = healthCheckComponent.Name,
Critical = healthCheckComponent.Critical,
IsHealthy = isHealthy,
AdditionalData = additionalData
});
}
else
{
result.Components.Add(new HealthCheckComponentStatus
{
Name = healthCheckComponent.Name,
Critical = healthCheckComponent.Critical,
IsHealthy = false,
AdditionalData = "Invalid health check function"
});
}
}
var actionResult = new JsonResult(result);
if (!result.IsHealthy)
{
Logger.LogWarning("Health check failed - one or more components are unhealthy");
actionResult.StatusCode = 503;
TrackSuccessTelemetry("HealthCheckFailed", "Status", new Dictionary<string, string>
{
{ "Controller", "HealthCheck" },
{ "Resource", "SystemHealth" },
{ "IsHealthy", "false" },
{ "ComponentCount", result.Components.Count.ToString() }
});
}
else
{
Logger.LogInformation("Health check completed successfully - all components are healthy");
TrackSuccessTelemetry("HealthCheckPassed", "Status", new Dictionary<string, string>
{
{ "Controller", "HealthCheck" },
{ "Resource", "SystemHealth" },
{ "IsHealthy", "true" },
{ "ComponentCount", result.Components.Count.ToString() }
});
}
return actionResult;
}, "Status").ConfigureAwait(false);
}
/// <summary>
/// Represents the overall health check response containing all component statuses
/// </summary>
public class HealthCheckResponse
{
/// <summary>
/// Gets a value indicating whether all components are healthy
/// </summary>
public bool IsHealthy => Components.All(c => c.IsHealthy);
/// <summary>
/// Gets or sets the list of component health statuses
/// </summary>
public List<HealthCheckComponentStatus> Components { get; set; } = [];
}
/// <summary>
/// Represents a health check component configuration
/// </summary>
public class HealthCheckComponent
{
/// <summary>
/// Gets or sets the name of the component being checked
/// </summary>
public string? Name { get; set; }
/// <summary>
/// Gets or sets a value indicating whether this component is critical to system operation
/// </summary>
public bool Critical { get; set; }
/// <summary>
/// Gets or sets the function that performs the health check for this component
/// </summary>
public Func<Task<Tuple<bool, string>>>? HealthFunc { get; set; }
}
/// <summary>
/// Represents the status of a single health check component
/// </summary>
public class HealthCheckComponentStatus
{
/// <summary>
/// Gets or sets the name of the component
/// </summary>
public string? Name { get; set; }
/// <summary>
/// Gets or sets a value indicating whether this component is critical to system operation
/// </summary>
public bool Critical { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the component is healthy
/// </summary>
public bool IsHealthy { get; set; }
/// <summary>
/// Gets or sets additional diagnostic data about the component status
/// </summary>
public string? AdditionalData { get; set; }
}
}