Merge pull request #473 from dotnet-maestro-bot/merge/release/2.2-to-master
[automated] Merge branch 'release/2.2' => 'master'
This commit is contained in:
commit
0bb4e948f7
|
|
@ -31,6 +31,7 @@
|
||||||
<MicrosoftExtensionsRazorViewsSourcesPackageVersion>3.0.0-alpha1-10352</MicrosoftExtensionsRazorViewsSourcesPackageVersion>
|
<MicrosoftExtensionsRazorViewsSourcesPackageVersion>3.0.0-alpha1-10352</MicrosoftExtensionsRazorViewsSourcesPackageVersion>
|
||||||
<MicrosoftExtensionsStackTraceSourcesPackageVersion>3.0.0-alpha1-10352</MicrosoftExtensionsStackTraceSourcesPackageVersion>
|
<MicrosoftExtensionsStackTraceSourcesPackageVersion>3.0.0-alpha1-10352</MicrosoftExtensionsStackTraceSourcesPackageVersion>
|
||||||
<MicrosoftExtensionsTypeNameHelperSourcesPackageVersion>3.0.0-alpha1-10352</MicrosoftExtensionsTypeNameHelperSourcesPackageVersion>
|
<MicrosoftExtensionsTypeNameHelperSourcesPackageVersion>3.0.0-alpha1-10352</MicrosoftExtensionsTypeNameHelperSourcesPackageVersion>
|
||||||
|
<MicrosoftExtensionsValueStopwatchSourcesPackageVersion>3.0.0-alpha1-10352</MicrosoftExtensionsValueStopwatchSourcesPackageVersion>
|
||||||
<MicrosoftNETCoreApp20PackageVersion>2.0.9</MicrosoftNETCoreApp20PackageVersion>
|
<MicrosoftNETCoreApp20PackageVersion>2.0.9</MicrosoftNETCoreApp20PackageVersion>
|
||||||
<MicrosoftNETCoreApp21PackageVersion>2.1.2</MicrosoftNETCoreApp21PackageVersion>
|
<MicrosoftNETCoreApp21PackageVersion>2.1.2</MicrosoftNETCoreApp21PackageVersion>
|
||||||
<MicrosoftNETCoreApp22PackageVersion>2.2.0-preview1-26618-02</MicrosoftNETCoreApp22PackageVersion>
|
<MicrosoftNETCoreApp22PackageVersion>2.2.0-preview1-26618-02</MicrosoftNETCoreApp22PackageVersion>
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,6 @@ namespace HealthChecksSample
|
||||||
{
|
{
|
||||||
// Registers required services for health checks
|
// Registers required services for health checks
|
||||||
services.AddHealthChecks()
|
services.AddHealthChecks()
|
||||||
|
|
||||||
// Add a health check for a SQL database
|
// Add a health check for a SQL database
|
||||||
.AddCheck(new SqlConnectionHealthCheck("MyDatabase", Configuration["ConnectionStrings:DefaultConnection"]));
|
.AddCheck(new SqlConnectionHealthCheck("MyDatabase", Configuration["ConnectionStrings:DefaultConnection"]));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,6 @@ namespace HealthChecksSample
|
||||||
// Registers required services for health checks
|
// Registers required services for health checks
|
||||||
services
|
services
|
||||||
.AddHealthChecks()
|
.AddHealthChecks()
|
||||||
.AddCheck("identity", () => Task.FromResult(HealthCheckResult.Healthy()))
|
|
||||||
.AddCheck(new SlowDependencyHealthCheck());
|
.AddCheck(new SlowDependencyHealthCheck());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -53,11 +52,8 @@ namespace HealthChecksSample
|
||||||
// The liveness check uses an 'identity' health check that always returns healthy
|
// The liveness check uses an 'identity' health check that always returns healthy
|
||||||
app.UseHealthChecks("/health/live", new HealthCheckOptions()
|
app.UseHealthChecks("/health/live", new HealthCheckOptions()
|
||||||
{
|
{
|
||||||
// Filters the set of health checks run by this middleware
|
// Exclude all checks, just return a 200.
|
||||||
HealthCheckNames =
|
Predicate = (check) => false,
|
||||||
{
|
|
||||||
"identity",
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
app.Run(async (context) =>
|
app.Run(async (context) =>
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ namespace HealthChecksSample
|
||||||
{
|
{
|
||||||
_scenarios = new Dictionary<string, Type>(StringComparer.OrdinalIgnoreCase)
|
_scenarios = new Dictionary<string, Type>(StringComparer.OrdinalIgnoreCase)
|
||||||
{
|
{
|
||||||
{ "", typeof(BasicStartup) },
|
{ "", typeof(DBHealthStartup) },
|
||||||
{ "basic", typeof(BasicStartup) },
|
{ "basic", typeof(BasicStartup) },
|
||||||
{ "writer", typeof(CustomWriterStartup) },
|
{ "writer", typeof(CustomWriterStartup) },
|
||||||
{ "liveness", typeof(LivenessProbeStartup) },
|
{ "liveness", typeof(LivenessProbeStartup) },
|
||||||
|
|
@ -48,6 +48,8 @@ namespace HealthChecksSample
|
||||||
.UseConfiguration(config)
|
.UseConfiguration(config)
|
||||||
.ConfigureLogging(builder =>
|
.ConfigureLogging(builder =>
|
||||||
{
|
{
|
||||||
|
builder.SetMinimumLevel(LogLevel.Trace);
|
||||||
|
builder.AddConfiguration(config);
|
||||||
builder.AddConsole();
|
builder.AddConsole();
|
||||||
})
|
})
|
||||||
.UseKestrel()
|
.UseKestrel()
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,13 @@
|
||||||
{
|
{
|
||||||
"ConnectionStrings": {
|
"ConnectionStrings": {
|
||||||
"DefaultConnection": "Server=(localdb)\\MSSQLLocalDB;Database=HealthCheckSample;Trusted_Connection=True;MultipleActiveResultSets=true;ConnectRetryCount=0"
|
"DefaultConnection": "Server=(localdb)\\MSSQLLocalDB;Database=HealthCheckSample;Trusted_Connection=True;MultipleActiveResultSets=true;ConnectRetryCount=0"
|
||||||
|
},
|
||||||
|
"Logging": {
|
||||||
|
"LogLevel": {
|
||||||
|
"Default": "Debug"
|
||||||
|
},
|
||||||
|
"Console": {
|
||||||
|
"IncludeScopes": "true"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -16,7 +16,6 @@ namespace Microsoft.AspNetCore.Diagnostics.HealthChecks
|
||||||
private readonly RequestDelegate _next;
|
private readonly RequestDelegate _next;
|
||||||
private readonly HealthCheckOptions _healthCheckOptions;
|
private readonly HealthCheckOptions _healthCheckOptions;
|
||||||
private readonly IHealthCheckService _healthCheckService;
|
private readonly IHealthCheckService _healthCheckService;
|
||||||
private readonly IHealthCheck[] _checks;
|
|
||||||
|
|
||||||
public HealthCheckMiddleware(
|
public HealthCheckMiddleware(
|
||||||
RequestDelegate next,
|
RequestDelegate next,
|
||||||
|
|
@ -41,8 +40,6 @@ namespace Microsoft.AspNetCore.Diagnostics.HealthChecks
|
||||||
_next = next;
|
_next = next;
|
||||||
_healthCheckOptions = healthCheckOptions.Value;
|
_healthCheckOptions = healthCheckOptions.Value;
|
||||||
_healthCheckService = healthCheckService;
|
_healthCheckService = healthCheckService;
|
||||||
|
|
||||||
_checks = FilterHealthChecks(_healthCheckService.Checks, healthCheckOptions.Value.HealthCheckNames);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -58,7 +55,7 @@ namespace Microsoft.AspNetCore.Diagnostics.HealthChecks
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get results
|
// Get results
|
||||||
var result = await _healthCheckService.CheckHealthAsync(_checks, httpContext.RequestAborted);
|
var result = await _healthCheckService.CheckHealthAsync(_healthCheckOptions.Predicate, httpContext.RequestAborted);
|
||||||
|
|
||||||
// Map status to response code - this is customizable via options.
|
// Map status to response code - this is customizable via options.
|
||||||
if (!_healthCheckOptions.ResultStatusCodes.TryGetValue(result.Status, out var statusCode))
|
if (!_healthCheckOptions.ResultStatusCodes.TryGetValue(result.Status, out var statusCode))
|
||||||
|
|
|
||||||
|
|
@ -15,15 +15,19 @@ namespace Microsoft.AspNetCore.Diagnostics.HealthChecks
|
||||||
public class HealthCheckOptions
|
public class HealthCheckOptions
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a set of health check names used to filter the set of health checks run.
|
/// Gets or sets a predicate that is used to filter the set of health checks executed.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// If <see cref="HealthCheckNames"/> is empty, the <see cref="HealthCheckMiddleware"/> will run all
|
/// If <see cref="Predicate"/> is <c>null</c>, the <see cref="HealthCheckMiddleware"/> will run all
|
||||||
/// registered health checks - this is the default behavior. To run a subset of health checks,
|
/// registered health checks - this is the default behavior. To run a subset of health checks,
|
||||||
/// add the names of the desired health checks.
|
/// provide a function that filters the set of checks.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public ISet<string> HealthCheckNames { get; } = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
public Func<IHealthCheck, bool> Predicate { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a dictionary mapping the <see cref="HealthCheckStatus"/> to an HTTP status code applied to the response.
|
||||||
|
/// This property can be used to configure the status codes returned for each status.
|
||||||
|
/// </summary>
|
||||||
public IDictionary<HealthCheckStatus, int> ResultStatusCodes { get; } = new Dictionary<HealthCheckStatus, int>()
|
public IDictionary<HealthCheckStatus, int> ResultStatusCodes { get; } = new Dictionary<HealthCheckStatus, int>()
|
||||||
{
|
{
|
||||||
{ HealthCheckStatus.Healthy, StatusCodes.Status200OK },
|
{ HealthCheckStatus.Healthy, StatusCodes.Status200OK },
|
||||||
|
|
|
||||||
|
|
@ -6,127 +6,139 @@ using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Internal;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.Extensions.Logging.Abstractions;
|
|
||||||
|
|
||||||
namespace Microsoft.Extensions.Diagnostics.HealthChecks
|
namespace Microsoft.Extensions.Diagnostics.HealthChecks
|
||||||
{
|
{
|
||||||
/// <summary>
|
internal class HealthCheckService : IHealthCheckService
|
||||||
/// Default implementation of <see cref="IHealthCheckService"/>.
|
|
||||||
/// </summary>
|
|
||||||
public class HealthCheckService : IHealthCheckService
|
|
||||||
{
|
{
|
||||||
|
private readonly IServiceScopeFactory _scopeFactory;
|
||||||
private readonly ILogger<HealthCheckService> _logger;
|
private readonly ILogger<HealthCheckService> _logger;
|
||||||
|
|
||||||
/// <summary>
|
public HealthCheckService(IServiceScopeFactory scopeFactory, ILogger<HealthCheckService> logger)
|
||||||
/// A <see cref="IReadOnlyDictionary{TKey, T}"/> containing all the health checks registered in the application.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// The key maps to the <see cref="IHealthCheck.Name"/> property of the health check, and the value is the <see cref="IHealthCheck"/>
|
|
||||||
/// instance itself.
|
|
||||||
/// </remarks>
|
|
||||||
public IReadOnlyDictionary<string, IHealthCheck> Checks { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Constructs a <see cref="HealthCheckService"/> from the provided collection of <see cref="IHealthCheck"/> instances.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="healthChecks">The <see cref="IHealthCheck"/> instances that have been registered in the application.</param>
|
|
||||||
public HealthCheckService(IEnumerable<IHealthCheck> healthChecks) : this(healthChecks, NullLogger<HealthCheckService>.Instance) { }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Constructs a <see cref="HealthCheckService"/> from the provided collection of <see cref="IHealthCheck"/> instances, and the provided logger.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="healthChecks">The <see cref="IHealthCheck"/> instances that have been registered in the application.</param>
|
|
||||||
/// <param name="logger">A <see cref="ILogger{T}"/> that can be used to log events that occur during health check operations.</param>
|
|
||||||
public HealthCheckService(IEnumerable<IHealthCheck> healthChecks, ILogger<HealthCheckService> logger)
|
|
||||||
{
|
{
|
||||||
healthChecks = healthChecks ?? throw new ArgumentNullException(nameof(healthChecks));
|
_scopeFactory = scopeFactory ?? throw new ArgumentNullException(nameof(scopeFactory));
|
||||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||||
|
|
||||||
// Scan the list for duplicate names to provide a better error if there are duplicates.
|
// We're specifically going out of our way to do this at startup time. We want to make sure you
|
||||||
var names = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
// get any kind of health-check related error as early as possible. Waiting until someone
|
||||||
var duplicates = new List<string>();
|
// actually tries to **run** health checks would be real baaaaad.
|
||||||
foreach (var check in healthChecks)
|
using (var scope = _scopeFactory.CreateScope())
|
||||||
{
|
{
|
||||||
if (!names.Add(check.Name))
|
var healthChecks = scope.ServiceProvider.GetRequiredService<IEnumerable<IHealthCheck>>();
|
||||||
{
|
EnsureNoDuplicates(healthChecks);
|
||||||
duplicates.Add(check.Name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (duplicates.Count > 0)
|
|
||||||
{
|
|
||||||
throw new ArgumentException($"Duplicate health checks were registered with the name(s): {string.Join(", ", duplicates)}", nameof(healthChecks));
|
|
||||||
}
|
|
||||||
|
|
||||||
Checks = healthChecks.ToDictionary(c => c.Name, StringComparer.OrdinalIgnoreCase);
|
|
||||||
|
|
||||||
if (_logger.IsEnabled(LogLevel.Debug))
|
|
||||||
{
|
|
||||||
foreach (var check in Checks)
|
|
||||||
{
|
|
||||||
_logger.LogDebug("Health check '{healthCheckName}' has been registered", check.Key);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Runs all the health checks in the application and returns the aggregated status.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="cancellationToken">A <see cref="CancellationToken"/> which can be used to cancel the health checks.</param>
|
|
||||||
/// <returns>
|
|
||||||
/// A <see cref="Task{T}"/> which will complete when all the health checks have been run,
|
|
||||||
/// yielding a <see cref="CompositeHealthCheckResult"/> containing the results.
|
|
||||||
/// </returns>
|
|
||||||
public Task<CompositeHealthCheckResult> CheckHealthAsync(CancellationToken cancellationToken = default) =>
|
public Task<CompositeHealthCheckResult> CheckHealthAsync(CancellationToken cancellationToken = default) =>
|
||||||
CheckHealthAsync(Checks.Values, cancellationToken);
|
CheckHealthAsync(predicate: null, cancellationToken);
|
||||||
|
|
||||||
/// <summary>
|
public async Task<CompositeHealthCheckResult> CheckHealthAsync(
|
||||||
/// Runs the provided health checks and returns the aggregated status
|
Func<IHealthCheck, bool> predicate,
|
||||||
/// </summary>
|
CancellationToken cancellationToken = default)
|
||||||
/// <param name="checks">The <see cref="IHealthCheck"/> instances to be run.</param>
|
|
||||||
/// <param name="cancellationToken">A <see cref="CancellationToken"/> which can be used to cancel the health checks.</param>
|
|
||||||
/// <returns>
|
|
||||||
/// A <see cref="Task{T}"/> which will complete when all the health checks have been run,
|
|
||||||
/// yielding a <see cref="CompositeHealthCheckResult"/> containing the results.
|
|
||||||
/// </returns>
|
|
||||||
public async Task<CompositeHealthCheckResult> CheckHealthAsync(IEnumerable<IHealthCheck> checks, CancellationToken cancellationToken = default)
|
|
||||||
{
|
{
|
||||||
var results = new Dictionary<string, HealthCheckResult>(Checks.Count, StringComparer.OrdinalIgnoreCase);
|
using (var scope = _scopeFactory.CreateScope())
|
||||||
foreach (var check in checks)
|
|
||||||
{
|
{
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
var healthChecks = scope.ServiceProvider.GetRequiredService<IEnumerable<IHealthCheck>>();
|
||||||
// If the health check does things like make Database queries using EF or backend HTTP calls,
|
|
||||||
// it may be valuable to know that logs it generates are part of a health check. So we start a scope.
|
var results = new Dictionary<string, HealthCheckResult>(StringComparer.OrdinalIgnoreCase);
|
||||||
using (_logger.BeginScope(new HealthCheckLogScope(check.Name)))
|
foreach (var healthCheck in healthChecks)
|
||||||
{
|
{
|
||||||
HealthCheckResult result;
|
if (predicate != null && !predicate(healthCheck))
|
||||||
try
|
|
||||||
{
|
{
|
||||||
_logger.LogTrace("Running health check: {healthCheckName}", check.Name);
|
continue;
|
||||||
result = await check.CheckHealthAsync(cancellationToken);
|
|
||||||
_logger.LogTrace("Health check '{healthCheckName}' completed with status '{healthCheckStatus}'", check.Name, result.Status);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
// We don't log this as an error because a health check failing shouldn't bring down the active task.
|
|
||||||
_logger.LogError(ex, "Health check '{healthCheckName}' threw an unexpected exception", check.Name);
|
|
||||||
result = new HealthCheckResult(HealthCheckStatus.Failed, ex, ex.Message, data: null);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// This can only happen if the result is default(HealthCheckResult)
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
if (result.Status == HealthCheckStatus.Unknown)
|
|
||||||
{
|
|
||||||
// This is different from the case above. We throw here because a health check is doing something specifically incorrect.
|
|
||||||
var exception = new InvalidOperationException($"Health check '{check.Name}' returned a result with a status of Unknown");
|
|
||||||
_logger.LogError(exception, "Health check '{healthCheckName}' returned a result with a status of Unknown", check.Name);
|
|
||||||
throw exception;
|
|
||||||
}
|
|
||||||
|
|
||||||
results[check.Name] = result;
|
// If the health check does things like make Database queries using EF or backend HTTP calls,
|
||||||
|
// it may be valuable to know that logs it generates are part of a health check. So we start a scope.
|
||||||
|
using (_logger.BeginScope(new HealthCheckLogScope(healthCheck.Name)))
|
||||||
|
{
|
||||||
|
HealthCheckResult result;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Log.HealthCheckBegin(_logger, healthCheck);
|
||||||
|
var stopwatch = ValueStopwatch.StartNew();
|
||||||
|
result = await healthCheck.CheckHealthAsync(cancellationToken);
|
||||||
|
Log.HealthCheckEnd(_logger, healthCheck, result, stopwatch.GetElapsedTime());
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log.HealthCheckError(_logger, healthCheck, ex);
|
||||||
|
result = new HealthCheckResult(HealthCheckStatus.Failed, ex, ex.Message, data: null);
|
||||||
|
}
|
||||||
|
|
||||||
|
// This can only happen if the result is default(HealthCheckResult)
|
||||||
|
if (result.Status == HealthCheckStatus.Unknown)
|
||||||
|
{
|
||||||
|
// This is different from the case above. We throw here because a health check is doing something specifically incorrect.
|
||||||
|
throw new InvalidOperationException($"Health check '{healthCheck.Name}' returned a result with a status of Unknown");
|
||||||
|
}
|
||||||
|
|
||||||
|
results[healthCheck.Name] = result;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return new CompositeHealthCheckResult(results);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void EnsureNoDuplicates(IEnumerable<IHealthCheck> healthChecks)
|
||||||
|
{
|
||||||
|
// Scan the list for duplicate names to provide a better error if there are duplicates.
|
||||||
|
var duplicateNames = healthChecks
|
||||||
|
.GroupBy(c => c.Name, StringComparer.OrdinalIgnoreCase)
|
||||||
|
.Where(g => g.Count() > 1)
|
||||||
|
.Select(g => g.Key)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
if (duplicateNames.Count > 0)
|
||||||
|
{
|
||||||
|
throw new ArgumentException($"Duplicate health checks were registered with the name(s): {string.Join(", ", duplicateNames)}", nameof(healthChecks));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class Log
|
||||||
|
{
|
||||||
|
public static class EventIds
|
||||||
|
{
|
||||||
|
public static readonly EventId HealthCheckBegin = new EventId(100, "HealthCheckBegin");
|
||||||
|
public static readonly EventId HealthCheckEnd = new EventId(101, "HealthCheckEnd");
|
||||||
|
public static readonly EventId HealthCheckError = new EventId(102, "HealthCheckError");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static readonly Action<ILogger, string, Exception> _healthCheckBegin = LoggerMessage.Define<string>(
|
||||||
|
LogLevel.Debug,
|
||||||
|
EventIds.HealthCheckBegin,
|
||||||
|
"Running health check {HealthCheckName}");
|
||||||
|
|
||||||
|
private static readonly Action<ILogger, string, double, HealthCheckStatus, Exception> _healthCheckEnd = LoggerMessage.Define<string, double, HealthCheckStatus>(
|
||||||
|
LogLevel.Debug,
|
||||||
|
EventIds.HealthCheckEnd,
|
||||||
|
"Health check {HealthCheckName} completed after {ElapsedMilliseconds}ms with status {HealthCheckStatus}");
|
||||||
|
|
||||||
|
private static readonly Action<ILogger, string, Exception> _healthCheckError = LoggerMessage.Define<string>(
|
||||||
|
LogLevel.Error,
|
||||||
|
EventIds.HealthCheckError,
|
||||||
|
"Health check {HealthCheckName} threw an unhandled exception");
|
||||||
|
|
||||||
|
public static void HealthCheckBegin(ILogger logger, IHealthCheck healthCheck)
|
||||||
|
{
|
||||||
|
_healthCheckBegin(logger, healthCheck.Name, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void HealthCheckEnd(ILogger logger, IHealthCheck healthCheck, HealthCheckResult result, TimeSpan duration)
|
||||||
|
{
|
||||||
|
_healthCheckEnd(logger, healthCheck.Name, duration.TotalMilliseconds, result.Status, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void HealthCheckError(ILogger logger, IHealthCheck healthCheck, Exception exception)
|
||||||
|
{
|
||||||
|
_healthCheckError(logger, healthCheck.Name, exception);
|
||||||
}
|
}
|
||||||
return new CompositeHealthCheckResult(results);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -68,7 +68,7 @@ namespace Microsoft.Extensions.DependencyInjection
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Adds a new health check with the implementation.
|
/// Adds a new health check with the provided implementation.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="builder">The <see cref="IHealthChecksBuilder"/> to add the check to.</param>
|
/// <param name="builder">The <see cref="IHealthChecksBuilder"/> to add the check to.</param>
|
||||||
/// <param name="check">An <see cref="IHealthCheck"/> implementation.</param>
|
/// <param name="check">An <see cref="IHealthCheck"/> implementation.</param>
|
||||||
|
|
@ -88,5 +88,27 @@ namespace Microsoft.Extensions.DependencyInjection
|
||||||
builder.Services.AddSingleton<IHealthCheck>(check);
|
builder.Services.AddSingleton<IHealthCheck>(check);
|
||||||
return builder;
|
return builder;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a new health check as a transient dependency injected service with the provided type.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The health check implementation type.</typeparam>
|
||||||
|
/// <param name="builder">The <see cref="IHealthChecksBuilder"/>.</param>
|
||||||
|
/// <returns>The <see cref="IHealthChecksBuilder"/>.</returns>
|
||||||
|
/// <remarks>
|
||||||
|
/// This method will register a transient service of type <see cref="IHealthCheck"/> with the
|
||||||
|
/// provided implementation type <typeparamref name="T"/>. Using this method to register a health
|
||||||
|
/// check allows you to register a health check that depends on transient and scoped services.
|
||||||
|
/// </remarks>
|
||||||
|
public static IHealthChecksBuilder AddCheck<T>(this IHealthChecksBuilder builder) where T : class, IHealthCheck
|
||||||
|
{
|
||||||
|
if (builder == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(builder));
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.Services.Add(ServiceDescriptor.Transient(typeof(IHealthCheck), typeof(T)));
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,26 +1,38 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Microsoft.Extensions.Diagnostics.HealthChecks
|
namespace Microsoft.Extensions.Diagnostics.HealthChecks
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A service which can be used to check the status of <see cref="IHealthCheck"/> instances registered in the application.
|
/// A service which can be used to check the status of <see cref="IHealthCheck"/> instances
|
||||||
|
/// registered in the application.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// <para>
|
||||||
|
/// The default implementation of <see cref="IHealthCheckService"/> is registered in the dependency
|
||||||
|
/// injection container as a singleton service by calling
|
||||||
|
/// <see cref="DependencyInjection.HealthCheckServiceCollectionExtensions.AddHealthChecks(DependencyInjection.IServiceCollection)"/>.
|
||||||
|
/// </para>
|
||||||
|
/// <para>
|
||||||
|
/// The <see cref="IHealthChecksBuilder"/> returned by
|
||||||
|
/// <see cref="DependencyInjection.HealthCheckServiceCollectionExtensions.AddHealthChecks(DependencyInjection.IServiceCollection)"/>
|
||||||
|
/// provides a convenience API for registering health checks.
|
||||||
|
/// </para>
|
||||||
|
/// <para>
|
||||||
|
/// The default implementation of <see cref="IHealthCheckService"/> will use all services
|
||||||
|
/// of type <see cref="IHealthCheck"/> registered in the dependency injection container. <see cref="IHealthCheck"/>
|
||||||
|
/// implementations may be registered with any service lifetime. The implementation will create a scope
|
||||||
|
/// for each aggregate health check operation and use the scope to resolve services. The scope
|
||||||
|
/// created for executing health checks is controlled by the health checks service and does not
|
||||||
|
/// share scoped services with any other scope in the application.
|
||||||
|
/// </para>
|
||||||
|
/// </remarks>
|
||||||
public interface IHealthCheckService
|
public interface IHealthCheckService
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// A <see cref="IReadOnlyDictionary{TKey, T}"/> containing all the health checks registered in the application.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// The key maps to the <see cref="IHealthCheck.Name"/> property of the health check, and the value is the <see cref="IHealthCheck"/>
|
|
||||||
/// instance itself.
|
|
||||||
/// </remarks>
|
|
||||||
IReadOnlyDictionary<string, IHealthCheck> Checks { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Runs all the health checks in the application and returns the aggregated status.
|
/// Runs all the health checks in the application and returns the aggregated status.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -34,13 +46,15 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Runs the provided health checks and returns the aggregated status
|
/// Runs the provided health checks and returns the aggregated status
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="checks">The <see cref="IHealthCheck"/> instances to be run.</param>
|
/// <param name="predicate">
|
||||||
|
/// A predicate that can be used to include health checks based on user-defined criteria.
|
||||||
|
/// </param>
|
||||||
/// <param name="cancellationToken">A <see cref="CancellationToken"/> which can be used to cancel the health checks.</param>
|
/// <param name="cancellationToken">A <see cref="CancellationToken"/> which can be used to cancel the health checks.</param>
|
||||||
/// <returns>
|
/// <returns>
|
||||||
/// A <see cref="Task{T}"/> which will complete when all the health checks have been run,
|
/// A <see cref="Task{T}"/> which will complete when all the health checks have been run,
|
||||||
/// yielding a <see cref="CompositeHealthCheckResult"/> containing the results.
|
/// yielding a <see cref="CompositeHealthCheckResult"/> containing the results.
|
||||||
/// </returns>
|
/// </returns>
|
||||||
Task<CompositeHealthCheckResult> CheckHealthAsync(IEnumerable<IHealthCheck> checks,
|
Task<CompositeHealthCheckResult> CheckHealthAsync(Func<IHealthCheck, bool> predicate,
|
||||||
CancellationToken cancellationToken = default);
|
CancellationToken cancellationToken = default);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ Microsoft.Extensions.Diagnostics.HealthChecks.IHealthChecksBuilder
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="$(MicrosoftExtensionsDependencyInjectionAbstractionsPackageVersion)" />
|
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="$(MicrosoftExtensionsDependencyInjectionAbstractionsPackageVersion)" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="$(MicrosoftExtensionsLoggingAbstractionsPackageVersion)" />
|
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="$(MicrosoftExtensionsLoggingAbstractionsPackageVersion)" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.ValueStopwatch.Sources" Version="$(MicrosoftExtensionsValueStopwatchSourcesPackageVersion)" PrivateAssets="All" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions\Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions.csproj" />
|
<ProjectReference Include="..\Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions\Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions.csproj" />
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
[assembly: InternalsVisibleTo("Microsoft.Extensions.Diagnostics.HealthChecks.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
|
||||||
|
|
@ -302,11 +302,7 @@ namespace Microsoft.AspNetCore.Diagnostics.HealthChecks
|
||||||
{
|
{
|
||||||
app.UseHealthChecks("/health", new HealthCheckOptions()
|
app.UseHealthChecks("/health", new HealthCheckOptions()
|
||||||
{
|
{
|
||||||
HealthCheckNames =
|
Predicate = (check) => check.Name == "Foo" || check.Name == "Baz",
|
||||||
{
|
|
||||||
"Baz",
|
|
||||||
"FOO",
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.ConfigureServices(services =>
|
.ConfigureServices(services =>
|
||||||
|
|
@ -327,35 +323,6 @@ namespace Microsoft.AspNetCore.Diagnostics.HealthChecks
|
||||||
Assert.Equal("Healthy", await response.Content.ReadAsStringAsync());
|
Assert.Equal("Healthy", await response.Content.ReadAsStringAsync());
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void CanFilterChecks_ThrowsForMissingCheck()
|
|
||||||
{
|
|
||||||
var builder = new WebHostBuilder()
|
|
||||||
.Configure(app =>
|
|
||||||
{
|
|
||||||
app.UseHealthChecks("/health", new HealthCheckOptions()
|
|
||||||
{
|
|
||||||
HealthCheckNames =
|
|
||||||
{
|
|
||||||
"Bazzzzzz",
|
|
||||||
"FOO",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.ConfigureServices(services =>
|
|
||||||
{
|
|
||||||
services.AddHealthChecks()
|
|
||||||
.AddCheck("Foo", () => Task.FromResult(HealthCheckResult.Healthy("A-ok!")))
|
|
||||||
.AddCheck("Bar", () => Task.FromResult(HealthCheckResult.Unhealthy("A-ok!")))
|
|
||||||
.AddCheck("Baz", () => Task.FromResult(HealthCheckResult.Healthy("A-ok!")));
|
|
||||||
});
|
|
||||||
|
|
||||||
var ex = Assert.Throws<InvalidOperationException>(() => new TestServer(builder));
|
|
||||||
Assert.Equal(
|
|
||||||
"The following health checks were not found: 'Bazzzzzz'. Registered health checks: 'Foo, Bar, Baz'.",
|
|
||||||
ex.Message);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task CanListenOnPort_AcceptsRequest_OnSpecifiedPort()
|
public async Task CanListenOnPort_AcceptsRequest_OnSpecifiedPort()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,9 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.Extensions.Logging.Testing;
|
using Microsoft.Extensions.Logging.Testing;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
@ -12,40 +14,30 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks
|
||||||
{
|
{
|
||||||
public class HealthCheckServiceTests
|
public class HealthCheckServiceTests
|
||||||
{
|
{
|
||||||
[Fact]
|
|
||||||
public void Constructor_BuildsDictionaryOfChecks()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var fooCheck = new HealthCheck("Foo", _ => Task.FromResult(HealthCheckResult.Healthy()));
|
|
||||||
var barCheck = new HealthCheck("Bar", _ => Task.FromResult(HealthCheckResult.Healthy()));
|
|
||||||
var bazCheck = new HealthCheck("Baz", _ => Task.FromResult(HealthCheckResult.Healthy()));
|
|
||||||
var checks = new[] { fooCheck, barCheck, bazCheck };
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var service = new HealthCheckService(checks);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.Same(fooCheck, service.Checks["Foo"]);
|
|
||||||
Assert.Same(barCheck, service.Checks["Bar"]);
|
|
||||||
Assert.Same(bazCheck, service.Checks["Baz"]);
|
|
||||||
Assert.Equal(3, service.Checks.Count);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Constructor_ThrowsUsefulExceptionForDuplicateNames()
|
public void Constructor_ThrowsUsefulExceptionForDuplicateNames()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var checks = new[]
|
//
|
||||||
{
|
// Doing this the old fashioned way so we can verify that the exception comes
|
||||||
new HealthCheck("Foo", _ => Task.FromResult(HealthCheckResult.Healthy())),
|
// from the constructor.
|
||||||
new HealthCheck("Foo", _ => Task.FromResult(HealthCheckResult.Healthy())),
|
var serviceCollection = new ServiceCollection();
|
||||||
new HealthCheck("Bar", _ => Task.FromResult(HealthCheckResult.Healthy())),
|
serviceCollection.AddLogging();
|
||||||
new HealthCheck("Baz", _ => Task.FromResult(HealthCheckResult.Healthy())),
|
serviceCollection.AddOptions();
|
||||||
new HealthCheck("Baz", _ => Task.FromResult(HealthCheckResult.Healthy())),
|
serviceCollection.AddHealthChecks()
|
||||||
};
|
.AddCheck(new HealthCheck("Foo", _ => Task.FromResult(HealthCheckResult.Healthy())))
|
||||||
|
.AddCheck(new HealthCheck("Foo", _ => Task.FromResult(HealthCheckResult.Healthy())))
|
||||||
|
.AddCheck(new HealthCheck("Bar", _ => Task.FromResult(HealthCheckResult.Healthy())))
|
||||||
|
.AddCheck(new HealthCheck("Baz", _ => Task.FromResult(HealthCheckResult.Healthy())))
|
||||||
|
.AddCheck(new HealthCheck("Baz", _ => Task.FromResult(HealthCheckResult.Healthy())));
|
||||||
|
|
||||||
|
var services = serviceCollection.BuildServiceProvider();
|
||||||
|
|
||||||
|
var scopeFactory = services.GetRequiredService<IServiceScopeFactory>();
|
||||||
|
var logger = services.GetRequiredService<ILogger<HealthCheckService>>();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var exception = Assert.Throws<ArgumentException>(() => new HealthCheckService(checks));
|
var exception = Assert.Throws<ArgumentException>(() => new HealthCheckService(scopeFactory, logger));
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal($"Duplicate health checks were registered with the name(s): Foo, Baz{Environment.NewLine}Parameter name: healthChecks", exception.Message);
|
Assert.Equal($"Duplicate health checks were registered with the name(s): Foo, Baz{Environment.NewLine}Parameter name: healthChecks", exception.Message);
|
||||||
|
|
@ -67,15 +59,11 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks
|
||||||
{ DataKey, DataValue }
|
{ DataKey, DataValue }
|
||||||
};
|
};
|
||||||
|
|
||||||
var healthyCheck = new HealthCheck("HealthyCheck", _ => Task.FromResult(HealthCheckResult.Healthy(HealthyMessage, data)));
|
var service = CreateHealthChecksService(b =>
|
||||||
var degradedCheck = new HealthCheck("DegradedCheck", _ => Task.FromResult(HealthCheckResult.Degraded(DegradedMessage)));
|
|
||||||
var unhealthyCheck = new HealthCheck("UnhealthyCheck", _ => Task.FromResult(HealthCheckResult.Unhealthy(UnhealthyMessage, exception)));
|
|
||||||
|
|
||||||
var service = new HealthCheckService(new[]
|
|
||||||
{
|
{
|
||||||
healthyCheck,
|
b.AddCheck("HealthyCheck", _ => Task.FromResult(HealthCheckResult.Healthy(HealthyMessage, data)));
|
||||||
degradedCheck,
|
b.AddCheck("DegradedCheck", _ => Task.FromResult(HealthCheckResult.Degraded(DegradedMessage)));
|
||||||
unhealthyCheck,
|
b.AddCheck("UnhealthyCheck", _ => Task.FromResult(HealthCheckResult.Unhealthy(UnhealthyMessage, exception)));
|
||||||
});
|
});
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
|
|
@ -85,7 +73,7 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks
|
||||||
Assert.Collection(results.Results,
|
Assert.Collection(results.Results,
|
||||||
actual =>
|
actual =>
|
||||||
{
|
{
|
||||||
Assert.Equal(healthyCheck.Name, actual.Key);
|
Assert.Equal("HealthyCheck", actual.Key);
|
||||||
Assert.Equal(HealthyMessage, actual.Value.Description);
|
Assert.Equal(HealthyMessage, actual.Value.Description);
|
||||||
Assert.Equal(HealthCheckStatus.Healthy, actual.Value.Status);
|
Assert.Equal(HealthCheckStatus.Healthy, actual.Value.Status);
|
||||||
Assert.Null(actual.Value.Exception);
|
Assert.Null(actual.Value.Exception);
|
||||||
|
|
@ -97,7 +85,7 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks
|
||||||
},
|
},
|
||||||
actual =>
|
actual =>
|
||||||
{
|
{
|
||||||
Assert.Equal(degradedCheck.Name, actual.Key);
|
Assert.Equal("DegradedCheck", actual.Key);
|
||||||
Assert.Equal(DegradedMessage, actual.Value.Description);
|
Assert.Equal(DegradedMessage, actual.Value.Description);
|
||||||
Assert.Equal(HealthCheckStatus.Degraded, actual.Value.Status);
|
Assert.Equal(HealthCheckStatus.Degraded, actual.Value.Status);
|
||||||
Assert.Null(actual.Value.Exception);
|
Assert.Null(actual.Value.Exception);
|
||||||
|
|
@ -105,7 +93,7 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks
|
||||||
},
|
},
|
||||||
actual =>
|
actual =>
|
||||||
{
|
{
|
||||||
Assert.Equal(unhealthyCheck.Name, actual.Key);
|
Assert.Equal("UnhealthyCheck", actual.Key);
|
||||||
Assert.Equal(UnhealthyMessage, actual.Value.Description);
|
Assert.Equal(UnhealthyMessage, actual.Value.Description);
|
||||||
Assert.Equal(HealthCheckStatus.Unhealthy, actual.Value.Status);
|
Assert.Equal(HealthCheckStatus.Unhealthy, actual.Value.Status);
|
||||||
Assert.Same(exception, actual.Value.Exception);
|
Assert.Same(exception, actual.Value.Exception);
|
||||||
|
|
@ -114,7 +102,7 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task CheckAsync_RunsProvidedChecksAndAggregatesResultsAsync()
|
public async Task CheckAsync_RunsFilteredChecksAndAggregatesResultsAsync()
|
||||||
{
|
{
|
||||||
const string DataKey = "Foo";
|
const string DataKey = "Foo";
|
||||||
const string DataValue = "Bar";
|
const string DataValue = "Bar";
|
||||||
|
|
@ -129,28 +117,21 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks
|
||||||
{ DataKey, DataValue }
|
{ DataKey, DataValue }
|
||||||
};
|
};
|
||||||
|
|
||||||
var healthyCheck = new HealthCheck("HealthyCheck", _ => Task.FromResult(HealthCheckResult.Healthy(HealthyMessage, data)));
|
var service = CreateHealthChecksService(b =>
|
||||||
var degradedCheck = new HealthCheck("DegradedCheck", _ => Task.FromResult(HealthCheckResult.Degraded(DegradedMessage)));
|
|
||||||
var unhealthyCheck = new HealthCheck("UnhealthyCheck", _ => Task.FromResult(HealthCheckResult.Unhealthy(UnhealthyMessage, exception)));
|
|
||||||
|
|
||||||
var service = new HealthCheckService(new[]
|
|
||||||
{
|
{
|
||||||
healthyCheck,
|
b.AddCheck("HealthyCheck", _ => Task.FromResult(HealthCheckResult.Healthy(HealthyMessage, data)));
|
||||||
degradedCheck,
|
b.AddCheck("DegradedCheck", _ => Task.FromResult(HealthCheckResult.Degraded(DegradedMessage)));
|
||||||
unhealthyCheck,
|
b.AddCheck("UnhealthyCheck", _ => Task.FromResult(HealthCheckResult.Unhealthy(UnhealthyMessage, exception)));
|
||||||
});
|
});
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var results = await service.CheckHealthAsync(new[]
|
var results = await service.CheckHealthAsync(c => c.Name == "HealthyCheck");
|
||||||
{
|
|
||||||
service.Checks["HealthyCheck"]
|
|
||||||
});
|
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Collection(results.Results,
|
Assert.Collection(results.Results,
|
||||||
actual =>
|
actual =>
|
||||||
{
|
{
|
||||||
Assert.Equal(healthyCheck.Name, actual.Key);
|
Assert.Equal("HealthyCheck", actual.Key);
|
||||||
Assert.Equal(HealthyMessage, actual.Value.Description);
|
Assert.Equal(HealthyMessage, actual.Value.Description);
|
||||||
Assert.Equal(HealthCheckStatus.Healthy, actual.Value.Status);
|
Assert.Equal(HealthCheckStatus.Healthy, actual.Value.Status);
|
||||||
Assert.Null(actual.Value.Exception);
|
Assert.Null(actual.Value.Exception);
|
||||||
|
|
@ -168,11 +149,12 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks
|
||||||
// Arrange
|
// Arrange
|
||||||
var thrownException = new InvalidOperationException("Whoops!");
|
var thrownException = new InvalidOperationException("Whoops!");
|
||||||
var faultedException = new InvalidOperationException("Ohnoes!");
|
var faultedException = new InvalidOperationException("Ohnoes!");
|
||||||
var service = new HealthCheckService(new[]
|
|
||||||
|
var service = CreateHealthChecksService(b =>
|
||||||
{
|
{
|
||||||
new HealthCheck("Throws", ct => throw thrownException),
|
b.AddCheck("Throws", ct => throw thrownException);
|
||||||
new HealthCheck("Faults", ct => Task.FromException<HealthCheckResult>(faultedException)),
|
b.AddCheck("Faults", ct => Task.FromException<HealthCheckResult>(faultedException));
|
||||||
new HealthCheck("Succeeds", ct => Task.FromResult(HealthCheckResult.Healthy())),
|
b.AddCheck("Succeeds", ct => Task.FromResult(HealthCheckResult.Healthy()));
|
||||||
});
|
});
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
|
|
@ -223,8 +205,15 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks
|
||||||
});
|
});
|
||||||
return Task.FromResult(HealthCheckResult.Healthy());
|
return Task.FromResult(HealthCheckResult.Healthy());
|
||||||
});
|
});
|
||||||
|
|
||||||
var loggerFactory = new TestLoggerFactory(sink, enabled: true);
|
var loggerFactory = new TestLoggerFactory(sink, enabled: true);
|
||||||
var service = new HealthCheckService(new[] { check }, loggerFactory.CreateLogger<HealthCheckService>());
|
var service = CreateHealthChecksService(b =>
|
||||||
|
{
|
||||||
|
// Override the logger factory for testing
|
||||||
|
b.Services.AddSingleton<ILoggerFactory>(loggerFactory);
|
||||||
|
|
||||||
|
b.AddCheck(check);
|
||||||
|
});
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var results = await service.CheckHealthAsync();
|
var results = await service.CheckHealthAsync();
|
||||||
|
|
@ -241,9 +230,9 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks
|
||||||
public async Task CheckHealthAsync_ThrowsIfCheckReturnsUnknownStatusResult()
|
public async Task CheckHealthAsync_ThrowsIfCheckReturnsUnknownStatusResult()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var service = new HealthCheckService(new[]
|
var service = CreateHealthChecksService(b =>
|
||||||
{
|
{
|
||||||
new HealthCheck("Kaboom", ct => Task.FromResult(default(HealthCheckResult))),
|
b.AddCheck("Kaboom", ct => Task.FromResult(default(HealthCheckResult)));
|
||||||
});
|
});
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
|
|
@ -252,5 +241,108 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal("Health check 'Kaboom' returned a result with a status of Unknown", ex.Message);
|
Assert.Equal("Health check 'Kaboom' returned a result with a status of Unknown", ex.Message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task CheckHealthAsync_CheckCanDependOnTransientService()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var service = CreateHealthChecksService(b =>
|
||||||
|
{
|
||||||
|
b.Services.AddTransient<AnotherService>();
|
||||||
|
|
||||||
|
b.AddCheck<CheckWithServiceDependency>();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var results = await service.CheckHealthAsync();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Collection(
|
||||||
|
results.Results,
|
||||||
|
actual =>
|
||||||
|
{
|
||||||
|
Assert.Equal("Test", actual.Key);
|
||||||
|
Assert.Equal(HealthCheckStatus.Healthy, actual.Value.Status);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task CheckHealthAsync_CheckCanDependOnScopedService()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var service = CreateHealthChecksService(b =>
|
||||||
|
{
|
||||||
|
b.Services.AddScoped<AnotherService>();
|
||||||
|
|
||||||
|
b.AddCheck<CheckWithServiceDependency>();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var results = await service.CheckHealthAsync();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Collection(
|
||||||
|
results.Results,
|
||||||
|
actual =>
|
||||||
|
{
|
||||||
|
Assert.Equal("Test", actual.Key);
|
||||||
|
Assert.Equal(HealthCheckStatus.Healthy, actual.Value.Status);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task CheckHealthAsync_CheckCanDependOnSingletonService()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var service = CreateHealthChecksService(b =>
|
||||||
|
{
|
||||||
|
b.Services.AddSingleton<AnotherService>();
|
||||||
|
|
||||||
|
b.AddCheck<CheckWithServiceDependency>();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var results = await service.CheckHealthAsync();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Collection(
|
||||||
|
results.Results,
|
||||||
|
actual =>
|
||||||
|
{
|
||||||
|
Assert.Equal("Test", actual.Key);
|
||||||
|
Assert.Equal(HealthCheckStatus.Healthy, actual.Value.Status);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private static HealthCheckService CreateHealthChecksService(Action<IHealthChecksBuilder> configure)
|
||||||
|
{
|
||||||
|
var services = new ServiceCollection();
|
||||||
|
services.AddLogging();
|
||||||
|
services.AddOptions();
|
||||||
|
|
||||||
|
var builder = services.AddHealthChecks();
|
||||||
|
if (configure != null)
|
||||||
|
{
|
||||||
|
configure(builder);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (HealthCheckService)services.BuildServiceProvider(validateScopes: true).GetRequiredService<IHealthCheckService>();
|
||||||
|
}
|
||||||
|
|
||||||
|
private class AnotherService { }
|
||||||
|
|
||||||
|
private class CheckWithServiceDependency : IHealthCheck
|
||||||
|
{
|
||||||
|
public CheckWithServiceDependency(AnotherService _)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Name => "Test";
|
||||||
|
|
||||||
|
public Task<HealthCheckResult> CheckHealthAsync(CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
return Task.FromResult(HealthCheckResult.Healthy());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
@ -19,12 +21,13 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks
|
||||||
.AddCheck("Bar", () => Task.FromResult(HealthCheckResult.Healthy()));
|
.AddCheck("Bar", () => Task.FromResult(HealthCheckResult.Healthy()));
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var healthCheckService = services.BuildServiceProvider().GetRequiredService<IHealthCheckService>();
|
var checks = services.BuildServiceProvider().GetRequiredService<IEnumerable<IHealthCheck>>();
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Collection(healthCheckService.Checks,
|
Assert.Collection(
|
||||||
actual => Assert.Equal("Foo", actual.Key),
|
checks,
|
||||||
actual => Assert.Equal("Bar", actual.Key));
|
actual => Assert.Equal("Foo", actual.Name),
|
||||||
|
actual => Assert.Equal("Bar", actual.Name));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue