Merge pull request #481 from dotnet-maestro-bot/merge/release/2.2-to-master
[automated] Merge branch 'release/2.2' => 'master'
This commit is contained in:
commit
4cecbc668a
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -8,7 +8,6 @@
|
|||
<FeatureBranchVersionPrefix Condition="'$(FeatureBranchVersionPrefix)' == ''">a-</FeatureBranchVersionPrefix>
|
||||
<VersionSuffix Condition="'$(VersionSuffix)' != '' And '$(FeatureBranchVersionSuffix)' != ''">$(FeatureBranchVersionPrefix)$(VersionSuffix)-$([System.Text.RegularExpressions.Regex]::Replace('$(FeatureBranchVersionSuffix)', '[^\w-]', '-'))</VersionSuffix>
|
||||
<VersionSuffix Condition="'$(VersionSuffix)' != '' And '$(BuildNumber)' != ''">$(VersionSuffix)-$(BuildNumber)</VersionSuffix>
|
||||
|
||||
<ExperimentalVersionPrefix>0.6.0</ExperimentalVersionPrefix>
|
||||
<ExperimentalVersionSuffix>alpha1</ExperimentalVersionSuffix>
|
||||
<ExperimentalPackageVersion Condition="'$(IsFinalBuild)' == 'true' AND '$(ExperimentalVersionSuffix)' == 'rtm' ">$(ExperimentalVersionPrefix)</ExperimentalPackageVersion>
|
||||
|
|
|
|||
Loading…
Reference in New Issue