-
-
Notifications
You must be signed in to change notification settings - Fork 42
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- VotingMonitor health check - Redis health check - Windows Azure health check - Firebase health check Redis, Windows Azure and Firebase are configured to be checked only on specific conditions that depend on their configuration settings Runtime configuration changes are considered so if you enable a health check while the application is running, then this is executed otherwise the health check is tagged as not run Health Check endpoint /health is customized to include specific details for each health check The health checks are published on Application Insights
- Loading branch information
1 parent
28633c0
commit d27884a
Showing
12 changed files
with
498 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
54 changes: 54 additions & 0 deletions
54
src/api/VoteMonitor.Api/Extensions/HealthChecks/AzureBlobStorageHealthChecksExtensions.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
using Microsoft.Extensions.DependencyInjection; | ||
using Microsoft.Extensions.Diagnostics.HealthChecks; | ||
using Microsoft.Extensions.Options; | ||
using Microsoft.WindowsAzure.Storage; | ||
using Microsoft.WindowsAzure.Storage.Auth; | ||
using Microsoft.WindowsAzure.Storage.Blob; | ||
using System; | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
using VoteMonitor.Api.Core.Options; | ||
|
||
namespace VoteMonitor.Api.Extensions.HealthChecks | ||
{ | ||
public static class AzureBlobStorageHealthChecksExtensions | ||
{ | ||
public static IHealthChecksBuilder AddAzureBlobStorage(this IHealthChecksBuilder builder, string name) | ||
=> builder.Add(new HealthCheckRegistration( | ||
name, | ||
sp => new AzureBlobStorageHealthCheck(sp.GetService<IOptionsSnapshot<BlobStorageOptions>>()), null, null, null)); | ||
} | ||
|
||
public class AzureBlobStorageHealthCheck : IHealthCheck | ||
{ | ||
private IOptions<BlobStorageOptions> _storageOptions; | ||
|
||
public AzureBlobStorageHealthCheck(IOptions<BlobStorageOptions> storageOptions) | ||
{ | ||
_storageOptions = storageOptions; | ||
} | ||
|
||
public async Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default) | ||
{ | ||
try | ||
{ | ||
var credentials = new StorageCredentials(_storageOptions.Value.AccountName, _storageOptions.Value.AccountKey); | ||
var blobClient = new CloudStorageAccount(credentials, _storageOptions.Value.UseHttps).CreateCloudBlobClient(); | ||
|
||
var serviceProperties = await blobClient.GetServicePropertiesAsync( | ||
new BlobRequestOptions(), | ||
operationContext: null, | ||
cancellationToken: cancellationToken); | ||
|
||
var container = blobClient.GetContainerReference(_storageOptions.Value.Container); | ||
await container.FetchAttributesAsync(); | ||
|
||
return HealthCheckResult.Healthy(); | ||
} | ||
catch (Exception ex) | ||
{ | ||
return new HealthCheckResult(context.Registration.FailureStatus, exception: ex); | ||
} | ||
} | ||
} | ||
} |
71 changes: 71 additions & 0 deletions
71
src/api/VoteMonitor.Api/Extensions/HealthChecks/ConditionalHealthCheck.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
using Microsoft.Extensions.DependencyInjection; | ||
using Microsoft.Extensions.Diagnostics.HealthChecks; | ||
using Microsoft.Extensions.Logging; | ||
using System; | ||
using System.Linq; | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
|
||
namespace VoteMonitor.Api.Extensions.HealthChecks | ||
{ | ||
public static class ConditionalHealthChecksExtensions | ||
{ | ||
public static IHealthChecksBuilder CheckOnlyWhen(this IHealthChecksBuilder builder, string name, Func<bool> predicate) | ||
{ | ||
builder.Services.Configure<HealthCheckServiceOptions>(options => | ||
{ | ||
var registration = options.Registrations.FirstOrDefault(c => c.Name == name); | ||
|
||
if (registration == null) | ||
{ | ||
throw new InvalidOperationException($"A health check registration named `{name}` is not found in the health registrations list, so its conditional check cannot be configured. The registration must be added before configuring the conditional predicate."); | ||
} | ||
|
||
var factory = registration.Factory; | ||
registration.Factory = sp => new ConditionalHealthCheck( | ||
() => factory(sp), | ||
predicate, | ||
sp.GetService<ILogger<ConditionalHealthCheck>>() | ||
); | ||
}); | ||
|
||
return builder; | ||
} | ||
} | ||
|
||
public class ConditionalHealthCheck : IHealthCheck | ||
{ | ||
private const string NotChecked = "NotChecked"; | ||
private readonly Func<bool> _predicate; | ||
private readonly ILogger<ConditionalHealthCheck> _logger; | ||
|
||
public ConditionalHealthCheck(Func<IHealthCheck> healthCheckFactory, | ||
Func<bool> predicate, | ||
ILogger<ConditionalHealthCheck> logger) | ||
{ | ||
HealthCheckFactory = healthCheckFactory; | ||
_predicate = predicate; | ||
_logger = logger; | ||
} | ||
|
||
public async Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default) | ||
{ | ||
context.Registration.Tags.Remove(NotChecked); | ||
|
||
if (!_predicate()) | ||
{ | ||
_logger.LogDebug("Healthcheck `{0}` will not be executed as its checking condition is not met.", context.Registration.Name); | ||
|
||
context.Registration.Tags.Add(NotChecked); | ||
|
||
return new HealthCheckResult(HealthStatus.Healthy, $"Health check on `{context.Registration.Name}` will not be evaluated " + | ||
$"as its checking condition is not met. This does not mean your dependency is healthy, " + | ||
$"but the health check operation on this dependency is not configured to be executed yet."); | ||
} | ||
|
||
return await HealthCheckFactory().CheckHealthAsync(context, cancellationToken); | ||
} | ||
|
||
internal Func<IHealthCheck> HealthCheckFactory { get; set; } | ||
} | ||
} |
44 changes: 44 additions & 0 deletions
44
src/api/VoteMonitor.Api/Extensions/HealthChecks/FirebaseHealthChecksExtensions.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
using FirebaseAdmin; | ||
using FirebaseAdmin.Auth; | ||
using Google.Apis.Auth.OAuth2; | ||
using Microsoft.Extensions.DependencyInjection; | ||
using Microsoft.Extensions.Diagnostics.HealthChecks; | ||
using System; | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
|
||
namespace VoteMonitor.Api.Extensions.HealthChecks | ||
{ | ||
public static class FirebaseHealthChecksExtensions | ||
{ | ||
public static IHealthChecksBuilder AddFirebase(this IHealthChecksBuilder builder, string name) | ||
=> builder.Add(new HealthCheckRegistration( | ||
name, | ||
sp => new FirebaseHealthCheck(), null, null, null)); | ||
} | ||
|
||
public class FirebaseHealthCheck : IHealthCheck | ||
{ | ||
public Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default) | ||
{ | ||
try | ||
{ | ||
if (FirebaseApp.DefaultInstance == null) | ||
{ | ||
FirebaseApp.Create(new AppOptions() | ||
{ | ||
Credential = GoogleCredential.GetApplicationDefault(), | ||
}); | ||
} | ||
|
||
var defaultAuth = FirebaseAuth.DefaultInstance; | ||
|
||
return Task.FromResult(HealthCheckResult.Healthy()); | ||
} | ||
catch (Exception ex) | ||
{ | ||
return Task.FromResult(new HealthCheckResult(context.Registration.FailureStatus, exception: ex)); | ||
} | ||
} | ||
} | ||
} |
66 changes: 66 additions & 0 deletions
66
src/api/VoteMonitor.Api/Extensions/HealthChecksConfiguration.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
using Microsoft.AspNetCore.Hosting; | ||
using Microsoft.AspNetCore.Http; | ||
using Microsoft.Extensions.Diagnostics.HealthChecks; | ||
using Microsoft.Extensions.Hosting; | ||
using System; | ||
using System.IO; | ||
using System.Linq; | ||
using System.Text; | ||
using System.Text.Json; | ||
using System.Threading.Tasks; | ||
|
||
namespace VoteMonitor.Api.Extensions | ||
{ | ||
public static class HealthChecksConfiguration | ||
{ | ||
public static async Task WriteResponse(HttpContext context, HealthReport result, IWebHostEnvironment env) | ||
{ | ||
context.Response.ContentType = "application/json; charset=utf-8"; | ||
|
||
var options = new JsonSerializerOptions | ||
{ | ||
IgnoreNullValues = true | ||
}; | ||
|
||
using var stream = new MemoryStream(); | ||
|
||
var healthResponse = new | ||
{ | ||
status = result.Status.ToString(), | ||
totalDuration = result.TotalDuration.ToString(), | ||
entries = result.Entries.Select(e => new | ||
{ | ||
name = e.Key, | ||
status = e.Value.Status.ToString(), | ||
tags = e.Value.Tags, | ||
description = e.Value.Description, | ||
data = env.IsDevelopment() && e.Value.Data?.Count > 0 ? e.Value.Data : null, | ||
exception = env.IsDevelopment() ? ExtractSerializableExceptionData(e.Value.Exception) : null | ||
}).ToList() | ||
}; | ||
|
||
await JsonSerializer.SerializeAsync(stream, healthResponse, healthResponse.GetType(), options); | ||
var json = Encoding.UTF8.GetString(stream.ToArray()); | ||
|
||
await context.Response.WriteAsync(json); | ||
|
||
static object ExtractSerializableExceptionData(Exception exception) | ||
{ | ||
if (exception == null) | ||
{ | ||
return exception; | ||
} | ||
|
||
return new | ||
{ | ||
type = exception.GetType().ToString(), | ||
message = exception.Message, | ||
stackTrace = exception.StackTrace, | ||
source = exception.Source, | ||
data = exception.Data?.Count > 0 ? exception.Data : null, | ||
innerException = exception.InnerException != null ? ExtractSerializableExceptionData(exception.InnerException) : null | ||
}; | ||
}; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.