Use options for registering health checks (#479)
* Use options for registering health checks This change pivots to use options for registering health checks. We get a few pretty nice things out of this, and it unblocks some of our requirements. Now all registration methods support the application developer configuring the name, failure-status, and tags for each health check. This is a requirment, that we weren't really satisfying - which is what led to this redesign. In support of this health checks now return pass/fail, and the service is responsible for assigning the status. ---- Health check authors that need configuration data (connection string as an example) now have three ways to do this depending on their requirements. 1. Create an instance and register that (easiest) 2. Use Type Activation and pass parameters (middle) 3. Use named options (richest) We expect most health checks to need/want some kind of configuration - which 1) works pretty well to solve. However many other health checks will need DI + configuration. It was also a gap that we didn't have a good way to use named options, when it's such a good fit for our scenarios. Added new registration methods for type activation that allow you to pass parameters for 2). Added a context type that allows the running health check access to its registration for 3). ---- Redesigned and renamed how status gets reported. Health checks return pass/fail result, but the overall HealthReport includes entries of a different type. This seems fine because there isn't really a way to consume a HealthCheckResult directly - the service is the only consumer. ---- Added support for tags. This was easy to add now that we have a separate registration type, and it's quite handy for building filters (see sample). * HARDER BETTER STRONGER FASTER
This commit is contained in:
parent
3ac6439dcf
commit
4259b65c16
|
|
@ -17,14 +17,10 @@ namespace HealthChecksSample
|
|||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
// Registers required services for health checks
|
||||
services.AddHealthChecks();
|
||||
|
||||
// This is an example of registering a custom health check as a service.
|
||||
// All IHealthCheck services will be available to the health check service and
|
||||
// middleware.
|
||||
//
|
||||
// We recommend registering all health checks as Singleton services.
|
||||
services.AddSingleton<IHealthCheck, GCInfoHealthCheck>();
|
||||
services.AddHealthChecks()
|
||||
|
||||
// Registers a custom health check implementation
|
||||
.AddGCInfoCheck("GCInfo");
|
||||
}
|
||||
|
||||
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
|
||||
|
|
@ -45,13 +41,13 @@ namespace HealthChecksSample
|
|||
});
|
||||
}
|
||||
|
||||
private static Task WriteResponse(HttpContext httpContext, CompositeHealthCheckResult result)
|
||||
private static Task WriteResponse(HttpContext httpContext, HealthReport result)
|
||||
{
|
||||
httpContext.Response.ContentType = "application/json";
|
||||
|
||||
var json = new JObject(
|
||||
new JProperty("status", result.Status.ToString()),
|
||||
new JProperty("results", new JObject(result.Results.Select(pair =>
|
||||
new JProperty("results", new JObject(result.Entries.Select(pair =>
|
||||
new JProperty(pair.Key, new JObject(
|
||||
new JProperty("status", pair.Value.Status.ToString()),
|
||||
new JProperty("description", pair.Value.Description),
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ namespace HealthChecksSample
|
|||
// Registers required services for health checks
|
||||
services.AddHealthChecks()
|
||||
// Add a health check for a SQL database
|
||||
.AddCheck(new SqlConnectionHealthCheck("MyDatabase", Configuration["ConnectionStrings:DefaultConnection"]));
|
||||
.AddCheck("MyDatabase", new SqlConnectionHealthCheck(Configuration["ConnectionStrings:DefaultConnection"]));
|
||||
}
|
||||
|
||||
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
|
||||
|
|
|
|||
|
|
@ -11,20 +11,17 @@ namespace HealthChecksSample
|
|||
{
|
||||
public abstract class DbConnectionHealthCheck : IHealthCheck
|
||||
{
|
||||
protected DbConnectionHealthCheck(string name, string connectionString)
|
||||
: this(name, connectionString, testQuery: null)
|
||||
protected DbConnectionHealthCheck(string connectionString)
|
||||
: this(connectionString, testQuery: null)
|
||||
{
|
||||
}
|
||||
|
||||
protected DbConnectionHealthCheck(string name, string connectionString, string testQuery)
|
||||
protected DbConnectionHealthCheck(string connectionString, string testQuery)
|
||||
{
|
||||
Name = name ?? throw new System.ArgumentNullException(nameof(name));
|
||||
ConnectionString = connectionString ?? throw new ArgumentNullException(nameof(connectionString));
|
||||
TestQuery = testQuery;
|
||||
}
|
||||
|
||||
public string Name { get; }
|
||||
|
||||
protected string ConnectionString { get; }
|
||||
|
||||
// This sample supports specifying a query to run as a boolean test of whether the database
|
||||
|
|
@ -36,7 +33,7 @@ namespace HealthChecksSample
|
|||
|
||||
protected abstract DbConnection CreateConnection(string connectionString);
|
||||
|
||||
public async Task<HealthCheckResult> CheckHealthAsync(CancellationToken cancellationToken = default(CancellationToken))
|
||||
public async Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
using (var connection = CreateConnection(ConnectionString))
|
||||
{
|
||||
|
|
@ -54,11 +51,11 @@ namespace HealthChecksSample
|
|||
}
|
||||
catch (DbException ex)
|
||||
{
|
||||
return HealthCheckResult.Unhealthy(ex);
|
||||
return HealthCheckResult.Failed(exception: ex);
|
||||
}
|
||||
}
|
||||
|
||||
return HealthCheckResult.Healthy();
|
||||
return HealthCheckResult.Passed();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,21 +2,58 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Diagnostics.HealthChecks;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace HealthChecksSample
|
||||
{
|
||||
// This is an example of a custom health check that implements IHealthCheck.
|
||||
//
|
||||
// See CustomWriterStartup to see how this is registered.
|
||||
// This example also shows a technique for authoring a health check that needs to be registered
|
||||
// with additional configuration data. This technique works via named options, and is useful
|
||||
// for authoring health checks that can be disctributed as libraries.
|
||||
|
||||
public static class GCInfoHealthCheckBuilderExtensions
|
||||
{
|
||||
public static IHealthChecksBuilder AddGCInfoCheck(
|
||||
this IHealthChecksBuilder builder,
|
||||
string name,
|
||||
HealthStatus? failureStatus = null,
|
||||
IEnumerable<string> tags = null,
|
||||
long? thresholdInBytes = null)
|
||||
{
|
||||
// Register a check of type GCInfo
|
||||
builder.AddCheck<GCInfoHealthCheck>(name, failureStatus ?? HealthStatus.Degraded, tags);
|
||||
|
||||
// Configure named options to pass the threshold into the check.
|
||||
if (thresholdInBytes.HasValue)
|
||||
{
|
||||
builder.Services.Configure<GCInfoOptions>(name, options =>
|
||||
{
|
||||
options.Threshold = thresholdInBytes.Value;
|
||||
});
|
||||
}
|
||||
|
||||
return builder;
|
||||
}
|
||||
}
|
||||
|
||||
public class GCInfoHealthCheck : IHealthCheck
|
||||
{
|
||||
public string Name { get; } = "GCInfo";
|
||||
private readonly IOptionsMonitor<GCInfoOptions> _options;
|
||||
|
||||
public Task<HealthCheckResult> CheckHealthAsync(CancellationToken cancellationToken = default(CancellationToken))
|
||||
public GCInfoHealthCheck(IOptionsMonitor<GCInfoOptions> options)
|
||||
{
|
||||
_options = options;
|
||||
}
|
||||
|
||||
public Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
var options = _options.Get(context.Registration.Name);
|
||||
|
||||
// This example will report degraded status if the application is using
|
||||
// more than 1gb of memory.
|
||||
// more than the configured amount of memory (1gb by default).
|
||||
//
|
||||
// Additionally we include some GC info in the reported diagnostics.
|
||||
var allocated = GC.GetTotalMemory(forceFullCollection: false);
|
||||
|
|
@ -28,14 +65,20 @@ namespace HealthChecksSample
|
|||
{ "Gen2Collections", GC.CollectionCount(2) },
|
||||
};
|
||||
|
||||
// Report degraded status if the allocated memory is >= 1gb (in bytes)
|
||||
var status = allocated >= 1024 * 1024 * 1024 ? HealthCheckStatus.Degraded : HealthCheckStatus.Healthy;
|
||||
// Report failure if the allocated memory is >= the threshold
|
||||
var result = allocated >= options.Threshold;
|
||||
|
||||
return Task.FromResult(new HealthCheckResult(
|
||||
status,
|
||||
exception: null,
|
||||
result,
|
||||
description: "reports degraded status if allocated bytes >= 1gb",
|
||||
exception: null,
|
||||
data: data));
|
||||
}
|
||||
}
|
||||
|
||||
public class GCInfoOptions
|
||||
{
|
||||
// The failure threshold (in bytes)
|
||||
public long Threshold { get; set; } = 1024L * 1024L * 1024L;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,9 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Diagnostics.HealthChecks;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Diagnostics.HealthChecks;
|
||||
|
||||
namespace HealthChecksSample
|
||||
{
|
||||
|
|
@ -17,7 +15,7 @@ namespace HealthChecksSample
|
|||
// Registers required services for health checks
|
||||
services
|
||||
.AddHealthChecks()
|
||||
.AddCheck(new SlowDependencyHealthCheck());
|
||||
.AddCheck<SlowDependencyHealthCheck>("Slow", failureStatus: null, tags: new[] { "ready", });
|
||||
}
|
||||
|
||||
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
|
||||
|
|
@ -46,10 +44,13 @@ namespace HealthChecksSample
|
|||
// long initialization time (15 seconds).
|
||||
|
||||
|
||||
// The readiness check uses all of the registered health checks (default)
|
||||
app.UseHealthChecks("/health/ready");
|
||||
// The readiness check uses all registered checks with the 'ready' tag.
|
||||
app.UseHealthChecks("/health/ready", new HealthCheckOptions()
|
||||
{
|
||||
Predicate = (check) => check.Tags.Contains("ready"),
|
||||
});
|
||||
|
||||
// The liveness check uses an 'identity' health check that always returns healthy
|
||||
// The liveness filters out all checks and just returns success
|
||||
app.UseHealthChecks("/health/live", new HealthCheckOptions()
|
||||
{
|
||||
// Exclude all checks, just return a 200.
|
||||
|
|
|
|||
|
|
@ -17,16 +17,14 @@ namespace HealthChecksSample
|
|||
_task = Task.Delay(15 * 1000);
|
||||
}
|
||||
|
||||
public string Name => HealthCheckName;
|
||||
|
||||
public Task<HealthCheckResult> CheckHealthAsync(CancellationToken cancellationToken = default(CancellationToken))
|
||||
public Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
if (_task.IsCompleted)
|
||||
{
|
||||
return Task.FromResult(HealthCheckResult.Healthy("Dependency is ready"));
|
||||
return Task.FromResult(HealthCheckResult.Passed("Dependency is ready"));
|
||||
}
|
||||
|
||||
return Task.FromResult(HealthCheckResult.Unhealthy("Dependency is still initializing"));
|
||||
return Task.FromResult(HealthCheckResult.Failed("Dependency is still initializing"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
using System.Data.Common;
|
||||
using System.Data.SqlClient;
|
||||
using Microsoft.Extensions.Diagnostics.HealthChecks;
|
||||
|
||||
namespace HealthChecksSample
|
||||
{
|
||||
|
|
@ -8,13 +7,13 @@ namespace HealthChecksSample
|
|||
{
|
||||
private static readonly string DefaultTestQuery = "Select 1";
|
||||
|
||||
public SqlConnectionHealthCheck(string name, string connectionString)
|
||||
: this(name, connectionString, testQuery: DefaultTestQuery)
|
||||
public SqlConnectionHealthCheck(string connectionString)
|
||||
: this(connectionString, testQuery: DefaultTestQuery)
|
||||
{
|
||||
}
|
||||
|
||||
public SqlConnectionHealthCheck(string name, string connectionString, string testQuery)
|
||||
: base(name, connectionString, testQuery ?? DefaultTestQuery)
|
||||
public SqlConnectionHealthCheck(string connectionString, string testQuery)
|
||||
: base(connectionString, testQuery ?? DefaultTestQuery)
|
||||
{
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -15,12 +15,12 @@ namespace Microsoft.AspNetCore.Diagnostics.HealthChecks
|
|||
{
|
||||
private readonly RequestDelegate _next;
|
||||
private readonly HealthCheckOptions _healthCheckOptions;
|
||||
private readonly IHealthCheckService _healthCheckService;
|
||||
private readonly HealthCheckService _healthCheckService;
|
||||
|
||||
public HealthCheckMiddleware(
|
||||
RequestDelegate next,
|
||||
IOptions<HealthCheckOptions> healthCheckOptions,
|
||||
IHealthCheckService healthCheckService)
|
||||
HealthCheckService healthCheckService)
|
||||
{
|
||||
if (next == null)
|
||||
{
|
||||
|
|
@ -61,7 +61,7 @@ namespace Microsoft.AspNetCore.Diagnostics.HealthChecks
|
|||
if (!_healthCheckOptions.ResultStatusCodes.TryGetValue(result.Status, out var statusCode))
|
||||
{
|
||||
var message =
|
||||
$"No status code mapping found for {nameof(HealthCheckStatus)} value: {result.Status}." +
|
||||
$"No status code mapping found for {nameof(HealthStatus)} value: {result.Status}." +
|
||||
$"{nameof(HealthCheckOptions)}.{nameof(HealthCheckOptions.ResultStatusCodes)} must contain" +
|
||||
$"an entry for {result.Status}.";
|
||||
|
||||
|
|
|
|||
|
|
@ -22,20 +22,20 @@ namespace Microsoft.AspNetCore.Diagnostics.HealthChecks
|
|||
/// registered health checks - this is the default behavior. To run a subset of health checks,
|
||||
/// provide a function that filters the set of checks.
|
||||
/// </remarks>
|
||||
public Func<IHealthCheck, bool> Predicate { get; set; }
|
||||
public Func<HealthCheckRegistration, bool> Predicate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a dictionary mapping the <see cref="HealthCheckStatus"/> to an HTTP status code applied to the response.
|
||||
/// Gets a dictionary mapping the <see cref="HealthStatus"/> 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<HealthStatus, int> ResultStatusCodes { get; } = new Dictionary<HealthStatus, int>()
|
||||
{
|
||||
{ HealthCheckStatus.Healthy, StatusCodes.Status200OK },
|
||||
{ HealthCheckStatus.Degraded, StatusCodes.Status200OK },
|
||||
{ HealthCheckStatus.Unhealthy, StatusCodes.Status503ServiceUnavailable },
|
||||
{ HealthStatus.Healthy, StatusCodes.Status200OK },
|
||||
{ HealthStatus.Degraded, StatusCodes.Status200OK },
|
||||
{ HealthStatus.Unhealthy, StatusCodes.Status503ServiceUnavailable },
|
||||
|
||||
// This means that a health check failed, so 500 is appropriate. This is an error.
|
||||
{ HealthCheckStatus.Failed, StatusCodes.Status500InternalServerError },
|
||||
{ HealthStatus.Failed, StatusCodes.Status500InternalServerError },
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -43,8 +43,8 @@ namespace Microsoft.AspNetCore.Diagnostics.HealthChecks
|
|||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The default value is a delegate that will write a minimal <c>text/plain</c> response with the value
|
||||
/// of <see cref="CompositeHealthCheckResult.Status"/> as a string.
|
||||
/// of <see cref="HealthReport.Status"/> as a string.
|
||||
/// </remarks>
|
||||
public Func<HttpContext, CompositeHealthCheckResult, Task> ResponseWriter { get; set; } = HealthCheckResponseWriters.WriteMinimalPlaintext;
|
||||
public Func<HttpContext, HealthReport, Task> ResponseWriter { get; set; } = HealthCheckResponseWriters.WriteMinimalPlaintext;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ namespace Microsoft.AspNetCore.Diagnostics.HealthChecks
|
|||
{
|
||||
internal static class HealthCheckResponseWriters
|
||||
{
|
||||
public static Task WriteMinimalPlaintext(HttpContext httpContext, CompositeHealthCheckResult result)
|
||||
public static Task WriteMinimalPlaintext(HttpContext httpContext, HealthReport result)
|
||||
{
|
||||
httpContext.Response.ContentType = "text/plain";
|
||||
return httpContext.Response.WriteAsync(result.Status.ToString());
|
||||
|
|
|
|||
|
|
@ -0,0 +1,13 @@
|
|||
// 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.
|
||||
|
||||
namespace Microsoft.Extensions.Diagnostics.HealthChecks
|
||||
{
|
||||
public sealed class HealthCheckContext
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the <see cref="HealthCheckRegistration"/> of the currently executing <see cref="IHealthCheck"/>.
|
||||
/// </summary>
|
||||
public HealthCheckRegistration Registration { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,132 @@
|
|||
// 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.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Microsoft.Extensions.Diagnostics.HealthChecks
|
||||
{
|
||||
/// <summary>
|
||||
/// Represent the registration information associated with an <see cref="IHealthCheck"/> implementation.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// The health check registration is provided as a separate object so that application developers can customize
|
||||
/// how health check implementations are configured.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// The registration is provided to an <see cref="IHealthCheck"/> implementation during execution through
|
||||
/// <see cref="HealthCheckContext.Registration"/>. This allows a health check implementation to access named
|
||||
/// options or perform other operations based on the registered name.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public sealed class HealthCheckRegistration
|
||||
{
|
||||
private Func<IServiceProvider, IHealthCheck> _factory;
|
||||
private string _name;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="HealthCheckRegistration"/> for an existing <see cref="IHealthCheck"/> instance.
|
||||
/// </summary>
|
||||
/// <param name="name">The health check name.</param>
|
||||
/// <param name="instance">The <see cref="IHealthCheck"/> instance.</param>
|
||||
/// <param name="failureStatus">
|
||||
/// The <see cref="HealthStatus"/> that should be reported upon failure of the health check. If the provided value
|
||||
/// is <c>null</c>, then <see cref="HealthStatus.Unhealthy"/> will be reported.
|
||||
/// </param>
|
||||
/// <param name="tags">A list of tags that can be used for filtering health checks.</param>
|
||||
public HealthCheckRegistration(string name, IHealthCheck instance, HealthStatus? failureStatus, IEnumerable<string> tags)
|
||||
{
|
||||
if (name == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(name));
|
||||
}
|
||||
|
||||
if (instance == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(instance));
|
||||
}
|
||||
|
||||
Name = name;
|
||||
FailureStatus = failureStatus ?? HealthStatus.Unhealthy;
|
||||
Tags = new HashSet<string>(tags ?? Array.Empty<string>(), StringComparer.OrdinalIgnoreCase);
|
||||
Factory = (_) => instance;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="HealthCheckRegistration"/> for an existing <see cref="IHealthCheck"/> instance.
|
||||
/// </summary>
|
||||
/// <param name="name">The health check name.</param>
|
||||
/// <param name="factory">A delegate used to create the <see cref="IHealthCheck"/> instance.</param>
|
||||
/// <param name="failureStatus">
|
||||
/// The <see cref="HealthStatus"/> that should be reported when the health check reports a failure. If the provided value
|
||||
/// is <c>null</c>, then <see cref="HealthStatus.Unhealthy"/> will be reported.
|
||||
/// </param>
|
||||
/// <param name="tags">A list of tags that can be used for filtering health checks.</param>
|
||||
public HealthCheckRegistration(
|
||||
string name,
|
||||
Func<IServiceProvider, IHealthCheck> factory,
|
||||
HealthStatus? failureStatus,
|
||||
IEnumerable<string> tags)
|
||||
{
|
||||
if (name == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(name));
|
||||
}
|
||||
|
||||
if (factory == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(factory));
|
||||
}
|
||||
|
||||
Name = name;
|
||||
FailureStatus = failureStatus ?? HealthStatus.Unhealthy;
|
||||
Tags = new HashSet<string>(tags ?? Array.Empty<string>(), StringComparer.OrdinalIgnoreCase);
|
||||
Factory = factory;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a delegate used to create the <see cref="IHealthCheck"/> instance.
|
||||
/// </summary>
|
||||
public Func<IServiceProvider, IHealthCheck> Factory
|
||||
{
|
||||
get => _factory;
|
||||
set
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(value));
|
||||
}
|
||||
|
||||
_factory = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the <see cref="HealthStatus"/> that should be reported upon failure of the health check.
|
||||
/// </summary>
|
||||
public HealthStatus FailureStatus { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the health check name.
|
||||
/// </summary>
|
||||
public string Name
|
||||
{
|
||||
get => _name;
|
||||
set
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(value));
|
||||
}
|
||||
|
||||
_name = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of tags that can be used for filtering health checks.
|
||||
/// </summary>
|
||||
public ISet<string> Tags { get; }
|
||||
}
|
||||
}
|
||||
|
|
@ -13,176 +13,65 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks
|
|||
{
|
||||
private static readonly IReadOnlyDictionary<string, object> _emptyReadOnlyDictionary = new Dictionary<string, object>();
|
||||
|
||||
private string _description;
|
||||
private IReadOnlyDictionary<string, object> _data;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a <see cref="HealthCheckStatus"/> value indicating the status of the component that was checked.
|
||||
/// Creates a new <see cref="HealthCheckResult"/> with the specified values for <paramref name="result"/>, <paramref name="exception"/>,
|
||||
/// <paramref name="description"/>, and <paramref name="data"/>.
|
||||
/// </summary>
|
||||
public HealthCheckStatus Status { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets an <see cref="Exception"/> representing the exception that was thrown when checking for status (if any).
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This value is expected to be 'null' if <see cref="Status"/> is <see cref="HealthCheckStatus.Healthy"/>.
|
||||
/// </remarks>
|
||||
public Exception Exception { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a human-readable description of the status of the component that was checked.
|
||||
/// </summary>
|
||||
public string Description => _description ?? string.Empty;
|
||||
/// <param name="result">A value indicating the pass/fail status of the component that was checked.</param>
|
||||
/// <param name="description">A human-readable description of the status of the component that was checked.</param>
|
||||
/// <param name="exception">An <see cref="Exception"/> representing the exception that was thrown when checking for status (if any).</param>
|
||||
/// <param name="data">Additional key-value pairs describing the health of the component.</param>
|
||||
public HealthCheckResult(bool result, string description, Exception exception, IReadOnlyDictionary<string, object> data)
|
||||
{
|
||||
Result = result;
|
||||
Description = description;
|
||||
Exception = exception;
|
||||
Data = data ?? _emptyReadOnlyDictionary;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets additional key-value pairs describing the health of the component.
|
||||
/// </summary>
|
||||
public IReadOnlyDictionary<string, object> Data => _data ?? _emptyReadOnlyDictionary;
|
||||
public IReadOnlyDictionary<string, object> Data { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="HealthCheckResult"/> with the specified <paramref name="status"/>, <paramref name="exception"/>,
|
||||
/// <paramref name="description"/>, and <paramref name="data"/>.
|
||||
/// Gets a human-readable description of the status of the component that was checked.
|
||||
/// </summary>
|
||||
/// <param name="status">A <see cref="HealthCheckStatus"/> value indicating the status of the component that was checked.</param>
|
||||
/// <param name="exception">An <see cref="Exception"/> representing the exception that was thrown when checking for status (if any).</param>
|
||||
/// <param name="description">A human-readable description of the status of the component that was checked.</param>
|
||||
/// <param name="data">Additional key-value pairs describing the health of the component.</param>
|
||||
public HealthCheckResult(HealthCheckStatus status, Exception exception, string description, IReadOnlyDictionary<string, object> data)
|
||||
{
|
||||
if (status == HealthCheckStatus.Unknown)
|
||||
{
|
||||
throw new ArgumentException($"'{nameof(HealthCheckStatus.Unknown)}' is not a valid value for the 'status' parameter.", nameof(status));
|
||||
}
|
||||
public string Description { get; }
|
||||
|
||||
Status = status;
|
||||
Exception = exception;
|
||||
_description = description;
|
||||
_data = data;
|
||||
/// <summary>
|
||||
/// Gets an <see cref="Exception"/> representing the exception that was thrown when checking for status (if any).
|
||||
/// </summary>
|
||||
public Exception Exception { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating the pass/fail status of the component that was checked. If <c>true</c>, then the component
|
||||
/// is considered to have passed health validation. A <c>false</c> value will be mapped to the configured
|
||||
/// <see cref="HealthStatus"/> by the health check system.
|
||||
/// </summary>
|
||||
public bool Result { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a <see cref="HealthCheckResult"/> representing a passing component.
|
||||
/// </summary>
|
||||
/// <returns>A <see cref="HealthCheckResult"/> representing a passing component.</returns>
|
||||
/// <param name="description">A human-readable description of the status of the component that was checked. Optional.</param>
|
||||
/// <param name="data">Additional key-value pairs describing the health of the component. Optional.</param>
|
||||
public static HealthCheckResult Passed(string description = null, IReadOnlyDictionary<string, object> data = null)
|
||||
{
|
||||
return new HealthCheckResult(result: true, description, exception: null, data);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a <see cref="HealthCheckResult"/> representing an unhealthy component.
|
||||
/// Creates a <see cref="HealthCheckResult"/> representing an failing component.
|
||||
/// </summary>
|
||||
/// <returns>A <see cref="HealthCheckResult"/> representing an unhealthy component.</returns>
|
||||
public static HealthCheckResult Unhealthy()
|
||||
=> new HealthCheckResult(HealthCheckStatus.Unhealthy, exception: null, description: string.Empty, data: null);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a <see cref="HealthCheckResult"/> representing an unhealthy component.
|
||||
/// </summary>
|
||||
/// <returns>A <see cref="HealthCheckResult"/> representing an unhealthy component.</returns>
|
||||
/// <param name="description">A human-readable description of the status of the component that was checked.</param>
|
||||
public static HealthCheckResult Unhealthy(string description)
|
||||
=> new HealthCheckResult(HealthCheckStatus.Unhealthy, exception: null, description: description, data: null);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a <see cref="HealthCheckResult"/> representing an unhealthy component.
|
||||
/// </summary>
|
||||
/// <returns>A <see cref="HealthCheckResult"/> representing an unhealthy component.</returns>
|
||||
/// <param name="description">A human-readable description of the status of the component that was checked.</param>
|
||||
/// <param name="data">Additional key-value pairs describing the health of the component.</param>
|
||||
public static HealthCheckResult Unhealthy(string description, IReadOnlyDictionary<string, object> data)
|
||||
=> new HealthCheckResult(HealthCheckStatus.Unhealthy, exception: null, description: description, data: data);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a <see cref="HealthCheckResult"/> representing an unhealthy component.
|
||||
/// </summary>
|
||||
/// <returns>A <see cref="HealthCheckResult"/> representing an unhealthy component.</returns>
|
||||
/// <param name="exception">An <see cref="Exception"/> representing the exception that was thrown when checking for status (if any).</param>
|
||||
public static HealthCheckResult Unhealthy(Exception exception)
|
||||
=> new HealthCheckResult(HealthCheckStatus.Unhealthy, exception, description: string.Empty, data: null);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a <see cref="HealthCheckResult"/> representing an unhealthy component.
|
||||
/// </summary>
|
||||
/// <returns>A <see cref="HealthCheckResult"/> representing an unhealthy component.</returns>
|
||||
/// <param name="description">A human-readable description of the status of the component that was checked.</param>
|
||||
/// <param name="exception">An <see cref="Exception"/> representing the exception that was thrown when checking for status (if any).</param>
|
||||
public static HealthCheckResult Unhealthy(string description, Exception exception)
|
||||
=> new HealthCheckResult(HealthCheckStatus.Unhealthy, exception, description, data: null);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a <see cref="HealthCheckResult"/> representing an unhealthy component.
|
||||
/// </summary>
|
||||
/// <returns>A <see cref="HealthCheckResult"/> representing an unhealthy component.</returns>
|
||||
/// <param name="description">A human-readable description of the status of the component that was checked.</param>
|
||||
/// <param name="exception">An <see cref="Exception"/> representing the exception that was thrown when checking for status (if any).</param>
|
||||
/// <param name="data">Additional key-value pairs describing the health of the component.</param>
|
||||
public static HealthCheckResult Unhealthy(string description, Exception exception, IReadOnlyDictionary<string, object> data)
|
||||
=> new HealthCheckResult(HealthCheckStatus.Unhealthy, exception, description, data);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a <see cref="HealthCheckResult"/> representing a healthy component.
|
||||
/// </summary>
|
||||
/// <returns>A <see cref="HealthCheckResult"/> representing a healthy component.</returns>
|
||||
public static HealthCheckResult Healthy()
|
||||
=> new HealthCheckResult(HealthCheckStatus.Healthy, exception: null, description: string.Empty, data: null);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a <see cref="HealthCheckResult"/> representing a healthy component.
|
||||
/// </summary>
|
||||
/// <returns>A <see cref="HealthCheckResult"/> representing a healthy component.</returns>
|
||||
/// <param name="description">A human-readable description of the status of the component that was checked.</param>
|
||||
public static HealthCheckResult Healthy(string description)
|
||||
=> new HealthCheckResult(HealthCheckStatus.Healthy, exception: null, description: description, data: null);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a <see cref="HealthCheckResult"/> representing a healthy component.
|
||||
/// </summary>
|
||||
/// <returns>A <see cref="HealthCheckResult"/> representing a healthy component.</returns>
|
||||
/// <param name="description">A human-readable description of the status of the component that was checked.</param>
|
||||
/// <param name="data">Additional key-value pairs describing the health of the component.</param>
|
||||
public static HealthCheckResult Healthy(string description, IReadOnlyDictionary<string, object> data)
|
||||
=> new HealthCheckResult(HealthCheckStatus.Healthy, exception: null, description: description, data: data);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a <see cref="HealthCheckResult"/> representing a component in a degraded state.
|
||||
/// </summary>
|
||||
/// <returns>A <see cref="HealthCheckResult"/> representing a component in a degraded state.</returns>
|
||||
public static HealthCheckResult Degraded()
|
||||
=> new HealthCheckResult(HealthCheckStatus.Degraded, exception: null, description: string.Empty, data: null);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a <see cref="HealthCheckResult"/> representing a component in a degraded state.
|
||||
/// </summary>
|
||||
/// <returns>A <see cref="HealthCheckResult"/> representing a component in a degraded state.</returns>
|
||||
/// <param name="description">A human-readable description of the status of the component that was checked.</param>
|
||||
public static HealthCheckResult Degraded(string description)
|
||||
=> new HealthCheckResult(HealthCheckStatus.Degraded, exception: null, description: description, data: null);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a <see cref="HealthCheckResult"/> representing a component in a degraded state.
|
||||
/// </summary>
|
||||
/// <returns>A <see cref="HealthCheckResult"/> representing a component in a degraded state.</returns>
|
||||
/// <param name="description">A human-readable description of the status of the component that was checked.</param>
|
||||
/// <param name="data">Additional key-value pairs describing the health of the component.</param>
|
||||
public static HealthCheckResult Degraded(string description, IReadOnlyDictionary<string, object> data)
|
||||
=> new HealthCheckResult(HealthCheckStatus.Degraded, exception: null, description: description, data: data);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a <see cref="HealthCheckResult"/> representing a component in a degraded state.
|
||||
/// </summary>
|
||||
/// <returns>A <see cref="HealthCheckResult"/> representing a component in a degraded state.</returns>
|
||||
public static HealthCheckResult Degraded(Exception exception)
|
||||
=> new HealthCheckResult(HealthCheckStatus.Degraded, exception: null, description: string.Empty, data: null);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a <see cref="HealthCheckResult"/> representing a component in a degraded state.
|
||||
/// </summary>
|
||||
/// <returns>A <see cref="HealthCheckResult"/> representing a component in a degraded state.</returns>
|
||||
/// <param name="description">A human-readable description of the status of the component that was checked.</param>
|
||||
/// <param name="exception">An <see cref="Exception"/> representing the exception that was thrown when checking for status (if any).</param>
|
||||
public static HealthCheckResult Degraded(string description, Exception exception)
|
||||
=> new HealthCheckResult(HealthCheckStatus.Degraded, exception, description, data: null);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a <see cref="HealthCheckResult"/> representing a component in a degraded state.
|
||||
/// </summary>
|
||||
/// <returns>A <see cref="HealthCheckResult"/> representing a component in a degraded state.</returns>
|
||||
/// <param name="description">A human-readable description of the status of the component that was checked.</param>
|
||||
/// <param name="exception">An <see cref="Exception"/> representing the exception that was thrown when checking for status (if any).</param>
|
||||
/// <param name="data">Additional key-value pairs describing the health of the component.</param>
|
||||
public static HealthCheckResult Degraded(string description, Exception exception, IReadOnlyDictionary<string, object> data)
|
||||
=> new HealthCheckResult(HealthCheckStatus.Degraded, exception, description, data);
|
||||
/// <param name="description">A human-readable description of the status of the component that was checked. Optional.</param>
|
||||
/// <param name="exception">An <see cref="Exception"/> representing the exception that was thrown when checking for status. Optional.</param>
|
||||
/// <param name="data">Additional key-value pairs describing the health of the component. Optional.</param>
|
||||
/// <returns>A <see cref="HealthCheckResult"/> representing an failing component.</returns>
|
||||
public static HealthCheckResult Failed(string description = null, Exception exception = null, IReadOnlyDictionary<string, object> data = null)
|
||||
{
|
||||
return new HealthCheckResult(result: false, description, exception, data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,38 +4,38 @@
|
|||
namespace Microsoft.Extensions.Diagnostics.HealthChecks
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the status of a health check result.
|
||||
/// Represents the reported status of a health check result.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The values of this enum or ordered from least healthy to most healthy. So <see cref="HealthCheckStatus.Degraded"/> is
|
||||
/// greater than <see cref="HealthCheckStatus.Unhealthy"/> but less than <see cref="HealthCheckStatus.Healthy"/>.
|
||||
/// <para>
|
||||
/// A health status is derived the pass/fail result of an <see cref="IHealthCheck"/> (<see cref="HealthCheckResult.Result"/>)
|
||||
/// and the corresponding value of <see cref="HealthCheckRegistration.FailureStatus"/>.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// The values of this enum or ordered from least healthy to most healthy. So <see cref="HealthStatus.Degraded"/> is
|
||||
/// greater than <see cref="HealthStatus.Unhealthy"/> but less than <see cref="HealthStatus.Healthy"/>.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public enum HealthCheckStatus
|
||||
public enum HealthStatus
|
||||
{
|
||||
/// <summary>
|
||||
/// This value should not be returned by a health check. It is used to represent an uninitialized value.
|
||||
/// Indicates that an unexpected exception was thrown when running the health check.
|
||||
/// </summary>
|
||||
Unknown = 0,
|
||||
|
||||
/// <summary>
|
||||
/// This value should not be returned by a health check. It is used to indicate that an unexpected exception was
|
||||
/// thrown when running the health check.
|
||||
/// </summary>
|
||||
Failed = 1,
|
||||
Failed = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Indicates that the health check determined that the component was unhealthy.
|
||||
/// </summary>
|
||||
Unhealthy = 2,
|
||||
Unhealthy = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Indicates that the health check determined that the component was in a degraded state.
|
||||
/// </summary>
|
||||
Degraded = 3,
|
||||
Degraded = 2,
|
||||
|
||||
/// <summary>
|
||||
/// Indicates that the health check determined that the component was healthy.
|
||||
/// </summary>
|
||||
Healthy = 4,
|
||||
Healthy = 3,
|
||||
}
|
||||
}
|
||||
|
|
@ -12,16 +12,12 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks
|
|||
/// </summary>
|
||||
public interface IHealthCheck
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the name of the health check, which should indicate the component being checked.
|
||||
/// </summary>
|
||||
string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Runs the health check, returning the status of the component being checked.
|
||||
/// </summary>
|
||||
/// <param name="context">A context object associated with the current execution.</param>
|
||||
/// <param name="cancellationToken">A <see cref="CancellationToken"/> that can be used to cancel the health check.</param>
|
||||
/// <returns>A <see cref="Task{HealthCheckResult}"/> that completes when the health check has finished, yielding the status of the component being checked.</returns>
|
||||
Task<HealthCheckResult> CheckHealthAsync(CancellationToken cancellationToken = default);
|
||||
Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,66 +0,0 @@
|
|||
// 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.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Microsoft.Extensions.Diagnostics.HealthChecks
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the results of multiple health checks.
|
||||
/// </summary>
|
||||
public class CompositeHealthCheckResult
|
||||
{
|
||||
/// <summary>
|
||||
/// A <see cref="IReadOnlyDictionary{TKey, T}"/> containing the results from each health check.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The keys in this dictionary map to the name of the health check, the values are the <see cref="HealthCheckResult"/>
|
||||
/// returned when <see cref="IHealthCheck.CheckHealthAsync(System.Threading.CancellationToken)"/> was called for that health check.
|
||||
/// </remarks>
|
||||
public IReadOnlyDictionary<string, HealthCheckResult> Results { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a <see cref="HealthCheckStatus"/> representing the aggregate status of all the health checks.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This value is determined by taking the "worst" result of all the results. So if any result is <see cref="HealthCheckStatus.Failed"/>,
|
||||
/// this value is <see cref="HealthCheckStatus.Failed"/>. If no result is <see cref="HealthCheckStatus.Failed"/> but any result is
|
||||
/// <see cref="HealthCheckStatus.Unhealthy"/>, this value is <see cref="HealthCheckStatus.Unhealthy"/>, etc.
|
||||
/// </remarks>
|
||||
public HealthCheckStatus Status { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="CompositeHealthCheckResult"/> from the specified results.
|
||||
/// </summary>
|
||||
/// <param name="results">A <see cref="IReadOnlyDictionary{TKey, T}"/> containing the results from each health check.</param>
|
||||
public CompositeHealthCheckResult(IReadOnlyDictionary<string, HealthCheckResult> results)
|
||||
{
|
||||
Results = results;
|
||||
Status = CalculateAggregateStatus(results.Values);
|
||||
}
|
||||
|
||||
private HealthCheckStatus CalculateAggregateStatus(IEnumerable<HealthCheckResult> results)
|
||||
{
|
||||
// This is basically a Min() check, but we know the possible range, so we don't need to walk the whole list
|
||||
var currentValue = HealthCheckStatus.Healthy;
|
||||
foreach (var result in results)
|
||||
{
|
||||
if (currentValue > result.Status)
|
||||
{
|
||||
currentValue = result.Status;
|
||||
}
|
||||
|
||||
if (currentValue == HealthCheckStatus.Failed)
|
||||
{
|
||||
// Game over, man! Game over!
|
||||
// (We hit the worst possible status, so there's no need to keep iterating)
|
||||
return currentValue;
|
||||
}
|
||||
}
|
||||
|
||||
return currentValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,149 @@
|
|||
// 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.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Internal;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Microsoft.Extensions.Diagnostics.HealthChecks
|
||||
{
|
||||
internal class DefaultHealthCheckService : HealthCheckService
|
||||
{
|
||||
private readonly IServiceScopeFactory _scopeFactory;
|
||||
private readonly IOptions<HealthCheckServiceOptions> _options;
|
||||
private readonly ILogger<DefaultHealthCheckService> _logger;
|
||||
|
||||
public DefaultHealthCheckService(
|
||||
IServiceScopeFactory scopeFactory,
|
||||
IOptions<HealthCheckServiceOptions> options,
|
||||
ILogger<DefaultHealthCheckService> logger)
|
||||
{
|
||||
_scopeFactory = scopeFactory ?? throw new ArgumentNullException(nameof(scopeFactory));
|
||||
_options = options ?? throw new ArgumentNullException(nameof(options));
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
|
||||
// We're specifically going out of our way to do this at startup time. We want to make sure you
|
||||
// get any kind of health-check related error as early as possible. Waiting until someone
|
||||
// actually tries to **run** health checks would be real baaaaad.
|
||||
ValidateRegistrations(_options.Value.Registrations);
|
||||
}
|
||||
public override async Task<HealthReport> CheckHealthAsync(
|
||||
Func<HealthCheckRegistration, bool> predicate,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var registrations = _options.Value.Registrations;
|
||||
|
||||
using (var scope = _scopeFactory.CreateScope())
|
||||
{
|
||||
var context = new HealthCheckContext();
|
||||
var entries = new Dictionary<string, HealthReportEntry>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
foreach (var registration in registrations)
|
||||
{
|
||||
if (predicate != null && !predicate(registration))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
var healthCheck = registration.Factory(scope.ServiceProvider);
|
||||
|
||||
// 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(registration.Name)))
|
||||
{
|
||||
var stopwatch = ValueStopwatch.StartNew();
|
||||
context.Registration = registration;
|
||||
|
||||
Log.HealthCheckBegin(_logger, registration);
|
||||
|
||||
HealthReportEntry entry;
|
||||
try
|
||||
{
|
||||
var result = await healthCheck.CheckHealthAsync(context, cancellationToken);
|
||||
|
||||
entry = new HealthReportEntry(
|
||||
result.Result ? HealthStatus.Healthy : registration.FailureStatus,
|
||||
result.Description,
|
||||
result.Exception,
|
||||
result.Data);
|
||||
|
||||
Log.HealthCheckEnd(_logger, registration, entry, stopwatch.GetElapsedTime());
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
entry = new HealthReportEntry(HealthStatus.Failed, ex.Message, ex, data: null);
|
||||
Log.HealthCheckError(_logger, registration, ex, stopwatch.GetElapsedTime());
|
||||
}
|
||||
|
||||
entries[registration.Name] = entry;
|
||||
}
|
||||
}
|
||||
|
||||
return new HealthReport(entries);
|
||||
}
|
||||
}
|
||||
|
||||
private static void ValidateRegistrations(IEnumerable<HealthCheckRegistration> registrations)
|
||||
{
|
||||
// Scan the list for duplicate names to provide a better error if there are duplicates.
|
||||
var duplicateNames = registrations
|
||||
.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(registrations));
|
||||
}
|
||||
}
|
||||
|
||||
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, HealthStatus, Exception> _healthCheckEnd = LoggerMessage.Define<string, double, HealthStatus>(
|
||||
LogLevel.Debug,
|
||||
EventIds.HealthCheckEnd,
|
||||
"Health check {HealthCheckName} completed after {ElapsedMilliseconds}ms with status {HealthCheckStatus}");
|
||||
|
||||
private static readonly Action<ILogger, string, double, Exception> _healthCheckError = LoggerMessage.Define<string, double>(
|
||||
LogLevel.Error,
|
||||
EventIds.HealthCheckError,
|
||||
"Health check {HealthCheckName} threw an unhandled exception after {ElapsedMilliseconds}ms");
|
||||
|
||||
public static void HealthCheckBegin(ILogger logger, HealthCheckRegistration registration)
|
||||
{
|
||||
_healthCheckBegin(logger, registration.Name, null);
|
||||
}
|
||||
|
||||
public static void HealthCheckEnd(ILogger logger, HealthCheckRegistration registration, HealthReportEntry entry, TimeSpan duration)
|
||||
{
|
||||
_healthCheckEnd(logger, registration.Name, duration.TotalMilliseconds, entry.Status, null);
|
||||
}
|
||||
|
||||
public static void HealthCheckError(ILogger logger, HealthCheckRegistration registration, Exception exception, TimeSpan duration)
|
||||
{
|
||||
_healthCheckError(logger, registration.Name, duration.TotalMilliseconds, exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -11,31 +11,25 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks
|
|||
/// A simple implementation of <see cref="IHealthCheck"/> which uses a provided delegate to
|
||||
/// implement the check.
|
||||
/// </summary>
|
||||
public sealed class HealthCheck : IHealthCheck
|
||||
internal sealed class DelegateHealthCheck : IHealthCheck
|
||||
{
|
||||
private readonly Func<CancellationToken, Task<HealthCheckResult>> _check;
|
||||
|
||||
/// <summary>
|
||||
/// Create an instance of <see cref="HealthCheck"/> from the specified <paramref name="name"/> and <paramref name="check"/>.
|
||||
/// Create an instance of <see cref="DelegateHealthCheck"/> from the specified delegate.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the health check, which should indicate the component being checked.</param>
|
||||
/// <param name="check">A delegate which provides the code to execute when the health check is run.</param>
|
||||
public HealthCheck(string name, Func<CancellationToken, Task<HealthCheckResult>> check)
|
||||
public DelegateHealthCheck(Func<CancellationToken, Task<HealthCheckResult>> check)
|
||||
{
|
||||
Name = name ?? throw new ArgumentNullException(nameof(name));
|
||||
_check = check ?? throw new ArgumentNullException(nameof(check));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the health check, which should indicate the component being checked.
|
||||
/// </summary>
|
||||
public string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Runs the health check, returning the status of the component being checked.
|
||||
/// </summary>
|
||||
/// <param name="context">A context object associated with the current execution.</param>
|
||||
/// <param name="cancellationToken">A <see cref="CancellationToken"/> that can be used to cancel the health check.</param>
|
||||
/// <returns>A <see cref="Task{HealthCheckResult}"/> that completes when the health check has finished, yielding the status of the component being checked.</returns>
|
||||
public Task<HealthCheckResult> CheckHealthAsync(CancellationToken cancellationToken = default) => _check(cancellationToken);
|
||||
public Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default) => _check(cancellationToken);
|
||||
}
|
||||
}
|
||||
|
|
@ -7,24 +7,24 @@ using Microsoft.Extensions.Diagnostics.HealthChecks;
|
|||
namespace Microsoft.Extensions.DependencyInjection
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides extension methods for registering <see cref="IHealthCheckService"/> in an <see cref="IServiceCollection"/>.
|
||||
/// Provides extension methods for registering <see cref="HealthCheckService"/> in an <see cref="IServiceCollection"/>.
|
||||
/// </summary>
|
||||
public static class HealthCheckServiceCollectionExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds the <see cref="IHealthCheckService"/> to the container, using the provided delegate to register
|
||||
/// Adds the <see cref="HealthCheckService"/> to the container, using the provided delegate to register
|
||||
/// health checks.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This operation is idempotent - multiple invocations will still only result in a single
|
||||
/// <see cref="IHealthCheckService"/> instance in the <see cref="IServiceCollection"/>. It can be invoked
|
||||
/// <see cref="HealthCheckService"/> instance in the <see cref="IServiceCollection"/>. It can be invoked
|
||||
/// multiple times in order to get access to the <see cref="IHealthChecksBuilder"/> in multiple places.
|
||||
/// </remarks>
|
||||
/// <param name="services">The <see cref="IServiceCollection"/> to add the <see cref="IHealthCheckService"/> to.</param>
|
||||
/// <param name="services">The <see cref="IServiceCollection"/> to add the <see cref="HealthCheckService"/> to.</param>
|
||||
/// <returns>An instance of <see cref="IHealthChecksBuilder"/> from which health checks can be registered.</returns>
|
||||
public static IHealthChecksBuilder AddHealthChecks(this IServiceCollection services)
|
||||
{
|
||||
services.TryAdd(ServiceDescriptor.Singleton<IHealthCheckService, HealthCheckService>());
|
||||
services.TryAdd(ServiceDescriptor.Singleton<HealthCheckService, DefaultHealthCheckService>());
|
||||
return new HealthChecksBuilder(services);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
// 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.
|
||||
|
||||
using System;
|
||||
using Microsoft.Extensions.Diagnostics.HealthChecks;
|
||||
|
||||
namespace Microsoft.Extensions.DependencyInjection
|
||||
{
|
||||
internal class HealthChecksBuilder : IHealthChecksBuilder
|
||||
{
|
||||
public HealthChecksBuilder(IServiceCollection services)
|
||||
{
|
||||
Services = services;
|
||||
}
|
||||
|
||||
public IServiceCollection Services { get; }
|
||||
|
||||
public IHealthChecksBuilder Add(HealthCheckRegistration registration)
|
||||
{
|
||||
if (registration == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(registration));
|
||||
}
|
||||
|
||||
Services.Configure<HealthCheckServiceOptions>(options =>
|
||||
{
|
||||
options.Registrations.Add(registration);
|
||||
});
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,191 @@
|
|||
// 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.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.Extensions.Diagnostics.HealthChecks;
|
||||
|
||||
namespace Microsoft.Extensions.DependencyInjection
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides basic extension methods for registering <see cref="IHealthCheck"/> instances in an <see cref="IHealthChecksBuilder"/>.
|
||||
/// </summary>
|
||||
public static class HealthChecksBuilderAddCheckExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds a new health check with the specified name and implementation.
|
||||
/// </summary>
|
||||
/// <param name="builder">The <see cref="IHealthChecksBuilder"/>.</param>
|
||||
/// <param name="name">The name of the health check.</param>
|
||||
/// <param name="instance">An <see cref="IHealthCheck"/> instance.</param>
|
||||
/// <param name="failureStatus">
|
||||
/// The <see cref="HealthStatus"/> that should be reported when the health check reports a failure. If the provided value
|
||||
/// is <c>null</c>, then <see cref="HealthStatus.Unhealthy"/> will be reported.
|
||||
/// </param>
|
||||
/// <param name="tags">A list of tags that can be used to filter health checks.</param>
|
||||
/// <returns>The <see cref="IHealthChecksBuilder"/>.</returns>
|
||||
public static IHealthChecksBuilder AddCheck(
|
||||
this IHealthChecksBuilder builder,
|
||||
string name,
|
||||
IHealthCheck instance,
|
||||
HealthStatus? failureStatus = null,
|
||||
IEnumerable<string> tags = null)
|
||||
{
|
||||
if (builder == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(builder));
|
||||
}
|
||||
|
||||
if (name == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(name));
|
||||
}
|
||||
|
||||
if (instance == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(instance));
|
||||
}
|
||||
|
||||
return builder.Add(new HealthCheckRegistration(name, instance, failureStatus, tags));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a new health check with the specified name and implementation.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The health check implementation type.</typeparam>
|
||||
/// <param name="builder">The <see cref="IHealthChecksBuilder"/>.</param>
|
||||
/// <param name="name">The name of the health check.</param>
|
||||
/// <param name="failureStatus">
|
||||
/// The <see cref="HealthStatus"/> that should be reported when the health check reports a failure. If the provided value
|
||||
/// is <c>null</c>, then <see cref="HealthStatus.Unhealthy"/> will be reported.
|
||||
/// </param>
|
||||
/// <param name="tags">A list of tags that can be used to filter health checks.</param>
|
||||
/// <returns>The <see cref="IHealthChecksBuilder"/>.</returns>
|
||||
/// <remarks>
|
||||
/// This method will use <see cref="ActivatorUtilities.GetServiceOrCreateInstance{T}(IServiceProvider)"/> to create the health check
|
||||
/// instance when needed. If a service of type <typeparamref name="T"/> is registred in the dependency injection container
|
||||
/// with any liftime it will be used. Otherwise an instance of type <typeparamref name="T"/> will be constructed with
|
||||
/// access to services from the dependency injection container.
|
||||
/// </remarks>
|
||||
public static IHealthChecksBuilder AddCheck<T>(
|
||||
this IHealthChecksBuilder builder,
|
||||
string name,
|
||||
HealthStatus? failureStatus = null,
|
||||
IEnumerable<string> tags = null) where T : class, IHealthCheck
|
||||
{
|
||||
if (builder == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(builder));
|
||||
}
|
||||
|
||||
if (name == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(name));
|
||||
}
|
||||
|
||||
return builder.Add(new HealthCheckRegistration(name, s => ActivatorUtilities.GetServiceOrCreateInstance<T>(s), failureStatus, tags));
|
||||
}
|
||||
|
||||
// NOTE: AddTypeActivatedCheck has overloads rather than default parameters values, because default parameter values don't
|
||||
// play super well with params.
|
||||
|
||||
/// <summary>
|
||||
/// Adds a new type activated health check with the specified name and implementation.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The health check implementation type.</typeparam>
|
||||
/// <param name="builder">The <see cref="IHealthChecksBuilder"/>.</param>
|
||||
/// <param name="name">The name of the health check.</param>
|
||||
/// <param name="args">Additional arguments to provide to the constructor.</param>
|
||||
/// <returns>The <see cref="IHealthChecksBuilder"/>.</returns>
|
||||
/// <remarks>
|
||||
/// This method will use <see cref="ActivatorUtilities.CreateInstance{T}(IServiceProvider, object[])"/> to create the health check
|
||||
/// instance when needed. Additional arguments can be provided to the constructor via <paramref name="args"/>.
|
||||
/// </remarks>
|
||||
public static IHealthChecksBuilder AddTypeActivatedCheck<T>(this IHealthChecksBuilder builder, string name, params object[] args) where T : class, IHealthCheck
|
||||
{
|
||||
if (builder == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(builder));
|
||||
}
|
||||
|
||||
if (name == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(name));
|
||||
}
|
||||
|
||||
return AddTypeActivatedCheck<T>(builder, name, failureStatus: null, tags: null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a new type activated health check with the specified name and implementation.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The health check implementation type.</typeparam>
|
||||
/// <param name="builder">The <see cref="IHealthChecksBuilder"/>.</param>
|
||||
/// <param name="name">The name of the health check.</param>
|
||||
/// <param name="failureStatus">
|
||||
/// The <see cref="HealthStatus"/> that should be reported when the health check reports a failure. If the provided value
|
||||
/// is <c>null</c>, then <see cref="HealthStatus.Unhealthy"/> will be reported.
|
||||
/// </param>
|
||||
/// <param name="args">Additional arguments to provide to the constructor.</param>
|
||||
/// <returns>The <see cref="IHealthChecksBuilder"/>.</returns>
|
||||
/// <remarks>
|
||||
/// This method will use <see cref="ActivatorUtilities.CreateInstance{T}(IServiceProvider, object[])"/> to create the health check
|
||||
/// instance when needed. Additional arguments can be provided to the constructor via <paramref name="args"/>.
|
||||
/// </remarks>
|
||||
public static IHealthChecksBuilder AddTypeActivatedCheck<T>(
|
||||
this IHealthChecksBuilder builder,
|
||||
string name,
|
||||
HealthStatus? failureStatus,
|
||||
params object[] args) where T : class, IHealthCheck
|
||||
{
|
||||
if (builder == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(builder));
|
||||
}
|
||||
|
||||
if (name == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(name));
|
||||
}
|
||||
|
||||
return AddTypeActivatedCheck<T>(builder, name, failureStatus, tags: null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a new type activated health check with the specified name and implementation.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The health check implementation type.</typeparam>
|
||||
/// <param name="builder">The <see cref="IHealthChecksBuilder"/>.</param>
|
||||
/// <param name="name">The name of the health check.</param>
|
||||
/// <param name="failureStatus">
|
||||
/// The <see cref="HealthStatus"/> that should be reported when the health check reports a failure. If the provided value
|
||||
/// is <c>null</c>, then <see cref="HealthStatus.Unhealthy"/> will be reported.
|
||||
/// </param>
|
||||
/// <param name="tags">A list of tags that can be used to filter health checks.</param>
|
||||
/// <param name="args">Additional arguments to provide to the constructor.</param>
|
||||
/// <returns>The <see cref="IHealthChecksBuilder"/>.</returns>
|
||||
/// <remarks>
|
||||
/// This method will use <see cref="ActivatorUtilities.CreateInstance{T}(IServiceProvider, object[])"/> to create the health check
|
||||
/// instance when needed. Additional arguments can be provided to the constructor via <paramref name="args"/>.
|
||||
/// </remarks>
|
||||
public static IHealthChecksBuilder AddTypeActivatedCheck<T>(
|
||||
this IHealthChecksBuilder builder,
|
||||
string name,
|
||||
HealthStatus? failureStatus,
|
||||
IEnumerable<string> tags,
|
||||
params object[] args) where T : class, IHealthCheck
|
||||
{
|
||||
if (builder == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(builder));
|
||||
}
|
||||
|
||||
if (name == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(name));
|
||||
}
|
||||
|
||||
return builder.Add(new HealthCheckRegistration(name, s => ActivatorUtilities.CreateInstance<T>(s, args), failureStatus, tags));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,169 @@
|
|||
// 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.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Diagnostics.HealthChecks;
|
||||
|
||||
namespace Microsoft.Extensions.DependencyInjection
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides extension methods for registering delegates with the <see cref="IHealthChecksBuilder"/>.
|
||||
/// </summary>
|
||||
public static class HealthChecksBuilderDelegateExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds a new health check with the specified name and implementation.
|
||||
/// </summary>
|
||||
/// <param name="builder">The <see cref="IHealthChecksBuilder"/>.</param>
|
||||
/// <param name="name">The name of the health check.</param>
|
||||
/// <param name="failureStatus">
|
||||
/// The <see cref="HealthStatus"/> that should be reported when the health check reports a failure. If the provided value
|
||||
/// is <c>null</c>, then <see cref="HealthStatus.Unhealthy"/> will be reported.
|
||||
/// </param>
|
||||
/// <param name="tags">A list of tags that can be used to filter health checks.</param>
|
||||
/// <param name="check">A delegate that provides the health check implementation.</param>
|
||||
/// <returns>The <see cref="IHealthChecksBuilder"/>.</returns>
|
||||
public static IHealthChecksBuilder AddCheck(
|
||||
this IHealthChecksBuilder builder,
|
||||
string name,
|
||||
Func<HealthCheckResult> check,
|
||||
HealthStatus? failureStatus = null,
|
||||
IEnumerable<string> tags = null)
|
||||
{
|
||||
if (builder == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(builder));
|
||||
}
|
||||
|
||||
if (name == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(name));
|
||||
}
|
||||
|
||||
if (check == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(check));
|
||||
}
|
||||
|
||||
var instance = new DelegateHealthCheck((ct) => Task.FromResult(check()));
|
||||
return builder.Add(new HealthCheckRegistration(name, instance, failureStatus, tags));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a new health check with the specified name and implementation.
|
||||
/// </summary>
|
||||
/// <param name="builder">The <see cref="IHealthChecksBuilder"/>.</param>
|
||||
/// <param name="name">The name of the health check.</param>
|
||||
/// <param name="failureStatus">
|
||||
/// The <see cref="HealthStatus"/> that should be reported when the health check reports a failure. If the provided value
|
||||
/// is <c>null</c>, then <see cref="HealthStatus.Unhealthy"/> will be reported.
|
||||
/// </param>
|
||||
/// <param name="tags">A list of tags that can be used to filter health checks.</param>
|
||||
/// <param name="check">A delegate that provides the health check implementation.</param>
|
||||
/// <returns>The <see cref="IHealthChecksBuilder"/>.</returns>
|
||||
public static IHealthChecksBuilder AddCheck(
|
||||
this IHealthChecksBuilder builder,
|
||||
string name,
|
||||
Func<CancellationToken, HealthCheckResult> check,
|
||||
HealthStatus? failureStatus = null,
|
||||
IEnumerable<string> tags = null)
|
||||
{
|
||||
if (builder == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(builder));
|
||||
}
|
||||
|
||||
if (name == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(name));
|
||||
}
|
||||
|
||||
if (check == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(check));
|
||||
}
|
||||
|
||||
var instance = new DelegateHealthCheck((ct) => Task.FromResult(check(ct)));
|
||||
return builder.Add(new HealthCheckRegistration(name, instance, failureStatus, tags));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a new health check with the specified name and implementation.
|
||||
/// </summary>
|
||||
/// <param name="builder">The <see cref="IHealthChecksBuilder"/>.</param>
|
||||
/// <param name="name">The name of the health check.</param>
|
||||
/// <param name="failureStatus">
|
||||
/// The <see cref="HealthStatus"/> that should be reported when the health check reports a failure. If the provided value
|
||||
/// is <c>null</c>, then <see cref="HealthStatus.Unhealthy"/> will be reported.
|
||||
/// </param>
|
||||
/// <param name="tags">A list of tags that can be used to filter health checks.</param>
|
||||
/// <param name="check">A delegate that provides the health check implementation.</param>
|
||||
/// <returns>The <see cref="IHealthChecksBuilder"/>.</returns>
|
||||
public static IHealthChecksBuilder AddAsyncCheck(
|
||||
this IHealthChecksBuilder builder,
|
||||
string name,
|
||||
Func<Task<HealthCheckResult>> check,
|
||||
HealthStatus? failureStatus = null,
|
||||
IEnumerable<string> tags = null)
|
||||
{
|
||||
if (builder == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(builder));
|
||||
}
|
||||
|
||||
if (name == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(name));
|
||||
}
|
||||
|
||||
if (check == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(check));
|
||||
}
|
||||
|
||||
var instance = new DelegateHealthCheck((ct) => check());
|
||||
return builder.Add(new HealthCheckRegistration(name, instance, failureStatus, tags));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a new health check with the specified name and implementation.
|
||||
/// </summary>
|
||||
/// <param name="builder">The <see cref="IHealthChecksBuilder"/>.</param>
|
||||
/// <param name="name">The name of the health check.</param>
|
||||
/// <param name="failureStatus">
|
||||
/// The <see cref="HealthStatus"/> that should be reported when the health check reports a failure. If the provided value
|
||||
/// is <c>null</c>, then <see cref="HealthStatus.Unhealthy"/> will be reported.
|
||||
/// </param>
|
||||
/// <param name="tags">A list of tags that can be used to filter health checks.</param>
|
||||
/// <param name="check">A delegate that provides the health check implementation.</param>
|
||||
/// <returns>The <see cref="IHealthChecksBuilder"/>.</returns>
|
||||
public static IHealthChecksBuilder AddAsyncCheck(
|
||||
this IHealthChecksBuilder builder,
|
||||
string name,
|
||||
Func<CancellationToken, Task<HealthCheckResult>> check,
|
||||
HealthStatus? failureStatus = null,
|
||||
IEnumerable<string> tags = null)
|
||||
{
|
||||
if (builder == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(builder));
|
||||
}
|
||||
|
||||
if (name == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(name));
|
||||
}
|
||||
|
||||
if (check == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(check));
|
||||
}
|
||||
|
||||
var instance = new DelegateHealthCheck((ct) => check(ct));
|
||||
return builder.Add(new HealthCheckRegistration(name, instance, failureStatus, tags));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
// 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.
|
||||
|
||||
using Microsoft.Extensions.Diagnostics.HealthChecks;
|
||||
|
||||
namespace Microsoft.Extensions.DependencyInjection
|
||||
{
|
||||
/// <summary>
|
||||
/// A builder used to register health checks.
|
||||
/// </summary>
|
||||
public interface IHealthChecksBuilder
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds a <see cref="HealthCheckRegistration"/> for a health check.
|
||||
/// </summary>
|
||||
/// <param name="registration">The <see cref="HealthCheckRegistration"/>.</param>
|
||||
IHealthChecksBuilder Add(HealthCheckRegistration registration);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="IServiceCollection"/> into which <see cref="IHealthCheck"/> instances should be registered.
|
||||
/// </summary>
|
||||
IServiceCollection Services { get; }
|
||||
}
|
||||
}
|
||||
|
|
@ -2,143 +2,60 @@
|
|||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Internal;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Microsoft.Extensions.Diagnostics.HealthChecks
|
||||
{
|
||||
internal class HealthCheckService : IHealthCheckService
|
||||
/// <summary>
|
||||
/// A service which can be used to check the status of <see cref="IHealthCheck"/> instances
|
||||
/// registered in the application.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// The default implementation of <see cref="HealthCheckService"/> is registered in the dependency
|
||||
/// injection container as a singleton service by calling
|
||||
/// <see cref="HealthCheckServiceCollectionExtensions.AddHealthChecks(IServiceCollection)"/>.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// The <see cref="IHealthChecksBuilder"/> returned by
|
||||
/// <see cref="HealthCheckServiceCollectionExtensions.AddHealthChecks(IServiceCollection)"/>
|
||||
/// provides a convenience API for registering health checks.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// <see cref="IHealthCheck"/> implementations can be registered through extension methods provided by
|
||||
/// <see cref="IHealthChecksBuilder"/>.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public abstract class HealthCheckService
|
||||
{
|
||||
private readonly IServiceScopeFactory _scopeFactory;
|
||||
private readonly ILogger<HealthCheckService> _logger;
|
||||
|
||||
public HealthCheckService(IServiceScopeFactory scopeFactory, ILogger<HealthCheckService> logger)
|
||||
/// <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="HealthReport"/> containing the results.
|
||||
/// </returns>
|
||||
public Task<HealthReport> CheckHealthAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
_scopeFactory = scopeFactory ?? throw new ArgumentNullException(nameof(scopeFactory));
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
|
||||
// We're specifically going out of our way to do this at startup time. We want to make sure you
|
||||
// get any kind of health-check related error as early as possible. Waiting until someone
|
||||
// actually tries to **run** health checks would be real baaaaad.
|
||||
using (var scope = _scopeFactory.CreateScope())
|
||||
{
|
||||
var healthChecks = scope.ServiceProvider.GetRequiredService<IEnumerable<IHealthCheck>>();
|
||||
EnsureNoDuplicates(healthChecks);
|
||||
}
|
||||
return CheckHealthAsync(predicate: null, cancellationToken);
|
||||
}
|
||||
|
||||
public Task<CompositeHealthCheckResult> CheckHealthAsync(CancellationToken cancellationToken = default) =>
|
||||
CheckHealthAsync(predicate: null, cancellationToken);
|
||||
|
||||
public async Task<CompositeHealthCheckResult> CheckHealthAsync(
|
||||
Func<IHealthCheck, bool> predicate,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
using (var scope = _scopeFactory.CreateScope())
|
||||
{
|
||||
var healthChecks = scope.ServiceProvider.GetRequiredService<IEnumerable<IHealthCheck>>();
|
||||
|
||||
var results = new Dictionary<string, HealthCheckResult>(StringComparer.OrdinalIgnoreCase);
|
||||
foreach (var healthCheck in healthChecks)
|
||||
{
|
||||
if (predicate != null && !predicate(healthCheck))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Runs the provided health checks and returns the aggregated status
|
||||
/// </summary>
|
||||
/// <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>
|
||||
/// <returns>
|
||||
/// A <see cref="Task{T}"/> which will complete when all the health checks have been run,
|
||||
/// yielding a <see cref="HealthReport"/> containing the results.
|
||||
/// </returns>
|
||||
public abstract Task<HealthReport> CheckHealthAsync(
|
||||
Func<HealthCheckRegistration, bool> predicate,
|
||||
CancellationToken cancellationToken = default);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,18 @@
|
|||
// 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.
|
||||
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Microsoft.Extensions.Diagnostics.HealthChecks
|
||||
{
|
||||
/// <summary>
|
||||
/// Options for the default implementation of <see cref="HealthCheckService"/>
|
||||
/// </summary>
|
||||
public sealed class HealthCheckServiceOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the health check registrations.
|
||||
/// </summary>
|
||||
public ICollection<HealthCheckRegistration> Registrations { get; } = new List<HealthCheckRegistration>();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
// 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.
|
||||
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Microsoft.Extensions.Diagnostics.HealthChecks
|
||||
{
|
||||
internal class HealthChecksBuilder : IHealthChecksBuilder
|
||||
{
|
||||
public IServiceCollection Services { get; }
|
||||
|
||||
public HealthChecksBuilder(IServiceCollection services)
|
||||
{
|
||||
Services = services;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,114 +0,0 @@
|
|||
// 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.
|
||||
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Diagnostics.HealthChecks;
|
||||
|
||||
namespace Microsoft.Extensions.DependencyInjection
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides extension methods for registering <see cref="IHealthCheck"/> instances in an <see cref="IHealthChecksBuilder"/>.
|
||||
/// </summary>
|
||||
public static class HealthChecksBuilderAddCheckExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds a new health check with the specified name and implementation.
|
||||
/// </summary>
|
||||
/// <param name="builder">The <see cref="IHealthChecksBuilder"/> to add the check to.</param>
|
||||
/// <param name="name">The name of the health check, which should indicate the component being checked.</param>
|
||||
/// <param name="check">A delegate which provides the code to execute when the health check is run.</param>
|
||||
/// <returns>The <see cref="IHealthChecksBuilder"/>.</returns>
|
||||
public static IHealthChecksBuilder AddCheck(this IHealthChecksBuilder builder, string name, Func<CancellationToken, Task<HealthCheckResult>> check)
|
||||
{
|
||||
if (builder == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(builder));
|
||||
}
|
||||
|
||||
if (name == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(name));
|
||||
}
|
||||
|
||||
if (check == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(check));
|
||||
}
|
||||
|
||||
return builder.AddCheck(new HealthCheck(name, check));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a new health check with the specified name and implementation.
|
||||
/// </summary>
|
||||
/// <param name="builder">The <see cref="IHealthChecksBuilder"/> to add the check to.</param>
|
||||
/// <param name="name">The name of the health check, which should indicate the component being checked.</param>
|
||||
/// <param name="check">A delegate which provides the code to execute when the health check is run.</param>
|
||||
/// <returns>The <see cref="IHealthChecksBuilder"/>.</returns>
|
||||
public static IHealthChecksBuilder AddCheck(this IHealthChecksBuilder builder, string name, Func<Task<HealthCheckResult>> check)
|
||||
{
|
||||
if (builder == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(builder));
|
||||
}
|
||||
|
||||
if (name == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(name));
|
||||
}
|
||||
|
||||
if (check == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(check));
|
||||
}
|
||||
|
||||
return builder.AddCheck(name, _ => check());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a new health check with the provided implementation.
|
||||
/// </summary>
|
||||
/// <param name="builder">The <see cref="IHealthChecksBuilder"/> to add the check to.</param>
|
||||
/// <param name="check">An <see cref="IHealthCheck"/> implementation.</param>
|
||||
/// <returns>The <see cref="IHealthChecksBuilder"/>.</returns>
|
||||
public static IHealthChecksBuilder AddCheck(this IHealthChecksBuilder builder, IHealthCheck check)
|
||||
{
|
||||
if (builder == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(builder));
|
||||
}
|
||||
|
||||
if (check == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(check));
|
||||
}
|
||||
|
||||
builder.Services.AddSingleton<IHealthCheck>(check);
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
// 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.
|
||||
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Microsoft.Extensions.Diagnostics.HealthChecks
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the result of executing a group of <see cref="IHealthCheck"/> instances.
|
||||
/// </summary>
|
||||
public sealed class HealthReport
|
||||
{
|
||||
/// <summary>
|
||||
/// Create a new <see cref="HealthReport"/> from the specified results.
|
||||
/// </summary>
|
||||
/// <param name="entries">A <see cref="IReadOnlyDictionary{TKey, T}"/> containing the results from each health check.</param>
|
||||
public HealthReport(IReadOnlyDictionary<string, HealthReportEntry> entries)
|
||||
{
|
||||
Entries = entries;
|
||||
Status = CalculateAggregateStatus(entries.Values);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A <see cref="IReadOnlyDictionary{TKey, T}"/> containing the results from each health check.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The keys in this dictionary map the name of each executed health check to a <see cref="HealthReportEntry"/> for the
|
||||
/// result data retruned from the corresponding health check.
|
||||
/// </remarks>
|
||||
public IReadOnlyDictionary<string, HealthReportEntry> Entries { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a <see cref="HealthStatus"/> representing the aggregate status of all the health checks. The value of <see cref="Status"/>
|
||||
/// will be the most servere status reported by a health check. If no checks were executed, the value is always <see cref="HealthStatus.Healthy"/>.
|
||||
/// </summary>
|
||||
public HealthStatus Status { get; }
|
||||
|
||||
private HealthStatus CalculateAggregateStatus(IEnumerable<HealthReportEntry> entries)
|
||||
{
|
||||
// This is basically a Min() check, but we know the possible range, so we don't need to walk the whole list
|
||||
var currentValue = HealthStatus.Healthy;
|
||||
foreach (var entry in entries)
|
||||
{
|
||||
if (currentValue > entry.Status)
|
||||
{
|
||||
currentValue = entry.Status;
|
||||
}
|
||||
|
||||
if (currentValue == HealthStatus.Failed)
|
||||
{
|
||||
// Game over, man! Game over!
|
||||
// (We hit the worst possible status, so there's no need to keep iterating)
|
||||
return currentValue;
|
||||
}
|
||||
}
|
||||
|
||||
return currentValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
// 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.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Microsoft.Extensions.Diagnostics.HealthChecks
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents an entry in a <see cref="HealthReport"/>. Corresponds to the result of a single <see cref="IHealthCheck"/>.
|
||||
/// </summary>
|
||||
public struct HealthReportEntry
|
||||
{
|
||||
private static readonly IReadOnlyDictionary<string, object> _emptyReadOnlyDictionary = new Dictionary<string, object>();
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="HealthReportEntry"/> with the specified values for <paramref name="status"/>, <paramref name="exception"/>,
|
||||
/// <paramref name="description"/>, and <paramref name="data"/>.
|
||||
/// </summary>
|
||||
/// <param name="status">A value indicating the health status of the component that was checked.</param>
|
||||
/// <param name="description">A human-readable description of the status of the component that was checked.</param>
|
||||
/// <param name="exception">An <see cref="Exception"/> representing the exception that was thrown when checking for status (if any).</param>
|
||||
/// <param name="data">Additional key-value pairs describing the health of the component.</param>
|
||||
public HealthReportEntry(HealthStatus status, string description, Exception exception, IReadOnlyDictionary<string, object> data)
|
||||
{
|
||||
Status = status;
|
||||
Description = description;
|
||||
Exception = exception;
|
||||
Data = data ?? _emptyReadOnlyDictionary;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets additional key-value pairs describing the health of the component.
|
||||
/// </summary>
|
||||
public IReadOnlyDictionary<string, object> Data { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a human-readable description of the status of the component that was checked.
|
||||
/// </summary>
|
||||
public string Description { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets an <see cref="System.Exception"/> representing the exception that was thrown when checking for status (if any).
|
||||
/// </summary>
|
||||
public Exception Exception { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the health status of the component that was checked. The <see cref="Status"/> is based on the pass/fail value of
|
||||
/// <see cref="HealthCheckResult.Passed"/> and the configured value of <see cref="HealthCheckRegistration.FailureStatus"/>.
|
||||
/// </summary>
|
||||
public HealthStatus Status { get; }
|
||||
}
|
||||
}
|
||||
|
|
@ -1,60 +0,0 @@
|
|||
// 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.
|
||||
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.Extensions.Diagnostics.HealthChecks
|
||||
{
|
||||
/// <summary>
|
||||
/// A service which can be used to check the status of <see cref="IHealthCheck"/> instances
|
||||
/// registered in the application.
|
||||
/// </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
|
||||
{
|
||||
/// <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>
|
||||
Task<CompositeHealthCheckResult> CheckHealthAsync(CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Runs the provided health checks and returns the aggregated status
|
||||
/// </summary>
|
||||
/// <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>
|
||||
/// <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>
|
||||
Task<CompositeHealthCheckResult> CheckHealthAsync(Func<IHealthCheck, bool> predicate,
|
||||
CancellationToken cancellationToken = default);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
// 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.
|
||||
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Microsoft.Extensions.Diagnostics.HealthChecks
|
||||
{
|
||||
/// <summary>
|
||||
/// A builder used to collect instances of <see cref="IHealthCheck"/> and register them on an <see cref="IServiceCollection"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This type wraps an <see cref="IServiceCollection"/> and provides a place for health check components to attach extension
|
||||
/// methods for registering themselves in the <see cref="IServiceCollection"/>.
|
||||
/// </remarks>
|
||||
public interface IHealthChecksBuilder
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the <see cref="IServiceCollection"/> into which <see cref="IHealthCheck"/> instances should be registered.
|
||||
/// </summary>
|
||||
IServiceCollection Services { get; }
|
||||
}
|
||||
}
|
||||
|
|
@ -3,8 +3,8 @@
|
|||
<Description>Components for performing health checks in .NET applications
|
||||
|
||||
Commonly Used Types:
|
||||
Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheckService
|
||||
Microsoft.Extensions.Diagnostics.HealthChecks.IHealthChecksBuilder
|
||||
Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckService
|
||||
Microsoft.Extensions.Diagnostics.HealthChecks.IHealthChecksBuilder
|
||||
</Description>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<NoWarn>$(NoWarn);CS1591</NoWarn>
|
||||
|
|
@ -14,6 +14,7 @@ Microsoft.Extensions.Diagnostics.HealthChecks.IHealthChecksBuilder
|
|||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="$(MicrosoftExtensionsDependencyInjectionAbstractionsPackageVersion)" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="$(MicrosoftExtensionsLoggingAbstractionsPackageVersion)" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options" Version="$(MicrosoftExtensionsOptionsPackageVersion)" />
|
||||
<PackageReference Include="Microsoft.Extensions.ValueStopwatch.Sources" Version="$(MicrosoftExtensionsValueStopwatchSourcesPackageVersion)" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
// 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.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
|
|
@ -100,7 +98,6 @@ namespace Microsoft.AspNetCore.Diagnostics.HealthChecks
|
|||
Assert.Equal("Healthy", await response.Content.ReadAsStringAsync());
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
public async Task StatusCodeIs200IfAllChecksHealthy()
|
||||
{
|
||||
|
|
@ -112,9 +109,9 @@ namespace Microsoft.AspNetCore.Diagnostics.HealthChecks
|
|||
.ConfigureServices(services =>
|
||||
{
|
||||
services.AddHealthChecks()
|
||||
.AddCheck("Foo", () => Task.FromResult(HealthCheckResult.Healthy("A-ok!")))
|
||||
.AddCheck("Bar", () => Task.FromResult(HealthCheckResult.Healthy("A-ok!")))
|
||||
.AddCheck("Baz", () => Task.FromResult(HealthCheckResult.Healthy("A-ok!")));
|
||||
.AddCheck("Foo", () => HealthCheckResult.Passed("A-ok!"))
|
||||
.AddCheck("Bar", () => HealthCheckResult.Passed("A-ok!"))
|
||||
.AddCheck("Baz", () => HealthCheckResult.Passed("A-ok!"));
|
||||
});
|
||||
var server = new TestServer(builder);
|
||||
var client = server.CreateClient();
|
||||
|
|
@ -137,9 +134,9 @@ namespace Microsoft.AspNetCore.Diagnostics.HealthChecks
|
|||
.ConfigureServices(services =>
|
||||
{
|
||||
services.AddHealthChecks()
|
||||
.AddCheck("Foo", () => Task.FromResult(HealthCheckResult.Healthy("A-ok!")))
|
||||
.AddCheck("Bar", () => Task.FromResult(HealthCheckResult.Degraded("Not so great.")))
|
||||
.AddCheck("Baz", () => Task.FromResult(HealthCheckResult.Healthy("A-ok!")));
|
||||
.AddCheck("Foo", () => HealthCheckResult.Passed("A-ok!"))
|
||||
.AddCheck("Bar", () => HealthCheckResult.Failed("Not so great."), failureStatus: HealthStatus.Degraded)
|
||||
.AddCheck("Baz", () => HealthCheckResult.Passed("A-ok!"));
|
||||
});
|
||||
var server = new TestServer(builder);
|
||||
var client = server.CreateClient();
|
||||
|
|
@ -162,9 +159,9 @@ namespace Microsoft.AspNetCore.Diagnostics.HealthChecks
|
|||
.ConfigureServices(services =>
|
||||
{
|
||||
services.AddHealthChecks()
|
||||
.AddCheck("Foo", () => Task.FromResult(HealthCheckResult.Healthy("A-ok!")))
|
||||
.AddCheck("Bar", () => Task.FromResult(HealthCheckResult.Unhealthy("Pretty bad.")))
|
||||
.AddCheck("Baz", () => Task.FromResult(HealthCheckResult.Healthy("A-ok!")));
|
||||
.AddAsyncCheck("Foo", () => Task.FromResult(HealthCheckResult.Passed("A-ok!")))
|
||||
.AddAsyncCheck("Bar", () => Task.FromResult(HealthCheckResult.Failed("Pretty bad.")))
|
||||
.AddAsyncCheck("Baz", () => Task.FromResult(HealthCheckResult.Passed("A-ok!")));
|
||||
});
|
||||
var server = new TestServer(builder);
|
||||
var client = server.CreateClient();
|
||||
|
|
@ -187,9 +184,9 @@ namespace Microsoft.AspNetCore.Diagnostics.HealthChecks
|
|||
.ConfigureServices(services =>
|
||||
{
|
||||
services.AddHealthChecks()
|
||||
.AddCheck("Foo", () => Task.FromResult(HealthCheckResult.Healthy("A-ok!")))
|
||||
.AddCheck("Bar", () => Task.FromResult(new HealthCheckResult(HealthCheckStatus.Failed, null, null, null)))
|
||||
.AddCheck("Baz", () => Task.FromResult(HealthCheckResult.Healthy("A-ok!")));
|
||||
.AddAsyncCheck("Foo", () => Task.FromResult(HealthCheckResult.Passed("A-ok!")))
|
||||
.AddAsyncCheck("Bar", () => throw null)
|
||||
.AddAsyncCheck("Baz", () => Task.FromResult(HealthCheckResult.Passed("A-ok!")));
|
||||
});
|
||||
var server = new TestServer(builder);
|
||||
var client = server.CreateClient();
|
||||
|
|
@ -225,9 +222,9 @@ namespace Microsoft.AspNetCore.Diagnostics.HealthChecks
|
|||
.ConfigureServices(services =>
|
||||
{
|
||||
services.AddHealthChecks()
|
||||
.AddCheck("Foo", () => Task.FromResult(HealthCheckResult.Healthy("A-ok!")))
|
||||
.AddCheck("Bar", () => Task.FromResult(HealthCheckResult.Unhealthy("Pretty bad.")))
|
||||
.AddCheck("Baz", () => Task.FromResult(HealthCheckResult.Healthy("A-ok!")));
|
||||
.AddAsyncCheck("Foo", () => Task.FromResult(HealthCheckResult.Passed("A-ok!")))
|
||||
.AddAsyncCheck("Bar", () => Task.FromResult(HealthCheckResult.Failed("Pretty bad.")))
|
||||
.AddAsyncCheck("Baz", () => Task.FromResult(HealthCheckResult.Passed("A-ok!")));
|
||||
});
|
||||
var server = new TestServer(builder);
|
||||
var client = server.CreateClient();
|
||||
|
|
@ -254,9 +251,9 @@ namespace Microsoft.AspNetCore.Diagnostics.HealthChecks
|
|||
.ConfigureServices(services =>
|
||||
{
|
||||
services.AddHealthChecks()
|
||||
.AddCheck("Foo", () => Task.FromResult(HealthCheckResult.Healthy("A-ok!")))
|
||||
.AddCheck("Bar", () => Task.FromResult(HealthCheckResult.Unhealthy("Pretty bad.")))
|
||||
.AddCheck("Baz", () => Task.FromResult(HealthCheckResult.Healthy("A-ok!")));
|
||||
.AddAsyncCheck("Foo", () => Task.FromResult(HealthCheckResult.Passed("A-ok!")))
|
||||
.AddAsyncCheck("Bar", () => Task.FromResult(HealthCheckResult.Failed("Pretty bad.")))
|
||||
.AddAsyncCheck("Baz", () => Task.FromResult(HealthCheckResult.Passed("A-ok!")));
|
||||
});
|
||||
var server = new TestServer(builder);
|
||||
var client = server.CreateClient();
|
||||
|
|
@ -277,7 +274,7 @@ namespace Microsoft.AspNetCore.Diagnostics.HealthChecks
|
|||
{
|
||||
ResultStatusCodes =
|
||||
{
|
||||
[HealthCheckStatus.Healthy] = 201,
|
||||
[HealthStatus.Healthy] = 201,
|
||||
}
|
||||
});
|
||||
})
|
||||
|
|
@ -308,10 +305,10 @@ namespace Microsoft.AspNetCore.Diagnostics.HealthChecks
|
|||
.ConfigureServices(services =>
|
||||
{
|
||||
services.AddHealthChecks()
|
||||
.AddCheck("Foo", () => Task.FromResult(HealthCheckResult.Healthy("A-ok!")))
|
||||
.AddAsyncCheck("Foo", () => Task.FromResult(HealthCheckResult.Passed("A-ok!")))
|
||||
// Will get filtered out
|
||||
.AddCheck("Bar", () => Task.FromResult(HealthCheckResult.Unhealthy("A-ok!")))
|
||||
.AddCheck("Baz", () => Task.FromResult(HealthCheckResult.Healthy("A-ok!")));
|
||||
.AddAsyncCheck("Bar", () => Task.FromResult(HealthCheckResult.Failed("A-ok!")))
|
||||
.AddAsyncCheck("Baz", () => Task.FromResult(HealthCheckResult.Passed("A-ok!")));
|
||||
});
|
||||
var server = new TestServer(builder);
|
||||
var client = server.CreateClient();
|
||||
|
|
|
|||
|
|
@ -1,31 +0,0 @@
|
|||
// 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.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.Extensions.Diagnostics.HealthChecks.Tests
|
||||
{
|
||||
public class CompositeHealthCheckResultTests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData(HealthCheckStatus.Healthy)]
|
||||
[InlineData(HealthCheckStatus.Degraded)]
|
||||
[InlineData(HealthCheckStatus.Unhealthy)]
|
||||
[InlineData(HealthCheckStatus.Failed)]
|
||||
public void Status_MatchesWorstStatusInResults(HealthCheckStatus statusValue)
|
||||
{
|
||||
var result = new CompositeHealthCheckResult(new Dictionary<string, HealthCheckResult>()
|
||||
{
|
||||
{"Foo", HealthCheckResult.Healthy() },
|
||||
{"Bar", HealthCheckResult.Healthy() },
|
||||
{"Baz", new HealthCheckResult(statusValue, exception: null, description: null, data: null) },
|
||||
{"Quick", HealthCheckResult.Healthy() },
|
||||
{"Quack", HealthCheckResult.Healthy() },
|
||||
{"Quock", HealthCheckResult.Healthy() },
|
||||
});
|
||||
|
||||
Assert.Equal(statusValue, result.Status);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -8,11 +8,12 @@ using System.Threading.Tasks;
|
|||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging.Testing;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.Extensions.Diagnostics.HealthChecks
|
||||
{
|
||||
public class HealthCheckServiceTests
|
||||
public class DefaultHealthCheckServiceTest
|
||||
{
|
||||
[Fact]
|
||||
public void Constructor_ThrowsUsefulExceptionForDuplicateNames()
|
||||
|
|
@ -25,22 +26,23 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks
|
|||
serviceCollection.AddLogging();
|
||||
serviceCollection.AddOptions();
|
||||
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())));
|
||||
.AddCheck("Foo", new DelegateHealthCheck(_ => Task.FromResult(HealthCheckResult.Passed())))
|
||||
.AddCheck("Foo", new DelegateHealthCheck(_ => Task.FromResult(HealthCheckResult.Passed())))
|
||||
.AddCheck("Bar", new DelegateHealthCheck(_ => Task.FromResult(HealthCheckResult.Passed())))
|
||||
.AddCheck("Baz", new DelegateHealthCheck(_ => Task.FromResult(HealthCheckResult.Passed())))
|
||||
.AddCheck("Baz", new DelegateHealthCheck(_ => Task.FromResult(HealthCheckResult.Passed())));
|
||||
|
||||
var services = serviceCollection.BuildServiceProvider();
|
||||
|
||||
|
||||
var scopeFactory = services.GetRequiredService<IServiceScopeFactory>();
|
||||
var logger = services.GetRequiredService<ILogger<HealthCheckService>>();
|
||||
var options = services.GetRequiredService<IOptions<HealthCheckServiceOptions>>();
|
||||
var logger = services.GetRequiredService<ILogger<DefaultHealthCheckService>>();
|
||||
|
||||
// Act
|
||||
var exception = Assert.Throws<ArgumentException>(() => new HealthCheckService(scopeFactory, logger));
|
||||
var exception = Assert.Throws<ArgumentException>(() => new DefaultHealthCheckService(scopeFactory, options, logger));
|
||||
|
||||
// Assert
|
||||
Assert.Equal($"Duplicate health checks were registered with the name(s): Foo, Baz{Environment.NewLine}Parameter name: healthChecks", exception.Message);
|
||||
Assert.StartsWith($"Duplicate health checks were registered with the name(s): Foo, Baz", exception.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -61,21 +63,21 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks
|
|||
|
||||
var service = CreateHealthChecksService(b =>
|
||||
{
|
||||
b.AddCheck("HealthyCheck", _ => Task.FromResult(HealthCheckResult.Healthy(HealthyMessage, data)));
|
||||
b.AddCheck("DegradedCheck", _ => Task.FromResult(HealthCheckResult.Degraded(DegradedMessage)));
|
||||
b.AddCheck("UnhealthyCheck", _ => Task.FromResult(HealthCheckResult.Unhealthy(UnhealthyMessage, exception)));
|
||||
b.AddAsyncCheck("HealthyCheck", _ => Task.FromResult(HealthCheckResult.Passed(HealthyMessage, data)));
|
||||
b.AddAsyncCheck("DegradedCheck", _ => Task.FromResult(HealthCheckResult.Failed(DegradedMessage)), failureStatus: HealthStatus.Degraded);
|
||||
b.AddAsyncCheck("UnhealthyCheck", _ => Task.FromResult(HealthCheckResult.Failed(UnhealthyMessage, exception)));
|
||||
});
|
||||
|
||||
// Act
|
||||
var results = await service.CheckHealthAsync();
|
||||
|
||||
// Assert
|
||||
Assert.Collection(results.Results,
|
||||
Assert.Collection(results.Entries,
|
||||
actual =>
|
||||
{
|
||||
Assert.Equal("HealthyCheck", actual.Key);
|
||||
Assert.Equal(HealthyMessage, actual.Value.Description);
|
||||
Assert.Equal(HealthCheckStatus.Healthy, actual.Value.Status);
|
||||
Assert.Equal(HealthStatus.Healthy, actual.Value.Status);
|
||||
Assert.Null(actual.Value.Exception);
|
||||
Assert.Collection(actual.Value.Data, item =>
|
||||
{
|
||||
|
|
@ -87,7 +89,7 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks
|
|||
{
|
||||
Assert.Equal("DegradedCheck", actual.Key);
|
||||
Assert.Equal(DegradedMessage, actual.Value.Description);
|
||||
Assert.Equal(HealthCheckStatus.Degraded, actual.Value.Status);
|
||||
Assert.Equal(HealthStatus.Degraded, actual.Value.Status);
|
||||
Assert.Null(actual.Value.Exception);
|
||||
Assert.Empty(actual.Value.Data);
|
||||
},
|
||||
|
|
@ -95,7 +97,7 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks
|
|||
{
|
||||
Assert.Equal("UnhealthyCheck", actual.Key);
|
||||
Assert.Equal(UnhealthyMessage, actual.Value.Description);
|
||||
Assert.Equal(HealthCheckStatus.Unhealthy, actual.Value.Status);
|
||||
Assert.Equal(HealthStatus.Unhealthy, actual.Value.Status);
|
||||
Assert.Same(exception, actual.Value.Exception);
|
||||
Assert.Empty(actual.Value.Data);
|
||||
});
|
||||
|
|
@ -119,21 +121,21 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks
|
|||
|
||||
var service = CreateHealthChecksService(b =>
|
||||
{
|
||||
b.AddCheck("HealthyCheck", _ => Task.FromResult(HealthCheckResult.Healthy(HealthyMessage, data)));
|
||||
b.AddCheck("DegradedCheck", _ => Task.FromResult(HealthCheckResult.Degraded(DegradedMessage)));
|
||||
b.AddCheck("UnhealthyCheck", _ => Task.FromResult(HealthCheckResult.Unhealthy(UnhealthyMessage, exception)));
|
||||
b.AddAsyncCheck("HealthyCheck", _ => Task.FromResult(HealthCheckResult.Passed(HealthyMessage, data)));
|
||||
b.AddAsyncCheck("DegradedCheck", _ => Task.FromResult(HealthCheckResult.Failed(DegradedMessage)), failureStatus: HealthStatus.Degraded);
|
||||
b.AddAsyncCheck("UnhealthyCheck", _ => Task.FromResult(HealthCheckResult.Failed(UnhealthyMessage, exception)));
|
||||
});
|
||||
|
||||
// Act
|
||||
var results = await service.CheckHealthAsync(c => c.Name == "HealthyCheck");
|
||||
|
||||
// Assert
|
||||
Assert.Collection(results.Results,
|
||||
Assert.Collection(results.Entries,
|
||||
actual =>
|
||||
{
|
||||
Assert.Equal("HealthyCheck", actual.Key);
|
||||
Assert.Equal(HealthyMessage, actual.Value.Description);
|
||||
Assert.Equal(HealthCheckStatus.Healthy, actual.Value.Status);
|
||||
Assert.Equal(HealthStatus.Healthy, actual.Value.Status);
|
||||
Assert.Null(actual.Value.Exception);
|
||||
Assert.Collection(actual.Value.Data, item =>
|
||||
{
|
||||
|
|
@ -144,7 +146,7 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CheckHealthAsync_ConvertsExceptionInHealthCheckerToFailedResultAsync()
|
||||
public async Task CheckHealthAsync_SetsRegistrationForEachCheck()
|
||||
{
|
||||
// Arrange
|
||||
var thrownException = new InvalidOperationException("Whoops!");
|
||||
|
|
@ -152,35 +154,79 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks
|
|||
|
||||
var service = CreateHealthChecksService(b =>
|
||||
{
|
||||
b.AddCheck("Throws", ct => throw thrownException);
|
||||
b.AddCheck("Faults", ct => Task.FromException<HealthCheckResult>(faultedException));
|
||||
b.AddCheck("Succeeds", ct => Task.FromResult(HealthCheckResult.Healthy()));
|
||||
b.AddCheck<NameCapturingCheck>("A");
|
||||
b.AddCheck<NameCapturingCheck>("B");
|
||||
b.AddCheck<NameCapturingCheck>("C");
|
||||
});
|
||||
|
||||
// Act
|
||||
var results = await service.CheckHealthAsync();
|
||||
|
||||
// Assert
|
||||
Assert.Collection(results.Results,
|
||||
Assert.Collection(
|
||||
results.Entries,
|
||||
actual =>
|
||||
{
|
||||
Assert.Equal("A", actual.Key);
|
||||
Assert.Collection(
|
||||
actual.Value.Data,
|
||||
kvp => Assert.Equal(kvp, new KeyValuePair<string, object>("name", "A")));
|
||||
},
|
||||
actual =>
|
||||
{
|
||||
Assert.Equal("B", actual.Key);
|
||||
Assert.Collection(
|
||||
actual.Value.Data,
|
||||
kvp => Assert.Equal(kvp, new KeyValuePair<string, object>("name", "B")));
|
||||
},
|
||||
actual =>
|
||||
{
|
||||
Assert.Equal("C", actual.Key);
|
||||
Assert.Collection(
|
||||
actual.Value.Data,
|
||||
kvp => Assert.Equal(kvp, new KeyValuePair<string, object>("name", "C")));
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CheckHealthAsync_ConvertsExceptionInHealthCheckToFailedResultAsync()
|
||||
{
|
||||
// Arrange
|
||||
var thrownException = new InvalidOperationException("Whoops!");
|
||||
var faultedException = new InvalidOperationException("Ohnoes!");
|
||||
|
||||
var service = CreateHealthChecksService(b =>
|
||||
{
|
||||
b.AddAsyncCheck("Throws", ct => throw thrownException);
|
||||
b.AddAsyncCheck("Faults", ct => Task.FromException<HealthCheckResult>(faultedException));
|
||||
b.AddAsyncCheck("Succeeds", ct => Task.FromResult(HealthCheckResult.Passed()));
|
||||
});
|
||||
|
||||
// Act
|
||||
var results = await service.CheckHealthAsync();
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
results.Entries,
|
||||
actual =>
|
||||
{
|
||||
Assert.Equal("Throws", actual.Key);
|
||||
Assert.Equal(thrownException.Message, actual.Value.Description);
|
||||
Assert.Equal(HealthCheckStatus.Failed, actual.Value.Status);
|
||||
Assert.Equal(HealthStatus.Failed, actual.Value.Status);
|
||||
Assert.Same(thrownException, actual.Value.Exception);
|
||||
},
|
||||
actual =>
|
||||
{
|
||||
Assert.Equal("Faults", actual.Key);
|
||||
Assert.Equal(faultedException.Message, actual.Value.Description);
|
||||
Assert.Equal(HealthCheckStatus.Failed, actual.Value.Status);
|
||||
Assert.Equal(HealthStatus.Failed, actual.Value.Status);
|
||||
Assert.Same(faultedException, actual.Value.Exception);
|
||||
},
|
||||
actual =>
|
||||
{
|
||||
Assert.Equal("Succeeds", actual.Key);
|
||||
Assert.Empty(actual.Value.Description);
|
||||
Assert.Equal(HealthCheckStatus.Healthy, actual.Value.Status);
|
||||
Assert.Null(actual.Value.Description);
|
||||
Assert.Equal(HealthStatus.Healthy, actual.Value.Status);
|
||||
Assert.Null(actual.Value.Exception);
|
||||
});
|
||||
}
|
||||
|
|
@ -190,12 +236,12 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks
|
|||
{
|
||||
// Arrange
|
||||
var sink = new TestSink();
|
||||
var check = new HealthCheck("TestScope", cancellationToken =>
|
||||
var check = new DelegateHealthCheck(cancellationToken =>
|
||||
{
|
||||
Assert.Collection(sink.Scopes,
|
||||
actual =>
|
||||
{
|
||||
Assert.Equal(actual.LoggerName, typeof(HealthCheckService).FullName);
|
||||
Assert.Equal(actual.LoggerName, typeof(DefaultHealthCheckService).FullName);
|
||||
Assert.Collection((IEnumerable<KeyValuePair<string, object>>)actual.Scope,
|
||||
item =>
|
||||
{
|
||||
|
|
@ -203,7 +249,7 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks
|
|||
Assert.Equal("TestScope", item.Value);
|
||||
});
|
||||
});
|
||||
return Task.FromResult(HealthCheckResult.Healthy());
|
||||
return Task.FromResult(HealthCheckResult.Passed());
|
||||
});
|
||||
|
||||
var loggerFactory = new TestLoggerFactory(sink, enabled: true);
|
||||
|
|
@ -212,36 +258,20 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks
|
|||
// Override the logger factory for testing
|
||||
b.Services.AddSingleton<ILoggerFactory>(loggerFactory);
|
||||
|
||||
b.AddCheck(check);
|
||||
b.AddCheck("TestScope", check);
|
||||
});
|
||||
|
||||
// Act
|
||||
var results = await service.CheckHealthAsync();
|
||||
|
||||
// Assert
|
||||
Assert.Collection(results.Results, actual =>
|
||||
Assert.Collection(results.Entries, actual =>
|
||||
{
|
||||
Assert.Equal("TestScope", actual.Key);
|
||||
Assert.Equal(HealthCheckStatus.Healthy, actual.Value.Status);
|
||||
Assert.Equal(HealthStatus.Healthy, actual.Value.Status);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CheckHealthAsync_ThrowsIfCheckReturnsUnknownStatusResult()
|
||||
{
|
||||
// Arrange
|
||||
var service = CreateHealthChecksService(b =>
|
||||
{
|
||||
b.AddCheck("Kaboom", ct => Task.FromResult(default(HealthCheckResult)));
|
||||
});
|
||||
|
||||
// Act
|
||||
var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => service.CheckHealthAsync());
|
||||
|
||||
// Assert
|
||||
Assert.Equal("Health check 'Kaboom' returned a result with a status of Unknown", ex.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CheckHealthAsync_CheckCanDependOnTransientService()
|
||||
{
|
||||
|
|
@ -250,7 +280,7 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks
|
|||
{
|
||||
b.Services.AddTransient<AnotherService>();
|
||||
|
||||
b.AddCheck<CheckWithServiceDependency>();
|
||||
b.AddCheck<CheckWithServiceDependency>("Test");
|
||||
});
|
||||
|
||||
// Act
|
||||
|
|
@ -258,11 +288,11 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks
|
|||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
results.Results,
|
||||
results.Entries,
|
||||
actual =>
|
||||
{
|
||||
Assert.Equal("Test", actual.Key);
|
||||
Assert.Equal(HealthCheckStatus.Healthy, actual.Value.Status);
|
||||
Assert.Equal(HealthStatus.Healthy, actual.Value.Status);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -274,7 +304,7 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks
|
|||
{
|
||||
b.Services.AddScoped<AnotherService>();
|
||||
|
||||
b.AddCheck<CheckWithServiceDependency>();
|
||||
b.AddCheck<CheckWithServiceDependency>("Test");
|
||||
});
|
||||
|
||||
// Act
|
||||
|
|
@ -282,11 +312,11 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks
|
|||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
results.Results,
|
||||
results.Entries,
|
||||
actual =>
|
||||
{
|
||||
Assert.Equal("Test", actual.Key);
|
||||
Assert.Equal(HealthCheckStatus.Healthy, actual.Value.Status);
|
||||
Assert.Equal(HealthStatus.Healthy, actual.Value.Status);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -298,7 +328,7 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks
|
|||
{
|
||||
b.Services.AddSingleton<AnotherService>();
|
||||
|
||||
b.AddCheck<CheckWithServiceDependency>();
|
||||
b.AddCheck<CheckWithServiceDependency>("Test");
|
||||
});
|
||||
|
||||
// Act
|
||||
|
|
@ -306,15 +336,15 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks
|
|||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
results.Results,
|
||||
results.Entries,
|
||||
actual =>
|
||||
{
|
||||
Assert.Equal("Test", actual.Key);
|
||||
Assert.Equal(HealthCheckStatus.Healthy, actual.Value.Status);
|
||||
Assert.Equal(HealthStatus.Healthy, actual.Value.Status);
|
||||
});
|
||||
}
|
||||
|
||||
private static HealthCheckService CreateHealthChecksService(Action<IHealthChecksBuilder> configure)
|
||||
private static DefaultHealthCheckService CreateHealthChecksService(Action<IHealthChecksBuilder> configure)
|
||||
{
|
||||
var services = new ServiceCollection();
|
||||
services.AddLogging();
|
||||
|
|
@ -326,7 +356,7 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks
|
|||
configure(builder);
|
||||
}
|
||||
|
||||
return (HealthCheckService)services.BuildServiceProvider(validateScopes: true).GetRequiredService<IHealthCheckService>();
|
||||
return (DefaultHealthCheckService)services.BuildServiceProvider(validateScopes: true).GetRequiredService<HealthCheckService>();
|
||||
}
|
||||
|
||||
private class AnotherService { }
|
||||
|
|
@ -336,12 +366,22 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks
|
|||
public CheckWithServiceDependency(AnotherService _)
|
||||
{
|
||||
}
|
||||
|
||||
public string Name => "Test";
|
||||
|
||||
public Task<HealthCheckResult> CheckHealthAsync(CancellationToken cancellationToken = default)
|
||||
|
||||
public Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return Task.FromResult(HealthCheckResult.Healthy());
|
||||
return Task.FromResult(HealthCheckResult.Passed());
|
||||
}
|
||||
}
|
||||
|
||||
private class NameCapturingCheck : IHealthCheck
|
||||
{
|
||||
public Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var data = new Dictionary<string, object>()
|
||||
{
|
||||
{ "name", context.Registration.Name },
|
||||
};
|
||||
return Task.FromResult(HealthCheckResult.Passed(data: data));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,257 @@
|
|||
// 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.
|
||||
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Diagnostics.HealthChecks;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.Extensions.DependencyInjection
|
||||
{
|
||||
// Integration tests for extension methods on IHealthCheckBuilder
|
||||
//
|
||||
// We test the longest overload of each 'family' of Add...Check methods, since they chain to each other.
|
||||
public class HealthChecksBuilderTest
|
||||
{
|
||||
[Fact]
|
||||
public void AddCheck_Instance()
|
||||
{
|
||||
// Arrange
|
||||
var instance = new DelegateHealthCheck((_) =>
|
||||
{
|
||||
return Task.FromResult(HealthCheckResult.Passed());
|
||||
});
|
||||
|
||||
var services = CreateServices();
|
||||
services.AddHealthChecks().AddCheck("test", failureStatus: HealthStatus.Degraded,tags: new[] { "tag", }, instance: instance);
|
||||
|
||||
var serviceProvider = services.BuildServiceProvider();
|
||||
|
||||
// Act
|
||||
var options = serviceProvider.GetRequiredService<IOptions<HealthCheckServiceOptions>>().Value;
|
||||
|
||||
// Assert
|
||||
var registration = Assert.Single(options.Registrations);
|
||||
Assert.Equal("test", registration.Name);
|
||||
Assert.Equal(HealthStatus.Degraded, registration.FailureStatus);
|
||||
Assert.Equal<string>(new[] { "tag", }, registration.Tags);
|
||||
Assert.Same(instance, registration.Factory(serviceProvider));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddCheck_T_TypeActivated()
|
||||
{
|
||||
// Arrange
|
||||
var services = CreateServices();
|
||||
services.AddHealthChecks().AddCheck<TestHealthCheck>("test", failureStatus: HealthStatus.Degraded, tags: new[] { "tag", });
|
||||
|
||||
var serviceProvider = services.BuildServiceProvider();
|
||||
|
||||
// Act
|
||||
var options = serviceProvider.GetRequiredService<IOptions<HealthCheckServiceOptions>>().Value;
|
||||
|
||||
// Assert
|
||||
var registration = Assert.Single(options.Registrations);
|
||||
Assert.Equal("test", registration.Name);
|
||||
Assert.Equal(HealthStatus.Degraded, registration.FailureStatus);
|
||||
Assert.Equal<string>(new[] { "tag", }, registration.Tags);
|
||||
Assert.IsType<TestHealthCheck>(registration.Factory(serviceProvider));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddCheck_T_Service()
|
||||
{
|
||||
// Arrange
|
||||
var instance = new TestHealthCheck();
|
||||
|
||||
var services = CreateServices();
|
||||
services.AddSingleton(instance);
|
||||
services.AddHealthChecks().AddCheck<TestHealthCheck>("test", failureStatus: HealthStatus.Degraded, tags: new[] { "tag", });
|
||||
|
||||
var serviceProvider = services.BuildServiceProvider();
|
||||
|
||||
// Act
|
||||
var options = serviceProvider.GetRequiredService<IOptions<HealthCheckServiceOptions>>().Value;
|
||||
|
||||
// Assert
|
||||
var registration = Assert.Single(options.Registrations);
|
||||
Assert.Equal("test", registration.Name);
|
||||
Assert.Equal(HealthStatus.Degraded, registration.FailureStatus);
|
||||
Assert.Equal<string>(new[] { "tag", }, registration.Tags);
|
||||
Assert.Same(instance, registration.Factory(serviceProvider));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddTypeActivatedCheck()
|
||||
{
|
||||
// Arrange
|
||||
var services = CreateServices();
|
||||
services
|
||||
.AddHealthChecks()
|
||||
.AddTypeActivatedCheck<TestHealthCheckWithArgs>("test", failureStatus: HealthStatus.Degraded, tags: new[] { "tag", }, args: new object[] { 5, "hi", });
|
||||
|
||||
var serviceProvider = services.BuildServiceProvider();
|
||||
|
||||
// Act
|
||||
var options = serviceProvider.GetRequiredService<IOptions<HealthCheckServiceOptions>>().Value;
|
||||
|
||||
// Assert
|
||||
var registration = Assert.Single(options.Registrations);
|
||||
Assert.Equal("test", registration.Name);
|
||||
Assert.Equal(HealthStatus.Degraded, registration.FailureStatus);
|
||||
Assert.Equal<string>(new[] { "tag", }, registration.Tags);
|
||||
|
||||
var check = Assert.IsType<TestHealthCheckWithArgs>(registration.Factory(serviceProvider));
|
||||
Assert.Equal(5, check.I);
|
||||
Assert.Equal("hi", check.S);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddDelegateCheck_NoArg()
|
||||
{
|
||||
// Arrange
|
||||
var services = CreateServices();
|
||||
services.AddHealthChecks().AddCheck("test", failureStatus: HealthStatus.Degraded, tags: new[] { "tag", }, check: () =>
|
||||
{
|
||||
return HealthCheckResult.Passed();
|
||||
});
|
||||
|
||||
var serviceProvider = services.BuildServiceProvider();
|
||||
|
||||
// Act
|
||||
var options = serviceProvider.GetRequiredService<IOptions<HealthCheckServiceOptions>>().Value;
|
||||
|
||||
// Assert
|
||||
var registration = Assert.Single(options.Registrations);
|
||||
Assert.Equal("test", registration.Name);
|
||||
Assert.Equal(HealthStatus.Degraded, registration.FailureStatus);
|
||||
Assert.Equal<string>(new[] { "tag", }, registration.Tags);
|
||||
Assert.IsType<DelegateHealthCheck>(registration.Factory(serviceProvider));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddDelegateCheck_CancellationToken()
|
||||
{
|
||||
// Arrange
|
||||
var services = CreateServices();
|
||||
services.AddHealthChecks().AddCheck("test", (_) =>
|
||||
{
|
||||
return HealthCheckResult.Passed();
|
||||
}, failureStatus: HealthStatus.Degraded, tags: new[] { "tag", });
|
||||
|
||||
var serviceProvider = services.BuildServiceProvider();
|
||||
|
||||
// Act
|
||||
var options = serviceProvider.GetRequiredService<IOptions<HealthCheckServiceOptions>>().Value;
|
||||
|
||||
// Assert
|
||||
var registration = Assert.Single(options.Registrations);
|
||||
Assert.Equal("test", registration.Name);
|
||||
Assert.Equal(HealthStatus.Degraded, registration.FailureStatus);
|
||||
Assert.Equal<string>(new[] { "tag", }, registration.Tags);
|
||||
Assert.IsType<DelegateHealthCheck>(registration.Factory(serviceProvider));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddAsyncDelegateCheck_NoArg()
|
||||
{
|
||||
// Arrange
|
||||
var services = CreateServices();
|
||||
services.AddHealthChecks().AddAsyncCheck("test", () =>
|
||||
{
|
||||
return Task.FromResult(HealthCheckResult.Passed());
|
||||
}, failureStatus: HealthStatus.Degraded, tags: new[] { "tag", });
|
||||
|
||||
var serviceProvider = services.BuildServiceProvider();
|
||||
|
||||
// Act
|
||||
var options = serviceProvider.GetRequiredService<IOptions<HealthCheckServiceOptions>>().Value;
|
||||
|
||||
// Assert
|
||||
var registration = Assert.Single(options.Registrations);
|
||||
Assert.Equal("test", registration.Name);
|
||||
Assert.Equal(HealthStatus.Degraded, registration.FailureStatus);
|
||||
Assert.Equal<string>(new[] { "tag", }, registration.Tags);
|
||||
Assert.IsType<DelegateHealthCheck>(registration.Factory(serviceProvider));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddAsyncDelegateCheck_CancellationToken()
|
||||
{
|
||||
// Arrange
|
||||
var services = CreateServices();
|
||||
services.AddHealthChecks().AddAsyncCheck("test", (_) =>
|
||||
{
|
||||
return Task.FromResult(HealthCheckResult.Passed());
|
||||
}, failureStatus: HealthStatus.Degraded, tags: new[] { "tag", });
|
||||
|
||||
var serviceProvider = services.BuildServiceProvider();
|
||||
|
||||
// Act
|
||||
var options = serviceProvider.GetRequiredService<IOptions<HealthCheckServiceOptions>>().Value;
|
||||
|
||||
// Assert
|
||||
var registration = Assert.Single(options.Registrations);
|
||||
Assert.Equal("test", registration.Name);
|
||||
Assert.Equal(HealthStatus.Degraded, registration.FailureStatus);
|
||||
Assert.Equal<string>(new[] { "tag", }, registration.Tags);
|
||||
Assert.IsType<DelegateHealthCheck>(registration.Factory(serviceProvider));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ChecksCanBeRegisteredInMultipleCallsToAddHealthChecks()
|
||||
{
|
||||
var services = new ServiceCollection();
|
||||
services
|
||||
.AddHealthChecks()
|
||||
.AddAsyncCheck("Foo", () => Task.FromResult(HealthCheckResult.Passed()));
|
||||
services
|
||||
.AddHealthChecks()
|
||||
.AddAsyncCheck("Bar", () => Task.FromResult(HealthCheckResult.Passed()));
|
||||
|
||||
// Act
|
||||
var options = services.BuildServiceProvider().GetRequiredService<IOptions<HealthCheckServiceOptions>>();
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
options.Value.Registrations,
|
||||
actual => Assert.Equal("Foo", actual.Name),
|
||||
actual => Assert.Equal("Bar", actual.Name));
|
||||
}
|
||||
|
||||
private IServiceCollection CreateServices()
|
||||
{
|
||||
var services = new ServiceCollection();
|
||||
services.AddLogging();
|
||||
services.AddOptions();
|
||||
return services;
|
||||
}
|
||||
|
||||
private class TestHealthCheck : IHealthCheck
|
||||
{
|
||||
public Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default)
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
private class TestHealthCheckWithArgs : IHealthCheck
|
||||
{
|
||||
public TestHealthCheckWithArgs(int i, string s)
|
||||
{
|
||||
I = i;
|
||||
S = s;
|
||||
}
|
||||
|
||||
public int I { get; set; }
|
||||
|
||||
public string S { get; set; }
|
||||
|
||||
public Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default)
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +1,12 @@
|
|||
// 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.
|
||||
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Diagnostics.HealthChecks;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.Extensions.Diagnostics.HealthChecks
|
||||
namespace Microsoft.Extensions.DependencyInjection
|
||||
{
|
||||
public class ServiceCollectionExtensionsTests
|
||||
public class ServiceCollectionExtensionsTest
|
||||
{
|
||||
[Fact]
|
||||
public void AddHealthChecks_RegistersSingletonHealthCheckServiceIdempotently()
|
||||
|
|
@ -23,8 +23,8 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks
|
|||
actual =>
|
||||
{
|
||||
Assert.Equal(ServiceLifetime.Singleton, actual.Lifetime);
|
||||
Assert.Equal(typeof(IHealthCheckService), actual.ServiceType);
|
||||
Assert.Equal(typeof(HealthCheckService), actual.ImplementationType);
|
||||
Assert.Equal(typeof(HealthCheckService), actual.ServiceType);
|
||||
Assert.Equal(typeof(DefaultHealthCheckService), actual.ImplementationType);
|
||||
Assert.Null(actual.ImplementationInstance);
|
||||
Assert.Null(actual.ImplementationFactory);
|
||||
});
|
||||
|
|
@ -1,33 +0,0 @@
|
|||
// 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.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.Extensions.Diagnostics.HealthChecks
|
||||
{
|
||||
public class HealthChecksBuilderTests
|
||||
{
|
||||
[Fact]
|
||||
public void ChecksCanBeRegisteredInMultipleCallsToAddHealthChecks()
|
||||
{
|
||||
var services = new ServiceCollection();
|
||||
services.AddHealthChecks()
|
||||
.AddCheck("Foo", () => Task.FromResult(HealthCheckResult.Healthy()));
|
||||
services.AddHealthChecks()
|
||||
.AddCheck("Bar", () => Task.FromResult(HealthCheckResult.Healthy()));
|
||||
|
||||
// Act
|
||||
var checks = services.BuildServiceProvider().GetRequiredService<IEnumerable<IHealthCheck>>();
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
checks,
|
||||
actual => Assert.Equal("Foo", actual.Name),
|
||||
actual => Assert.Equal("Bar", actual.Name));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
// 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.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.Extensions.Diagnostics.HealthChecks
|
||||
{
|
||||
public class HealthReportTest
|
||||
{
|
||||
[Theory]
|
||||
[InlineData(HealthStatus.Healthy)]
|
||||
[InlineData(HealthStatus.Degraded)]
|
||||
[InlineData(HealthStatus.Unhealthy)]
|
||||
[InlineData(HealthStatus.Failed)]
|
||||
public void Status_MatchesWorstStatusInResults(HealthStatus status)
|
||||
{
|
||||
var result = new HealthReport(new Dictionary<string, HealthReportEntry>()
|
||||
{
|
||||
{"Foo", new HealthReportEntry(HealthStatus.Healthy, null, null, null) },
|
||||
{"Bar", new HealthReportEntry(HealthStatus.Healthy, null, null, null) },
|
||||
{"Baz", new HealthReportEntry(status, exception: null, description: null, data: null) },
|
||||
{"Quick", new HealthReportEntry(HealthStatus.Healthy, null, null, null) },
|
||||
{"Quack", new HealthReportEntry(HealthStatus.Healthy, null, null, null) },
|
||||
{"Quock", new HealthReportEntry(HealthStatus.Healthy, null, null, null) },
|
||||
});
|
||||
|
||||
Assert.Equal(status, result.Status);
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue