Merge pull request #456 from dotnet-maestro-bot/merge/release/2.2-to-master

[automated] Merge branch 'release/2.2' => 'master'
This commit is contained in:
Ryan Nowak 2018-08-03 09:41:00 -07:00 committed by GitHub
commit a59765c472
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 1046 additions and 1018 deletions

View File

@ -16,6 +16,7 @@
<MicrosoftEntityFrameworkCoreInMemoryPackageVersion>3.0.0-alpha1-10173</MicrosoftEntityFrameworkCoreInMemoryPackageVersion>
<MicrosoftEntityFrameworkCoreRelationalPackageVersion>3.0.0-alpha1-10173</MicrosoftEntityFrameworkCoreRelationalPackageVersion>
<MicrosoftEntityFrameworkCoreSqlServerPackageVersion>3.0.0-alpha1-10173</MicrosoftEntityFrameworkCoreSqlServerPackageVersion>
<MicrosoftExtensionsConfigurationCommandLinePackageVersion>3.0.0-alpha1-10173</MicrosoftExtensionsConfigurationCommandLinePackageVersion>
<MicrosoftExtensionsDependencyInjectionAbstractionsPackageVersion>3.0.0-alpha1-10173</MicrosoftExtensionsDependencyInjectionAbstractionsPackageVersion>
<MicrosoftExtensionsDependencyInjectionPackageVersion>3.0.0-alpha1-10173</MicrosoftExtensionsDependencyInjectionPackageVersion>
<MicrosoftExtensionsDiagnosticAdapterPackageVersion>3.0.0-alpha1-10173</MicrosoftExtensionsDiagnosticAdapterPackageVersion>

View File

@ -0,0 +1,35 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
namespace HealthChecksSample
{
// Pass in `--scenario basic` at the command line to run this sample.
public class BasicStartup
{
public void ConfigureServices(IServiceCollection services)
{
// Registers required services for health checks
services.AddHealthChecks();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
// This will register the health checks middleware at the URL /health.
//
// By default health checks will return a 200 with 'Healthy'.
// - No health checks are registered by default, the app is healthy if it is reachable
// - The default response writer writes the HealthCheckStatus as text/plain content
//
// This is the simplest way to use health checks, it is suitable for systems
// that want to check for 'liveness' of an application.
app.UseHealthChecks("/health");
app.Run(async (context) =>
{
await context.Response.WriteAsync("Go to /health to see the health status");
});
}
}
}

View File

@ -0,0 +1,67 @@
using System.Linq;
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
{
// Pass in `--scenario writer` at the command line to run this sample.
public class CustomWriterStartup
{
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>();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
// This will register the health checks middleware at the URL /health
//
// This example overrides the HealthCheckResponseWriter to write the health
// check result in a totally custom way.
app.UseHealthChecks("/health", new HealthCheckOptions()
{
// This custom writer formats the detailed status as an HTML table.
ResponseWriter = WriteResponse,
});
app.Run(async (context) =>
{
await context.Response.WriteAsync("Go to /health to see the health status");
});
}
private static Task WriteResponse(HttpContext httpContext, CompositeHealthCheckResult result)
{
httpContext.Response.ContentType = "text/html";
return httpContext.Response.WriteAsync($@"
<html>
<body>
<h1>
Everything is {result.Status}
</h1>
<table>
<thead>
<tr><td>Name</td><td>Status</td></tr>
</thead>
<tbody>
{string.Join("", result.Results.Select(kvp => $"<tr><td>{kvp.Key}</td><td>{kvp.Value.Status}</td></tr>"))}
</tbody>
</table>
</body>
</html>");
}
}
}

View File

@ -0,0 +1,68 @@
using System;
using System.Collections.Generic;
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
{
// Pass in `--scenario detailed` at the command line to run this sample.
public class DetailedStatusStartup
{
public void ConfigureServices(IServiceCollection services)
{
// Registers required services for health checks
services
.AddHealthChecks()
// Registers a custom health check, in this case it will execute an
// inline delegate.
.AddCheck("GC Info", () =>
{
// This example will report degraded status if the application is using
// more than 1gb of memory.
//
// Additionally we include some GC info in the reported diagnostics.
var allocated = GC.GetTotalMemory(forceFullCollection: false);
var data = new Dictionary<string, object>()
{
{ "Allocated", allocated },
{ "Gen0Collections", GC.CollectionCount(0) },
{ "Gen1Collections", GC.CollectionCount(1) },
{ "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;
return Task.FromResult(new HealthCheckResult(
status,
exception: null,
description: "reports degraded status if allocated bytes >= 1gb",
data: data));
});
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
// This will register the health checks middleware at the URL /health
//
// This example overrides the ResponseWriter to include a detailed
// status as JSON. Use this response writer (or create your own) to include
// detailed diagnostic information for use by a monitoring system.
app.UseHealthChecks("/health", new HealthCheckOptions()
{
ResponseWriter = HealthCheckResponseWriters.WriteDetailedJson,
});
app.Run(async (context) =>
{
await context.Response.WriteAsync("Go to /health to see the health status");
});
}
}
}

View File

@ -0,0 +1,41 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Diagnostics.HealthChecks;
namespace HealthChecksSample
{
// This is an example of a custom health check that implements IHealthCheck.
// This is the same core logic as the DetailedStatusStartup example.
// See CustomWriterStartup to see how this is registered.
public class GCInfoHealthCheck : IHealthCheck
{
public string Name { get; } = "GCInfo";
public Task<HealthCheckResult> CheckHealthAsync(CancellationToken cancellationToken = default(CancellationToken))
{
// This example will report degraded status if the application is using
// more than 1gb of memory.
//
// Additionally we include some GC info in the reported diagnostics.
var allocated = GC.GetTotalMemory(forceFullCollection: false);
var data = new Dictionary<string, object>()
{
{ "Allocated", allocated },
{ "Gen0Collections", GC.CollectionCount(0) },
{ "Gen1Collections", GC.CollectionCount(1) },
{ "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;
return Task.FromResult(new HealthCheckResult(
status,
exception: null,
description: "reports degraded status if allocated bytes >= 1gb",
data: data));
}
}
}

View File

@ -1,10 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp2.0</TargetFramework>
<!-- Used in our tests -->
<TargetFrameworks Condition="'$(OS)' == 'Windows_NT'">netcoreapp2.0;net461</TargetFrameworks>
<TargetFrameworks Condition="'$(TargetFrameworks)'==''">netcoreapp2.0</TargetFrameworks>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Configuration.CommandLine" Version="$(MicrosoftExtensionsConfigurationCommandLinePackageVersion)" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="$(MicrosoftExtensionsLoggingConsolePackageVersion)" />
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="$(MicrosoftAspNetCoreServerKestrelPackageVersion)" />
<PackageReference Include="Microsoft.AspNetCore.StaticFiles" Version="$(MicrosoftAspNetCoreStaticFilesPackageVersion)" />

View File

@ -0,0 +1,80 @@
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
{
// Pass in `--scenario liveness` at the command line to run this sample.
public class LivenessProbeStartup
{
public void ConfigureServices(IServiceCollection services)
{
// Registers required services for health checks
services
.AddHealthChecks()
.AddCheck("identity", () => Task.FromResult(HealthCheckResult.Healthy()))
.AddCheck(new SlowDependencyHealthCheck());
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
// This will register the health checks middleware twice:
// - at /health/ready for 'readiness'
// - at /health/live for 'liveness'
//
// Using a separate liveness and readiness check is useful in an environment like Kubernetes
// when an application needs to do significant work before accepting requests. Using separate
// checks allows the orchestrator to distinguish whether the application is functioning but
// not yet ready or if the application has failed to start.
//
// For instance the liveness check will do a quick set of checks to determine if the process
// is functioning correctly.
//
// The readiness check might do a set of more expensive or time-consuming checks to determine
// if all other resources are responding.
//
// See https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-probes/ for
// more details about readiness and liveness probes in Kubernetes.
//
// In this example, the liveness check will us an 'identity' check that always returns healthy.
//
// In this example, the readiness check will run all registered checks, include a check with an
// long initialization time (15 seconds).
// The readiness check uses all of the registered health checks (default)
app.UseHealthChecks("/health/ready", new HealthCheckOptions()
{
// This sample is using detailed status to make more apparent which checks are being run - any
// output format will work with liveness and readiness checks.
ResponseWriter = HealthCheckResponseWriters.WriteDetailedJson,
});
// The liveness check uses an 'identity' health check that always returns healty
app.UseHealthChecks("/health/live", new HealthCheckOptions()
{
// Filters the set of health checks run by this middleware
HealthCheckNames =
{
"identity",
},
// This sample is using detailed status to make more apparent which checks are being run - any
// output format will work with liveness and readiness checks.
ResponseWriter = HealthCheckResponseWriters.WriteDetailedJson,
});
app.Run(async (context) =>
{
await context.Response.WriteAsync("Go to /health/ready to see the readiness status");
await context.Response.WriteAsync(Environment.NewLine);
await context.Response.WriteAsync("Go to /health/live to see the liveness status");
});
}
}
}

View File

@ -1,23 +1,55 @@
using System;
using System.Collections.Generic;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
namespace HealthChecksSample
{
public class Program
{
private static readonly Dictionary<string, Type> _scenarios;
static Program()
{
_scenarios = new Dictionary<string, Type>(StringComparer.OrdinalIgnoreCase)
{
{ "", typeof(BasicStartup) },
{ "basic", typeof(BasicStartup) },
{ "detailed", typeof(DetailedStatusStartup) },
{ "writer", typeof(CustomWriterStartup) },
{ "liveness", typeof(LivenessProbeStartup) },
};
}
public static void Main(string[] args)
{
BuildWebHost(args).Run();
}
public static IWebHost BuildWebHost(string[] args) =>
new WebHostBuilder()
public static IWebHost BuildWebHost(string[] args)
{
var config = new ConfigurationBuilder()
.AddEnvironmentVariables(prefix: "ASPNETCORE_")
.AddCommandLine(args)
.Build();
var scenario = config["scenario"] ?? string.Empty;
if (!_scenarios.TryGetValue(scenario, out var startupType))
{
startupType = typeof(BasicStartup);
}
return new WebHostBuilder()
.UseConfiguration(config)
.ConfigureLogging(builder =>
{
builder.AddConsole();
})
.UseKestrel()
.UseStartup<Startup>()
.UseStartup(startupType)
.Build();
}
}
}

View File

@ -0,0 +1,32 @@
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Diagnostics.HealthChecks;
namespace HealthChecksSample
{
// Simulates a health check for an application dependency that takes a while to initialize.
// This is part of the readiness/liveness probe sample.
public class SlowDependencyHealthCheck : IHealthCheck
{
public static readonly string HealthCheckName = "slow_dependency";
private readonly Task _task;
public SlowDependencyHealthCheck()
{
_task = Task.Delay(15 * 1000);
}
public string Name => HealthCheckName;
public Task<HealthCheckResult> CheckHealthAsync(CancellationToken cancellationToken = default(CancellationToken))
{
if (_task.IsCompleted)
{
return Task.FromResult(HealthCheckResult.Healthy("Dependency is ready"));
}
return Task.FromResult(HealthCheckResult.Unhealthy("Dependency is still initializing"));
}
}
}

View File

@ -1,28 +0,0 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
namespace HealthChecksSample
{
public class Startup
{
// This method gets called by the runtime. Use this method to add services to the container.
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
public void ConfigureServices(IServiceCollection services)
{
services.AddHealthChecks();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseHealthChecks("/health");
app.Run(async (context) =>
{
await context.Response.WriteAsync("Hello World!");
});
}
}
}

View File

@ -0,0 +1,67 @@
// 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.AspNetCore.Diagnostics.HealthChecks;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Options;
namespace Microsoft.AspNetCore.Builder
{
/// <summary>
/// <see cref="IApplicationBuilder"/> extension methods for the <see cref="HealthCheckMiddleware"/>.
/// </summary>
public static class HealthCheckApplicationBuilderExtensions
{
/// <summary>
/// Adds a middleware that provides health check status.
/// </summary>
/// <param name="app">The <see cref="IApplicationBuilder"/>.</param>
/// <param name="path">The path on which to provide health check status.</param>
/// <returns>A reference to the <paramref name="app"/> after the operation has completed.</returns>
/// <remarks>
/// The health check middleware will use default settings other than the provided <paramref name="path"/>.
/// </remarks>
public static IApplicationBuilder UseHealthChecks(this IApplicationBuilder app, PathString path)
{
if (app == null)
{
throw new ArgumentNullException(nameof(app));
}
if (!path.HasValue)
{
throw new ArgumentException("A URL path must be provided", nameof(path));
}
return app.Map(path, b => b.UseMiddleware<HealthCheckMiddleware>());
}
/// <summary>
/// Adds a middleware that provides health check status.
/// </summary>
/// <param name="app">The <see cref="IApplicationBuilder"/>.</param>
/// <param name="path">The path on which to provide health check status.</param>
/// <param name="options">A <see cref="HealthCheckOptions"/> used to configure the middleware.</param>
/// <returns>A reference to the <paramref name="app"/> after the operation has completed.</returns>
public static IApplicationBuilder UseHealthChecks(this IApplicationBuilder app, PathString path, HealthCheckOptions options)
{
if (app == null)
{
throw new ArgumentNullException(nameof(app));
}
if (!path.HasValue)
{
throw new ArgumentException("A URL path must be provided", nameof(path));
}
if (options == null)
{
throw new ArgumentNullException(nameof(options));
}
return app.Map(path, b => b.UseMiddleware<HealthCheckMiddleware>(Options.Create(options)));
}
}
}

View File

@ -1,32 +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 Microsoft.AspNetCore.Diagnostics.HealthChecks;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Options;
namespace Microsoft.AspNetCore.Builder
{
/// <summary>
/// <see cref="IApplicationBuilder"/> extension methods for the <see cref="HealthCheckMiddleware"/>.
/// </summary>
public static class HealthCheckAppBuilderExtensions
{
/// <summary>
/// Adds a middleware that provides a REST API for requesting health check status.
/// </summary>
/// <param name="app">The <see cref="IApplicationBuilder"/>.</param>
/// <param name="path">The path on which to provide the API.</param>
/// <returns>A reference to the <paramref name="app"/> after the operation has completed.</returns>
public static IApplicationBuilder UseHealthChecks(this IApplicationBuilder app, PathString path)
{
app = app ?? throw new ArgumentNullException(nameof(app));
return app.UseMiddleware<HealthCheckMiddleware>(Options.Create(new HealthCheckOptions()
{
Path = path
}));
}
}
}

View File

@ -2,14 +2,12 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Diagnostics;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Diagnostics.HealthChecks;
using Microsoft.Extensions.Options;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace Microsoft.AspNetCore.Diagnostics.HealthChecks
{
@ -18,62 +16,103 @@ namespace Microsoft.AspNetCore.Diagnostics.HealthChecks
private readonly RequestDelegate _next;
private readonly HealthCheckOptions _healthCheckOptions;
private readonly IHealthCheckService _healthCheckService;
private readonly IHealthCheck[] _checks;
public HealthCheckMiddleware(RequestDelegate next, IOptions<HealthCheckOptions> healthCheckOptions, IHealthCheckService healthCheckService)
public HealthCheckMiddleware(
RequestDelegate next,
IOptions<HealthCheckOptions> healthCheckOptions,
IHealthCheckService healthCheckService)
{
if (next == null)
{
throw new ArgumentNullException(nameof(next));
}
if (healthCheckOptions == null)
{
throw new ArgumentNullException(nameof(healthCheckOptions));
}
if (healthCheckService == null)
{
throw new ArgumentNullException(nameof(healthCheckService));
}
_next = next;
_healthCheckOptions = healthCheckOptions.Value;
_healthCheckService = healthCheckService;
_checks = FilterHealthChecks(_healthCheckService.Checks, healthCheckOptions.Value.HealthCheckNames);
}
/// <summary>
/// Process an individual request.
/// Processes a request.
/// </summary>
/// <param name="context"></param>
/// <param name="httpContext"></param>
/// <returns></returns>
public async Task InvokeAsync(HttpContext context)
public async Task InvokeAsync(HttpContext httpContext)
{
if (context.Request.Path == _healthCheckOptions.Path)
if (httpContext == null)
{
// Get results
var result = await _healthCheckService.CheckHealthAsync(context.RequestAborted);
throw new ArgumentNullException(nameof(httpContext));
}
// Map status to response code
switch (result.Status)
// Get results
var result = await _healthCheckService.CheckHealthAsync(_checks, httpContext.RequestAborted);
// Map status to response code - this is customizable via options.
if (!_healthCheckOptions.ResultStatusCodes.TryGetValue(result.Status, out var statusCode))
{
var message =
$"No status code mapping found for {nameof(HealthCheckStatus)} value: {result.Status}." +
$"{nameof(HealthCheckOptions)}.{nameof(HealthCheckOptions.ResultStatusCodes)} must contain" +
$"an entry for {result.Status}.";
throw new InvalidOperationException(message);
}
httpContext.Response.StatusCode = statusCode;
if (_healthCheckOptions.ResponseWriter != null)
{
await _healthCheckOptions.ResponseWriter(httpContext, result);
}
}
private static IHealthCheck[] FilterHealthChecks(
IReadOnlyDictionary<string, IHealthCheck> checks,
ISet<string> names)
{
// If there are no filters then include all checks.
if (names.Count == 0)
{
return checks.Values.ToArray();
}
// Keep track of what we don't find so we can report errors.
var notFound = new HashSet<string>(names, StringComparer.OrdinalIgnoreCase);
var matches = new List<IHealthCheck>();
foreach (var kvp in checks)
{
if (!notFound.Remove(kvp.Key))
{
case HealthCheckStatus.Failed:
context.Response.StatusCode = StatusCodes.Status500InternalServerError;
break;
case HealthCheckStatus.Unhealthy:
context.Response.StatusCode = StatusCodes.Status503ServiceUnavailable;
break;
case HealthCheckStatus.Degraded:
// Degraded doesn't mean unhealthy so we return 200, but the content will contain more details
context.Response.StatusCode = StatusCodes.Status200OK;
break;
case HealthCheckStatus.Healthy:
context.Response.StatusCode = StatusCodes.Status200OK;
break;
default:
// This will only happen when we change HealthCheckStatus and we don't update this.
Debug.Fail($"Unrecognized HealthCheckStatus value: {result.Status}");
throw new InvalidOperationException($"Unrecognized HealthCheckStatus value: {result.Status}");
// This check was excluded
continue;
}
// Render results to JSON
var json = new JObject(
new JProperty("status", result.Status.ToString()),
new JProperty("results", new JObject(result.Results.Select(pair =>
new JProperty(pair.Key, new JObject(
new JProperty("status", pair.Value.Status.ToString()),
new JProperty("description", pair.Value.Description),
new JProperty("data", new JObject(pair.Value.Data.Select(p => new JProperty(p.Key, p.Value))))))))));
await context.Response.WriteAsync(json.ToString(Formatting.None));
matches.Add(kvp.Value);
}
else
if (notFound.Count > 0)
{
await _next(context);
var message =
$"The following health checks were not found: '{string.Join(", ", notFound)}'. " +
$"Registered health checks: '{string.Join(", ", checks.Keys)}'.";
throw new InvalidOperationException(message);
}
return matches.ToArray();
}
}
}

View File

@ -1,7 +1,11 @@
// 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.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Diagnostics.HealthChecks;
namespace Microsoft.AspNetCore.Diagnostics.HealthChecks
{
@ -11,8 +15,32 @@ namespace Microsoft.AspNetCore.Diagnostics.HealthChecks
public class HealthCheckOptions
{
/// <summary>
/// Gets or sets the path at which the Health Check results will be available.
/// Gets a set of health check names used to filter the set of health checks run.
/// </summary>
public PathString Path { get; set; }
/// <remarks>
/// If <see cref="HealthCheckNames"/> is empty, the <see cref="HealthCheckMiddleware"/> will run all
/// registered health checks - this is the default behavior. To run a subset of health checks,
/// add the names of the desired health checks.
/// </remarks>
public ISet<string> HealthCheckNames { get; } = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
public IDictionary<HealthCheckStatus, int> ResultStatusCodes { get; } = new Dictionary<HealthCheckStatus, int>()
{
{ HealthCheckStatus.Healthy, StatusCodes.Status200OK },
{ HealthCheckStatus.Degraded, StatusCodes.Status200OK },
{ HealthCheckStatus.Unhealthy, StatusCodes.Status503ServiceUnavailable },
// This means that a health check failed, so 500 is appropriate. This is an error.
{ HealthCheckStatus.Failed, StatusCodes.Status500InternalServerError },
};
/// <summary>
/// Gets or sets a delegate used to write the response.
/// </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.
/// </remarks>
public Func<HttpContext, CompositeHealthCheckResult, Task> ResponseWriter { get; set; } = HealthCheckResponseWriters.WriteMinimalPlaintext;
}
}

View File

@ -0,0 +1,56 @@
// 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.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Diagnostics.HealthChecks;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace Microsoft.AspNetCore.Diagnostics.HealthChecks
{
public static class HealthCheckResponseWriters
{
public static Task WriteMinimalPlaintext(HttpContext httpContext, CompositeHealthCheckResult result)
{
if (httpContext == null)
{
throw new ArgumentNullException(nameof(httpContext));
}
if (result == null)
{
throw new ArgumentNullException(nameof(result));
}
httpContext.Response.ContentType = "text/plain";
return httpContext.Response.WriteAsync(result.Status.ToString());
}
public static Task WriteDetailedJson(HttpContext httpContext, CompositeHealthCheckResult result)
{
if (httpContext == null)
{
throw new ArgumentNullException(nameof(httpContext));
}
if (result == null)
{
throw new ArgumentNullException(nameof(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(pair.Key, new JObject(
new JProperty("status", pair.Value.Status.ToString()),
new JProperty("description", pair.Value.Description),
new JProperty("data", new JObject(pair.Value.Data.Select(p => new JProperty(p.Key, p.Value))))))))));
return httpContext.Response.WriteAsync(json.ToString(Formatting.Indented));
}
}
}

View File

@ -1,115 +1,5 @@
{
"AssemblyIdentity": "Microsoft.AspNetCore.Diagnostics.HealthChecks, Version=2.1.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60",
"Types": [
{
"Name": "Microsoft.AspNetCore.Diagnostics.HealthChecks.HealthCheckMiddleware",
"Visibility": "Public",
"Kind": "Class",
"ImplementedInterfaces": [],
"Members": [
{
"Kind": "Method",
"Name": "InvokeAsync",
"Parameters": [
{
"Name": "context",
"Type": "Microsoft.AspNetCore.Http.HttpContext"
}
],
"ReturnType": "System.Threading.Tasks.Task",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Constructor",
"Name": ".ctor",
"Parameters": [
{
"Name": "next",
"Type": "Microsoft.AspNetCore.Http.RequestDelegate"
},
{
"Name": "healthCheckOptions",
"Type": "Microsoft.Extensions.Options.IOptions<Microsoft.AspNetCore.Diagnostics.HealthChecks.HealthCheckOptions>"
},
{
"Name": "healthCheckService",
"Type": "Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheckService"
}
],
"Visibility": "Public",
"GenericParameter": []
}
],
"GenericParameters": []
},
{
"Name": "Microsoft.AspNetCore.Diagnostics.HealthChecks.HealthCheckOptions",
"Visibility": "Public",
"Kind": "Class",
"ImplementedInterfaces": [],
"Members": [
{
"Kind": "Method",
"Name": "get_Path",
"Parameters": [],
"ReturnType": "Microsoft.AspNetCore.Http.PathString",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "set_Path",
"Parameters": [
{
"Name": "value",
"Type": "Microsoft.AspNetCore.Http.PathString"
}
],
"ReturnType": "System.Void",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Constructor",
"Name": ".ctor",
"Parameters": [],
"Visibility": "Public",
"GenericParameter": []
}
],
"GenericParameters": []
},
{
"Name": "Microsoft.AspNetCore.Builder.HealthCheckAppBuilderExtensions",
"Visibility": "Public",
"Kind": "Class",
"Abstract": true,
"Static": true,
"Sealed": true,
"ImplementedInterfaces": [],
"Members": [
{
"Kind": "Method",
"Name": "UseHealthChecks",
"Parameters": [
{
"Name": "app",
"Type": "Microsoft.AspNetCore.Builder.IApplicationBuilder"
},
{
"Name": "path",
"Type": "Microsoft.AspNetCore.Http.PathString"
}
],
"ReturnType": "Microsoft.AspNetCore.Builder.IApplicationBuilder",
"Static": true,
"Extension": true,
"Visibility": "Public",
"GenericParameter": []
}
],
"GenericParameters": []
}
]
}

View File

@ -1,377 +1,5 @@
{
"AssemblyIdentity": "Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions, Version=2.1.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60",
"Types": [
{
"Name": "Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckResult",
"Visibility": "Public",
"Kind": "Struct",
"Sealed": true,
"ImplementedInterfaces": [],
"Members": [
{
"Kind": "Method",
"Name": "get_Status",
"Parameters": [],
"ReturnType": "Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckStatus",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "get_Exception",
"Parameters": [],
"ReturnType": "System.Exception",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "get_Description",
"Parameters": [],
"ReturnType": "System.String",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "get_Data",
"Parameters": [],
"ReturnType": "System.Collections.Generic.IReadOnlyDictionary<System.String, System.Object>",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "Unhealthy",
"Parameters": [],
"ReturnType": "Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckResult",
"Static": true,
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "Unhealthy",
"Parameters": [
{
"Name": "description",
"Type": "System.String"
}
],
"ReturnType": "Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckResult",
"Static": true,
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "Unhealthy",
"Parameters": [
{
"Name": "description",
"Type": "System.String"
},
{
"Name": "data",
"Type": "System.Collections.Generic.IReadOnlyDictionary<System.String, System.Object>"
}
],
"ReturnType": "Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckResult",
"Static": true,
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "Unhealthy",
"Parameters": [
{
"Name": "exception",
"Type": "System.Exception"
}
],
"ReturnType": "Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckResult",
"Static": true,
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "Unhealthy",
"Parameters": [
{
"Name": "description",
"Type": "System.String"
},
{
"Name": "exception",
"Type": "System.Exception"
}
],
"ReturnType": "Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckResult",
"Static": true,
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "Unhealthy",
"Parameters": [
{
"Name": "description",
"Type": "System.String"
},
{
"Name": "exception",
"Type": "System.Exception"
},
{
"Name": "data",
"Type": "System.Collections.Generic.IReadOnlyDictionary<System.String, System.Object>"
}
],
"ReturnType": "Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckResult",
"Static": true,
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "Healthy",
"Parameters": [],
"ReturnType": "Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckResult",
"Static": true,
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "Healthy",
"Parameters": [
{
"Name": "description",
"Type": "System.String"
}
],
"ReturnType": "Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckResult",
"Static": true,
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "Healthy",
"Parameters": [
{
"Name": "description",
"Type": "System.String"
},
{
"Name": "data",
"Type": "System.Collections.Generic.IReadOnlyDictionary<System.String, System.Object>"
}
],
"ReturnType": "Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckResult",
"Static": true,
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "Degraded",
"Parameters": [],
"ReturnType": "Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckResult",
"Static": true,
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "Degraded",
"Parameters": [
{
"Name": "description",
"Type": "System.String"
}
],
"ReturnType": "Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckResult",
"Static": true,
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "Degraded",
"Parameters": [
{
"Name": "description",
"Type": "System.String"
},
{
"Name": "data",
"Type": "System.Collections.Generic.IReadOnlyDictionary<System.String, System.Object>"
}
],
"ReturnType": "Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckResult",
"Static": true,
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "Degraded",
"Parameters": [
{
"Name": "exception",
"Type": "System.Exception"
}
],
"ReturnType": "Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckResult",
"Static": true,
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "Degraded",
"Parameters": [
{
"Name": "description",
"Type": "System.String"
},
{
"Name": "exception",
"Type": "System.Exception"
}
],
"ReturnType": "Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckResult",
"Static": true,
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "Degraded",
"Parameters": [
{
"Name": "description",
"Type": "System.String"
},
{
"Name": "exception",
"Type": "System.Exception"
},
{
"Name": "data",
"Type": "System.Collections.Generic.IReadOnlyDictionary<System.String, System.Object>"
}
],
"ReturnType": "Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckResult",
"Static": true,
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Constructor",
"Name": ".ctor",
"Parameters": [
{
"Name": "status",
"Type": "Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckStatus"
},
{
"Name": "exception",
"Type": "System.Exception"
},
{
"Name": "description",
"Type": "System.String"
},
{
"Name": "data",
"Type": "System.Collections.Generic.IReadOnlyDictionary<System.String, System.Object>"
}
],
"Visibility": "Public",
"GenericParameter": []
}
],
"GenericParameters": []
},
{
"Name": "Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckStatus",
"Visibility": "Public",
"Kind": "Enumeration",
"Sealed": true,
"ImplementedInterfaces": [],
"Members": [
{
"Kind": "Field",
"Name": "Unknown",
"Parameters": [],
"GenericParameter": [],
"Literal": "0"
},
{
"Kind": "Field",
"Name": "Failed",
"Parameters": [],
"GenericParameter": [],
"Literal": "1"
},
{
"Kind": "Field",
"Name": "Unhealthy",
"Parameters": [],
"GenericParameter": [],
"Literal": "2"
},
{
"Kind": "Field",
"Name": "Degraded",
"Parameters": [],
"GenericParameter": [],
"Literal": "3"
},
{
"Kind": "Field",
"Name": "Healthy",
"Parameters": [],
"GenericParameter": [],
"Literal": "4"
}
],
"GenericParameters": []
},
{
"Name": "Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck",
"Visibility": "Public",
"Kind": "Interface",
"Abstract": true,
"ImplementedInterfaces": [],
"Members": [
{
"Kind": "Method",
"Name": "get_Name",
"Parameters": [],
"ReturnType": "System.String",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "CheckHealthAsync",
"Parameters": [
{
"Name": "cancellationToken",
"Type": "System.Threading.CancellationToken",
"DefaultValue": "default(System.Threading.CancellationToken)"
}
],
"ReturnType": "System.Threading.Tasks.Task<Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckResult>",
"GenericParameter": []
}
],
"GenericParameters": []
}
]
}

View File

@ -11,15 +11,10 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks
/// A simple implementation of <see cref="IHealthCheck"/> which uses a provided delegate to
/// implement the check.
/// </summary>
public class HealthCheck : IHealthCheck
public sealed class HealthCheck : IHealthCheck
{
private readonly Func<CancellationToken, Task<HealthCheckResult>> _check;
/// <summary>
/// Gets the name of the health check, which should indicate the component being checked.
/// </summary>
public string Name { get; }
/// <summary>
/// Create an instance of <see cref="HealthCheck"/> from the specified <paramref name="name"/> and <paramref name="check"/>.
/// </summary>
@ -27,10 +22,15 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks
/// <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)
{
Name = name;
_check = 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>

View File

@ -22,8 +22,22 @@ namespace Microsoft.Extensions.DependencyInjection
/// <returns>The <see cref="IHealthChecksBuilder"/>.</returns>
public static IHealthChecksBuilder AddCheck(this IHealthChecksBuilder builder, string name, Func<CancellationToken, Task<HealthCheckResult>> check)
{
builder.Services.AddSingleton<IHealthCheck>(services => new HealthCheck(name, check));
return builder;
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>
@ -33,7 +47,46 @@ namespace Microsoft.Extensions.DependencyInjection
/// <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) =>
builder.AddCheck(name, _ => check());
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 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;
}
}
}

View File

@ -1,334 +1,5 @@
{
"AssemblyIdentity": "Microsoft.Extensions.Diagnostics.HealthChecks, Version=2.1.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60",
"Types": [
{
"Name": "Microsoft.Extensions.DependencyInjection.HealthChecksBuilderAddCheckExtensions",
"Visibility": "Public",
"Kind": "Class",
"Abstract": true,
"Static": true,
"Sealed": true,
"ImplementedInterfaces": [],
"Members": [
{
"Kind": "Method",
"Name": "AddCheck",
"Parameters": [
{
"Name": "builder",
"Type": "Microsoft.Extensions.Diagnostics.HealthChecks.IHealthChecksBuilder"
},
{
"Name": "name",
"Type": "System.String"
},
{
"Name": "check",
"Type": "System.Func<System.Threading.CancellationToken, System.Threading.Tasks.Task<Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckResult>>"
}
],
"ReturnType": "Microsoft.Extensions.Diagnostics.HealthChecks.IHealthChecksBuilder",
"Static": true,
"Extension": true,
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "AddCheck",
"Parameters": [
{
"Name": "builder",
"Type": "Microsoft.Extensions.Diagnostics.HealthChecks.IHealthChecksBuilder"
},
{
"Name": "name",
"Type": "System.String"
},
{
"Name": "check",
"Type": "System.Func<System.Threading.Tasks.Task<Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckResult>>"
}
],
"ReturnType": "Microsoft.Extensions.Diagnostics.HealthChecks.IHealthChecksBuilder",
"Static": true,
"Extension": true,
"Visibility": "Public",
"GenericParameter": []
}
],
"GenericParameters": []
},
{
"Name": "Microsoft.Extensions.DependencyInjection.HealthCheckServiceCollectionExtensions",
"Visibility": "Public",
"Kind": "Class",
"Abstract": true,
"Static": true,
"Sealed": true,
"ImplementedInterfaces": [],
"Members": [
{
"Kind": "Method",
"Name": "AddHealthChecks",
"Parameters": [
{
"Name": "services",
"Type": "Microsoft.Extensions.DependencyInjection.IServiceCollection"
}
],
"ReturnType": "Microsoft.Extensions.Diagnostics.HealthChecks.IHealthChecksBuilder",
"Static": true,
"Extension": true,
"Visibility": "Public",
"GenericParameter": []
}
],
"GenericParameters": []
},
{
"Name": "Microsoft.Extensions.Diagnostics.HealthChecks.CompositeHealthCheckResult",
"Visibility": "Public",
"Kind": "Class",
"ImplementedInterfaces": [],
"Members": [
{
"Kind": "Method",
"Name": "get_Results",
"Parameters": [],
"ReturnType": "System.Collections.Generic.IReadOnlyDictionary<System.String, Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckResult>",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "get_Status",
"Parameters": [],
"ReturnType": "Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckStatus",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Constructor",
"Name": ".ctor",
"Parameters": [
{
"Name": "results",
"Type": "System.Collections.Generic.IReadOnlyDictionary<System.String, Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckResult>"
}
],
"Visibility": "Public",
"GenericParameter": []
}
],
"GenericParameters": []
},
{
"Name": "Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheck",
"Visibility": "Public",
"Kind": "Class",
"ImplementedInterfaces": [
"Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck"
],
"Members": [
{
"Kind": "Method",
"Name": "get_Name",
"Parameters": [],
"ReturnType": "System.String",
"Sealed": true,
"Virtual": true,
"ImplementedInterface": "Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "CheckHealthAsync",
"Parameters": [
{
"Name": "cancellationToken",
"Type": "System.Threading.CancellationToken",
"DefaultValue": "default(System.Threading.CancellationToken)"
}
],
"ReturnType": "System.Threading.Tasks.Task<Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckResult>",
"Sealed": true,
"Virtual": true,
"ImplementedInterface": "Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Constructor",
"Name": ".ctor",
"Parameters": [
{
"Name": "name",
"Type": "System.String"
},
{
"Name": "check",
"Type": "System.Func<System.Threading.CancellationToken, System.Threading.Tasks.Task<Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckResult>>"
}
],
"Visibility": "Public",
"GenericParameter": []
}
],
"GenericParameters": []
},
{
"Name": "Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckService",
"Visibility": "Public",
"Kind": "Class",
"ImplementedInterfaces": [
"Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheckService"
],
"Members": [
{
"Kind": "Method",
"Name": "get_Checks",
"Parameters": [],
"ReturnType": "System.Collections.Generic.IReadOnlyDictionary<System.String, Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck>",
"Sealed": true,
"Virtual": true,
"ImplementedInterface": "Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheckService",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "CheckHealthAsync",
"Parameters": [
{
"Name": "cancellationToken",
"Type": "System.Threading.CancellationToken",
"DefaultValue": "default(System.Threading.CancellationToken)"
}
],
"ReturnType": "System.Threading.Tasks.Task<Microsoft.Extensions.Diagnostics.HealthChecks.CompositeHealthCheckResult>",
"Sealed": true,
"Virtual": true,
"ImplementedInterface": "Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheckService",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "CheckHealthAsync",
"Parameters": [
{
"Name": "checks",
"Type": "System.Collections.Generic.IEnumerable<Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck>"
},
{
"Name": "cancellationToken",
"Type": "System.Threading.CancellationToken",
"DefaultValue": "default(System.Threading.CancellationToken)"
}
],
"ReturnType": "System.Threading.Tasks.Task<Microsoft.Extensions.Diagnostics.HealthChecks.CompositeHealthCheckResult>",
"Sealed": true,
"Virtual": true,
"ImplementedInterface": "Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheckService",
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Constructor",
"Name": ".ctor",
"Parameters": [
{
"Name": "healthChecks",
"Type": "System.Collections.Generic.IEnumerable<Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck>"
}
],
"Visibility": "Public",
"GenericParameter": []
},
{
"Kind": "Constructor",
"Name": ".ctor",
"Parameters": [
{
"Name": "healthChecks",
"Type": "System.Collections.Generic.IEnumerable<Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck>"
},
{
"Name": "logger",
"Type": "Microsoft.Extensions.Logging.ILogger<Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckService>"
}
],
"Visibility": "Public",
"GenericParameter": []
}
],
"GenericParameters": []
},
{
"Name": "Microsoft.Extensions.Diagnostics.HealthChecks.IHealthChecksBuilder",
"Visibility": "Public",
"Kind": "Interface",
"Abstract": true,
"ImplementedInterfaces": [],
"Members": [
{
"Kind": "Method",
"Name": "get_Services",
"Parameters": [],
"ReturnType": "Microsoft.Extensions.DependencyInjection.IServiceCollection",
"GenericParameter": []
}
],
"GenericParameters": []
},
{
"Name": "Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheckService",
"Visibility": "Public",
"Kind": "Interface",
"Abstract": true,
"ImplementedInterfaces": [],
"Members": [
{
"Kind": "Method",
"Name": "get_Checks",
"Parameters": [],
"ReturnType": "System.Collections.Generic.IReadOnlyDictionary<System.String, Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck>",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "CheckHealthAsync",
"Parameters": [
{
"Name": "cancellationToken",
"Type": "System.Threading.CancellationToken",
"DefaultValue": "default(System.Threading.CancellationToken)"
}
],
"ReturnType": "System.Threading.Tasks.Task<Microsoft.Extensions.Diagnostics.HealthChecks.CompositeHealthCheckResult>",
"GenericParameter": []
},
{
"Kind": "Method",
"Name": "CheckHealthAsync",
"Parameters": [
{
"Name": "checks",
"Type": "System.Collections.Generic.IEnumerable<Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck>"
},
{
"Name": "cancellationToken",
"Type": "System.Threading.CancellationToken",
"DefaultValue": "default(System.Threading.CancellationToken)"
}
],
"ReturnType": "System.Threading.Tasks.Task<Microsoft.Extensions.Diagnostics.HealthChecks.CompositeHealthCheckResult>",
"GenericParameter": []
}
],
"GenericParameters": []
}
]
}

View File

@ -0,0 +1,127 @@
// 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.Net;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.TestHost;
using Newtonsoft.Json;
using Xunit;
namespace Microsoft.AspNetCore.Diagnostics.HealthChecks
{
public class HealthCheckMiddlewareSampleTest
{
[Fact]
public async Task BasicStartup()
{
var builder = new WebHostBuilder()
.UseStartup<HealthChecksSample.BasicStartup>();
var server = new TestServer(builder);
var client = server.CreateClient();
var response = await client.GetAsync("/health");
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Equal("text/plain", response.Content.Headers.ContentType.ToString());
Assert.Equal("Healthy", await response.Content.ReadAsStringAsync());
}
[Fact]
public async Task CustomWriterStartup()
{
var builder = new WebHostBuilder()
.UseStartup<HealthChecksSample.CustomWriterStartup>();
var server = new TestServer(builder);
var client = server.CreateClient();
var response = await client.GetAsync("/health");
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Equal("text/html", response.Content.Headers.ContentType.ToString());
// Ignoring the body since it contains a bunch of statistics
}
[Fact]
public async Task DetailedStatusStartup()
{
var builder = new WebHostBuilder()
.UseStartup<HealthChecksSample.DetailedStatusStartup>();
var server = new TestServer(builder);
var client = server.CreateClient();
var response = await client.GetAsync("/health");
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Equal("application/json", response.Content.Headers.ContentType.ToString());
// Ignoring the body since it contains a bunch of statistics
}
[Fact]
public async Task LivenessProbeStartup_Liveness()
{
var expectedJson = JsonConvert.SerializeObject(new
{
status = "Healthy",
results = new
{
identity = new
{
status = "Healthy",
description = "",
data = new { }
},
},
}, Formatting.Indented);
var builder = new WebHostBuilder()
.UseStartup<HealthChecksSample.LivenessProbeStartup>();
var server = new TestServer(builder);
var client = server.CreateClient();
var response = await client.GetAsync("/health/live");
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Equal("application/json", response.Content.Headers.ContentType.ToString());
Assert.Equal(expectedJson, await response.Content.ReadAsStringAsync());
}
[Fact]
public async Task LivenessProbeStartup_Readiness()
{
var expectedJson = JsonConvert.SerializeObject(new
{
status = "Unhealthy",
results = new
{
identity = new
{
status = "Healthy",
description = "",
data = new { }
},
slow_dependency = new
{
status = "Unhealthy",
description = "Dependency is still initializing",
data = new { }
},
},
}, Formatting.Indented);
var builder = new WebHostBuilder()
.UseStartup<HealthChecksSample.LivenessProbeStartup>();
var server = new TestServer(builder);
var client = server.CreateClient();
var response = await client.GetAsync("/health/ready");
Assert.Equal(HttpStatusCode.ServiceUnavailable, response.StatusCode);
Assert.Equal("application/json", response.Content.Headers.ContentType.ToString());
Assert.Equal(expectedJson, await response.Content.ReadAsStringAsync());
}
}
}

View File

@ -17,10 +17,8 @@ namespace Microsoft.AspNetCore.Diagnostics.HealthChecks
{
public class HealthCheckMiddlewareTests
{
[Theory]
[InlineData("/frob")]
[InlineData("/health/")] // Match is exact, for now at least
public async Task IgnoresRequestThatDoesNotMatchPath(string requestPath)
[Fact] // Matches based on '.Map'
public async Task IgnoresRequestThatDoesNotMatchPath()
{
var builder = new WebHostBuilder()
.Configure(app =>
@ -34,26 +32,190 @@ namespace Microsoft.AspNetCore.Diagnostics.HealthChecks
var server = new TestServer(builder);
var client = server.CreateClient();
var response = await client.GetAsync(requestPath);
var response = await client.GetAsync("/frob");
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
[Theory]
[InlineData("/health")]
[InlineData("/Health")]
[InlineData("/HEALTH")]
public async Task ReturnsEmptyHealthyRequestIfNoHealthChecksRegistered(string requestPath)
[Fact] // Matches based on '.Map'
public async Task MatchIsCaseInsensitive()
{
var builder = new WebHostBuilder()
.Configure(app =>
{
app.UseHealthChecks("/health");
})
.ConfigureServices(services =>
{
services.AddHealthChecks();
});
var server = new TestServer(builder);
var client = server.CreateClient();
var response = await client.GetAsync("/HEALTH");
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
}
[Fact]
public async Task ReturnsPlainTextStatus()
{
var builder = new WebHostBuilder()
.Configure(app =>
{
app.UseHealthChecks("/health");
})
.ConfigureServices(services =>
{
services.AddHealthChecks();
});
var server = new TestServer(builder);
var client = server.CreateClient();
var response = await client.GetAsync("/health");
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Equal("text/plain", response.Content.Headers.ContentType.ToString());
Assert.Equal("Healthy", await response.Content.ReadAsStringAsync());
}
[Fact]
public async Task StatusCodeIs200IfNoChecks()
{
var builder = new WebHostBuilder()
.Configure(app =>
{
app.UseHealthChecks("/health");
})
.ConfigureServices(services =>
{
services.AddHealthChecks();
});
var server = new TestServer(builder);
var client = server.CreateClient();
var response = await client.GetAsync("/health");
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Equal("text/plain", response.Content.Headers.ContentType.ToString());
Assert.Equal("Healthy", await response.Content.ReadAsStringAsync());
}
[Fact]
public async Task StatusCodeIs200IfAllChecksHealthy()
{
var builder = new WebHostBuilder()
.Configure(app =>
{
app.UseHealthChecks("/health");
})
.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!")));
});
var server = new TestServer(builder);
var client = server.CreateClient();
var response = await client.GetAsync("/health");
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Equal("text/plain", response.Content.Headers.ContentType.ToString());
Assert.Equal("Healthy", await response.Content.ReadAsStringAsync());
}
[Fact]
public async Task StatusCodeIs200IfCheckIsDegraded()
{
var builder = new WebHostBuilder()
.Configure(app =>
{
app.UseHealthChecks("/health");
})
.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!")));
});
var server = new TestServer(builder);
var client = server.CreateClient();
var response = await client.GetAsync("/health");
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Equal("text/plain", response.Content.Headers.ContentType.ToString());
Assert.Equal("Degraded", await response.Content.ReadAsStringAsync());
}
[Fact]
public async Task StatusCodeIs503IfCheckIsUnhealthy()
{
var builder = new WebHostBuilder()
.Configure(app =>
{
app.UseHealthChecks("/health");
})
.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!")));
});
var server = new TestServer(builder);
var client = server.CreateClient();
var response = await client.GetAsync("/health");
Assert.Equal(HttpStatusCode.ServiceUnavailable, response.StatusCode);
Assert.Equal("text/plain", response.Content.Headers.ContentType.ToString());
Assert.Equal("Unhealthy", await response.Content.ReadAsStringAsync());
}
[Fact]
public async Task StatusCodeIs500IfCheckIsFailed()
{
var builder = new WebHostBuilder()
.Configure(app =>
{
app.UseHealthChecks("/health");
})
.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!")));
});
var server = new TestServer(builder);
var client = server.CreateClient();
var response = await client.GetAsync("/health");
Assert.Equal(HttpStatusCode.InternalServerError, response.StatusCode);
Assert.Equal("text/plain", response.Content.Headers.ContentType.ToString());
Assert.Equal("Failed", await response.Content.ReadAsStringAsync());
}
[Fact]
public async Task DetailedJsonReturnsEmptyHealthyResponseIfNoHealthChecksRegistered()
{
var expectedJson = JsonConvert.SerializeObject(new
{
status = "Healthy",
results = new { }
}, Formatting.None);
}, Formatting.Indented);
var builder = new WebHostBuilder()
.Configure(app =>
{
app.UseHealthChecks("/health");
app.UseHealthChecks("/health", new HealthCheckOptions()
{
ResponseWriter = HealthCheckResponseWriters.WriteDetailedJson,
});
})
.ConfigureServices(services =>
{
@ -62,14 +224,14 @@ namespace Microsoft.AspNetCore.Diagnostics.HealthChecks
var server = new TestServer(builder);
var client = server.CreateClient();
var response = await client.GetAsync(requestPath);
var response = await client.GetAsync("/health");
var result = await response.Content.ReadAsStringAsync();
Assert.Equal(expectedJson, result);
}
[Fact]
public async Task ReturnsResultsFromHealthChecks()
public async Task DetailedJsonReturnsResultsFromHealthChecks()
{
var expectedJson = JsonConvert.SerializeObject(new
{
@ -101,12 +263,15 @@ namespace Microsoft.AspNetCore.Diagnostics.HealthChecks
data = new { }
},
},
}, Formatting.None);
}, Formatting.Indented);
var builder = new WebHostBuilder()
.Configure(app =>
{
app.UseHealthChecks("/health");
app.UseHealthChecks("/health", new HealthCheckOptions()
{
ResponseWriter = HealthCheckResponseWriters.WriteDetailedJson,
});
})
.ConfigureServices(services =>
{
@ -129,78 +294,15 @@ namespace Microsoft.AspNetCore.Diagnostics.HealthChecks
}
[Fact]
public async Task StatusCodeIs200IfNoChecks()
public async Task NoResponseWriterReturnsEmptyBody()
{
var builder = new WebHostBuilder()
.Configure(app =>
{
app.UseHealthChecks("/health");
})
.ConfigureServices(services =>
{
services.AddHealthChecks();
});
var server = new TestServer(builder);
var client = server.CreateClient();
var response = await client.GetAsync("/health");
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
}
[Fact]
public async Task StatusCodeIs200IfAllChecksHealthy()
{
var builder = new WebHostBuilder()
.Configure(app =>
{
app.UseHealthChecks("/health");
})
.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!")));
});
var server = new TestServer(builder);
var client = server.CreateClient();
var response = await client.GetAsync("/health");
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
}
[Fact]
public async Task StatusCodeIs200IfCheckIsDegraded()
{
var builder = new WebHostBuilder()
.Configure(app =>
{
app.UseHealthChecks("/health");
})
.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!")));
});
var server = new TestServer(builder);
var client = server.CreateClient();
var response = await client.GetAsync("/health");
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
}
[Fact]
public async Task StatusCodeIs503IfCheckIsUnhealthy()
{
var builder = new WebHostBuilder()
.Configure(app =>
{
app.UseHealthChecks("/health");
app.UseHealthChecks("/health", new HealthCheckOptions()
{
ResponseWriter = null,
});
})
.ConfigureServices(services =>
{
@ -215,21 +317,57 @@ namespace Microsoft.AspNetCore.Diagnostics.HealthChecks
var response = await client.GetAsync("/health");
Assert.Equal(HttpStatusCode.ServiceUnavailable, response.StatusCode);
Assert.Equal(string.Empty, await response.Content.ReadAsStringAsync());
}
[Fact]
public async Task StatusCodeIs500IfCheckIsFailed()
public async Task CanSetCustomStatusCodes()
{
var builder = new WebHostBuilder()
.Configure(app =>
{
app.UseHealthChecks("/health");
app.UseHealthChecks("/health", new HealthCheckOptions()
{
ResultStatusCodes =
{
[HealthCheckStatus.Healthy] = 201,
}
});
})
.ConfigureServices(services =>
{
services.AddHealthChecks();
});
var server = new TestServer(builder);
var client = server.CreateClient();
var response = await client.GetAsync("/health");
Assert.Equal(HttpStatusCode.Created, response.StatusCode);
Assert.Equal("Healthy", await response.Content.ReadAsStringAsync());
}
[Fact]
public async Task CanFilterChecks()
{
var builder = new WebHostBuilder()
.Configure(app =>
{
app.UseHealthChecks("/health", new HealthCheckOptions()
{
HealthCheckNames =
{
"Baz",
"FOO",
},
});
})
.ConfigureServices(services =>
{
services.AddHealthChecks()
.AddCheck("Foo", () => Task.FromResult(HealthCheckResult.Healthy("A-ok!")))
.AddCheck("Bar", () => Task.FromResult(new HealthCheckResult(HealthCheckStatus.Failed, null, null, null)))
// Will get filtered out
.AddCheck("Bar", () => Task.FromResult(HealthCheckResult.Unhealthy("A-ok!")))
.AddCheck("Baz", () => Task.FromResult(HealthCheckResult.Healthy("A-ok!")));
});
var server = new TestServer(builder);
@ -237,7 +375,38 @@ namespace Microsoft.AspNetCore.Diagnostics.HealthChecks
var response = await client.GetAsync("/health");
Assert.Equal(HttpStatusCode.InternalServerError, response.StatusCode);
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Equal("text/plain", response.Content.Headers.ContentType.ToString());
Assert.Equal("Healthy", await response.Content.ReadAsStringAsync());
}
[Fact]
public void CanFilterChecks_ThrowsForMissingCheck()
{
var builder = new WebHostBuilder()
.Configure(app =>
{
app.UseHealthChecks("/health", new HealthCheckOptions()
{
HealthCheckNames =
{
"Bazzzzzz",
"FOO",
},
});
})
.ConfigureServices(services =>
{
services.AddHealthChecks()
.AddCheck("Foo", () => Task.FromResult(HealthCheckResult.Healthy("A-ok!")))
.AddCheck("Bar", () => Task.FromResult(HealthCheckResult.Unhealthy("A-ok!")))
.AddCheck("Baz", () => Task.FromResult(HealthCheckResult.Healthy("A-ok!")));
});
var ex = Assert.Throws<InvalidOperationException>(() => new TestServer(builder));
Assert.Equal(
"The following health checks were not found: 'Bazzzzzz'. Registered health checks: 'Foo, Bar, Baz'.",
ex.Message);
}
}
}

View File

@ -14,6 +14,7 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\samples\HealthChecksSample\HealthChecksSample.csproj" />
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.Diagnostics.HealthChecks\Microsoft.AspNetCore.Diagnostics.HealthChecks.csproj" />
</ItemGroup>