Integrate WebStatus with Healthcheckpull/899/head
@ -1,76 +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.AspNetCore.Http; | |||||
using Microsoft.AspNetCore.Http.Features; | |||||
using Microsoft.Extensions.HealthChecks; | |||||
using Newtonsoft.Json; | |||||
namespace Microsoft.AspNetCore.HealthChecks | |||||
{ | |||||
public class HealthCheckMiddleware | |||||
{ | |||||
private readonly RequestDelegate _next; | |||||
private readonly string _path; | |||||
private readonly int? _port; | |||||
private readonly IHealthCheckService _service; | |||||
private readonly TimeSpan _timeout; | |||||
public HealthCheckMiddleware(RequestDelegate next, IHealthCheckService service, int port, TimeSpan timeout) | |||||
{ | |||||
_port = port; | |||||
_service = service; | |||||
_next = next; | |||||
_timeout = timeout; | |||||
} | |||||
public HealthCheckMiddleware(RequestDelegate next, IHealthCheckService service, string path, TimeSpan timeout) | |||||
{ | |||||
_path = path; | |||||
_service = service; | |||||
_next = next; | |||||
_timeout = timeout; | |||||
} | |||||
public async Task Invoke(HttpContext context) | |||||
{ | |||||
if (IsHealthCheckRequest(context)) | |||||
{ | |||||
var timeoutTokenSource = new CancellationTokenSource(_timeout); | |||||
var result = await _service.CheckHealthAsync(timeoutTokenSource.Token); | |||||
var status = result.CheckStatus; | |||||
if (status != CheckStatus.Healthy) | |||||
context.Response.StatusCode = 503; | |||||
context.Response.Headers.Add("content-type", "application/json"); | |||||
await context.Response.WriteAsync(JsonConvert.SerializeObject(new { status = status.ToString() })); | |||||
return; | |||||
} | |||||
else | |||||
{ | |||||
await _next.Invoke(context); | |||||
} | |||||
} | |||||
private bool IsHealthCheckRequest(HttpContext context) | |||||
{ | |||||
if (_port.HasValue) | |||||
{ | |||||
var connInfo = context.Features.Get<IHttpConnectionFeature>(); | |||||
if (connInfo.LocalPort == _port) | |||||
return true; | |||||
} | |||||
if (context.Request.Path == _path) | |||||
{ | |||||
return true; | |||||
} | |||||
return false; | |||||
} | |||||
} | |||||
} |
@ -1,45 +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.Builder; | |||||
using Microsoft.AspNetCore.Hosting; | |||||
namespace Microsoft.AspNetCore.HealthChecks | |||||
{ | |||||
public class HealthCheckStartupFilter : IStartupFilter | |||||
{ | |||||
private string _path; | |||||
private int? _port; | |||||
private TimeSpan _timeout; | |||||
public HealthCheckStartupFilter(int port, TimeSpan timeout) | |||||
{ | |||||
_port = port; | |||||
_timeout = timeout; | |||||
} | |||||
public HealthCheckStartupFilter(string path, TimeSpan timeout) | |||||
{ | |||||
_path = path; | |||||
_timeout = timeout; | |||||
} | |||||
public Action<IApplicationBuilder> Configure(Action<IApplicationBuilder> next) | |||||
{ | |||||
return app => | |||||
{ | |||||
if (_port.HasValue) | |||||
{ | |||||
app.UseMiddleware<HealthCheckMiddleware>(_port, _timeout); | |||||
} | |||||
else | |||||
{ | |||||
app.UseMiddleware<HealthCheckMiddleware>(_path, _timeout); | |||||
} | |||||
next(app); | |||||
}; | |||||
} | |||||
} | |||||
} |
@ -1,47 +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.HealthChecks; | |||||
using Microsoft.Extensions.DependencyInjection; | |||||
namespace Microsoft.AspNetCore.Hosting | |||||
{ | |||||
public static class HealthCheckWebHostBuilderExtension | |||||
{ | |||||
public static readonly TimeSpan DefaultTimeout = TimeSpan.FromSeconds(10); | |||||
public static IWebHostBuilder UseHealthChecks(this IWebHostBuilder builder, int port) | |||||
=> UseHealthChecks(builder, port, DefaultTimeout); | |||||
public static IWebHostBuilder UseHealthChecks(this IWebHostBuilder builder, int port, TimeSpan timeout) | |||||
{ | |||||
Guard.ArgumentValid(port > 0 && port < 65536, nameof(port), "Port must be a value between 1 and 65535."); | |||||
Guard.ArgumentValid(timeout > TimeSpan.Zero, nameof(timeout), "Health check timeout must be a positive time span."); | |||||
builder.ConfigureServices(services => | |||||
{ | |||||
var existingUrl = builder.GetSetting(WebHostDefaults.ServerUrlsKey); | |||||
builder.UseSetting(WebHostDefaults.ServerUrlsKey, $"{existingUrl};http://localhost:{port}"); | |||||
services.AddSingleton<IStartupFilter>(new HealthCheckStartupFilter(port, timeout)); | |||||
}); | |||||
return builder; | |||||
} | |||||
public static IWebHostBuilder UseHealthChecks(this IWebHostBuilder builder, string path) | |||||
=> UseHealthChecks(builder, path, DefaultTimeout); | |||||
public static IWebHostBuilder UseHealthChecks(this IWebHostBuilder builder, string path, TimeSpan timeout) | |||||
{ | |||||
Guard.ArgumentNotNull(nameof(path), path); | |||||
// REVIEW: Is there a better URL path validator somewhere? | |||||
Guard.ArgumentValid(!path.Contains("?"), nameof(path), "Path cannot contain query string values."); | |||||
Guard.ArgumentValid(path.StartsWith("/"), nameof(path), "Path should start with '/'."); | |||||
Guard.ArgumentValid(timeout > TimeSpan.Zero, nameof(timeout), "Health check timeout must be a positive time span."); | |||||
builder.ConfigureServices(services => services.AddSingleton<IStartupFilter>(new HealthCheckStartupFilter(path, timeout))); | |||||
return builder; | |||||
} | |||||
} | |||||
} |
@ -1,38 +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.Extensions.HealthChecks; | |||||
namespace Microsoft.AspNetCore.Hosting | |||||
{ | |||||
public static class HealthCheckWebHostExtensions | |||||
{ | |||||
private const int DEFAULT_TIMEOUT_SECONDS = 300; | |||||
public static void RunWhenHealthy(this IWebHost webHost) | |||||
{ | |||||
webHost.RunWhenHealthy(TimeSpan.FromSeconds(DEFAULT_TIMEOUT_SECONDS)); | |||||
} | |||||
public static void RunWhenHealthy(this IWebHost webHost, TimeSpan timeout) | |||||
{ | |||||
var healthChecks = webHost.Services.GetService(typeof(IHealthCheckService)) as IHealthCheckService; | |||||
var loops = 0; | |||||
do | |||||
{ | |||||
var checkResult = healthChecks.CheckHealthAsync().Result; | |||||
if (checkResult.CheckStatus == CheckStatus.Healthy) | |||||
{ | |||||
webHost.Run(); | |||||
break; | |||||
} | |||||
System.Threading.Thread.Sleep(1000); | |||||
loops++; | |||||
} while (loops < timeout.TotalSeconds); | |||||
} | |||||
} | |||||
} |
@ -1,19 +0,0 @@ | |||||
<Project Sdk="Microsoft.NET.Sdk"> | |||||
<PropertyGroup> | |||||
<TargetFramework>netstandard2.0</TargetFramework> | |||||
</PropertyGroup> | |||||
<ItemGroup> | |||||
<Compile Include="..\common\Guard.cs" Link="Internal\Guard.cs" /> | |||||
</ItemGroup> | |||||
<ItemGroup> | |||||
<ProjectReference Include="..\Microsoft.Extensions.HealthChecks\Microsoft.Extensions.HealthChecks.csproj" /> | |||||
</ItemGroup> | |||||
<ItemGroup> | |||||
<PackageReference Include="Microsoft.AspNetCore.Hosting" Version="2.2.0-preview3-35497" /> | |||||
</ItemGroup> | |||||
</Project> |
@ -1,175 +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.WindowsAzure.Storage; | |||||
using Microsoft.WindowsAzure.Storage.Auth; | |||||
namespace Microsoft.Extensions.HealthChecks | |||||
{ | |||||
// REVIEW: Do we want these to continue to use default parameters? | |||||
// REVIEW: What are the appropriate guards for these functions? | |||||
public static class AzureHealthCheckBuilderExtensions | |||||
{ | |||||
public static HealthCheckBuilder AddAzureBlobStorageCheck(this HealthCheckBuilder builder, string accountName, string accountKey, string containerName = null, TimeSpan? cacheDuration = null) | |||||
{ | |||||
var credentials = new StorageCredentials(accountName, accountKey); | |||||
var storageAccount = new CloudStorageAccount(credentials, true); | |||||
return AddAzureBlobStorageCheck(builder, storageAccount, containerName, cacheDuration); | |||||
} | |||||
public static HealthCheckBuilder AddAzureBlobStorageCheck(HealthCheckBuilder builder, CloudStorageAccount storageAccount, string containerName = null, TimeSpan? cacheDuration = null) | |||||
{ | |||||
builder.AddCheck($"AzureBlobStorageCheck {storageAccount.BlobStorageUri} {containerName}", async () => | |||||
{ | |||||
bool result; | |||||
try | |||||
{ | |||||
var blobClient = storageAccount.CreateCloudBlobClient(); | |||||
var properties = await blobClient.GetServicePropertiesAsync().ConfigureAwait(false); | |||||
if (!String.IsNullOrWhiteSpace(containerName)) | |||||
{ | |||||
var container = blobClient.GetContainerReference(containerName); | |||||
result = await container.ExistsAsync(); | |||||
} | |||||
result = true; | |||||
} | |||||
catch (Exception) | |||||
{ | |||||
result = false; | |||||
} | |||||
return result | |||||
? HealthCheckResult.Healthy($"AzureBlobStorage {storageAccount.BlobStorageUri} is available") | |||||
: HealthCheckResult.Unhealthy($"AzureBlobStorage {storageAccount.BlobStorageUri} is unavailable"); | |||||
}, cacheDuration ?? builder.DefaultCacheDuration); | |||||
return builder; | |||||
} | |||||
public static HealthCheckBuilder AddAzureTableStorageCheck(this HealthCheckBuilder builder, string accountName, string accountKey, string tableName = null, TimeSpan? cacheDuration = null) | |||||
{ | |||||
var credentials = new StorageCredentials(accountName, accountKey); | |||||
var storageAccount = new CloudStorageAccount(credentials, true); | |||||
return AddAzureTableStorageCheck(builder, storageAccount, tableName, cacheDuration); | |||||
} | |||||
public static HealthCheckBuilder AddAzureTableStorageCheck(HealthCheckBuilder builder, CloudStorageAccount storageAccount, string tableName = null, TimeSpan? cacheDuration = null) | |||||
{ | |||||
builder.AddCheck($"AzureTableStorageCheck {storageAccount.TableStorageUri} {tableName}", async () => | |||||
{ | |||||
bool result; | |||||
try | |||||
{ | |||||
var tableClient = storageAccount.CreateCloudTableClient(); | |||||
var properties = await tableClient.GetServicePropertiesAsync().ConfigureAwait(false); | |||||
if (!String.IsNullOrWhiteSpace(tableName)) | |||||
{ | |||||
var table = tableClient.GetTableReference(tableName); | |||||
result = await table.ExistsAsync(); | |||||
} | |||||
result = true; | |||||
} | |||||
catch (Exception) | |||||
{ | |||||
result = false; | |||||
} | |||||
return result | |||||
? HealthCheckResult.Healthy($"AzureTableStorage {storageAccount.BlobStorageUri} is available") | |||||
: HealthCheckResult.Unhealthy($"AzureTableStorage {storageAccount.BlobStorageUri} is unavailable"); | |||||
}, cacheDuration ?? builder.DefaultCacheDuration); | |||||
return builder; | |||||
} | |||||
public static HealthCheckBuilder AddAzureFileStorageCheck(this HealthCheckBuilder builder, string accountName, string accountKey, string shareName = null, TimeSpan? cacheDuration = null) | |||||
{ | |||||
var credentials = new StorageCredentials(accountName, accountKey); | |||||
var storageAccount = new CloudStorageAccount(credentials, true); | |||||
return AddAzureFileStorageCheck(builder, storageAccount, shareName, cacheDuration); | |||||
} | |||||
public static HealthCheckBuilder AddAzureFileStorageCheck(HealthCheckBuilder builder, CloudStorageAccount storageAccount, string shareName = null, TimeSpan? cacheDuration = null) | |||||
{ | |||||
builder.AddCheck($"AzureFileStorageCheck {storageAccount.FileStorageUri} {shareName}", async () => | |||||
{ | |||||
bool result; | |||||
try | |||||
{ | |||||
var fileClient = storageAccount.CreateCloudFileClient(); | |||||
var properties = await fileClient.GetServicePropertiesAsync().ConfigureAwait(false); | |||||
if (!String.IsNullOrWhiteSpace(shareName)) | |||||
{ | |||||
var share = fileClient.GetShareReference(shareName); | |||||
result = await share.ExistsAsync(); | |||||
} | |||||
result = true; | |||||
} | |||||
catch (Exception) | |||||
{ | |||||
result = false; | |||||
} | |||||
return result | |||||
? HealthCheckResult.Healthy($"AzureFileStorage {storageAccount.BlobStorageUri} is available") | |||||
: HealthCheckResult.Unhealthy($"AzureFileStorage {storageAccount.BlobStorageUri} is unavailable"); | |||||
}, cacheDuration ?? builder.DefaultCacheDuration); | |||||
return builder; | |||||
} | |||||
public static HealthCheckBuilder AddAzureQueueStorageCheck(this HealthCheckBuilder builder, string accountName, string accountKey, string queueName = null, TimeSpan? cacheDuration = null) | |||||
{ | |||||
var credentials = new StorageCredentials(accountName, accountKey); | |||||
var storageAccount = new CloudStorageAccount(credentials, true); | |||||
return AddAzureQueueStorageCheck(builder, storageAccount, queueName, cacheDuration); | |||||
} | |||||
public static HealthCheckBuilder AddAzureQueueStorageCheck(HealthCheckBuilder builder, CloudStorageAccount storageAccount, string queueName = null, TimeSpan? cacheDuration = null) | |||||
{ | |||||
builder.AddCheck($"AzureQueueStorageCheck {storageAccount.QueueStorageUri} {queueName}", async () => | |||||
{ | |||||
bool result; | |||||
try | |||||
{ | |||||
var queueClient = storageAccount.CreateCloudQueueClient(); | |||||
var properties = await queueClient.GetServicePropertiesAsync().ConfigureAwait(false); | |||||
if (!String.IsNullOrWhiteSpace(queueName)) | |||||
{ | |||||
var queue = queueClient.GetQueueReference(queueName); | |||||
result = await queue.ExistsAsync(); | |||||
} | |||||
result = true; | |||||
} | |||||
catch (Exception) | |||||
{ | |||||
result = false; | |||||
} | |||||
return result | |||||
? HealthCheckResult.Healthy($"AzureFileStorage {storageAccount.BlobStorageUri} is available") | |||||
: HealthCheckResult.Unhealthy($"AzureFileStorage {storageAccount.BlobStorageUri} is unavailable"); | |||||
}, cacheDuration ?? builder.DefaultCacheDuration); | |||||
return builder; | |||||
} | |||||
} | |||||
} |
@ -1,26 +0,0 @@ | |||||
<Project Sdk="Microsoft.NET.Sdk"> | |||||
<PropertyGroup> | |||||
<TargetFramework>netstandard2.0</TargetFramework> | |||||
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute> | |||||
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute> | |||||
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute> | |||||
</PropertyGroup> | |||||
<ItemGroup> | |||||
<Compile Include="..\common\Guard.cs" Link="Internal\Guard.cs" /> | |||||
</ItemGroup> | |||||
<ItemGroup> | |||||
<ProjectReference Include="..\Microsoft.Extensions.HealthChecks\Microsoft.Extensions.HealthChecks.csproj" /> | |||||
</ItemGroup> | |||||
<ItemGroup> | |||||
<PackageReference Include="Microsoft.AspNetCore.Hosting" Version="2.2.0-preview3-35497" /> | |||||
<PackageReference Include="System.Threading.Tasks.Parallel" Version="4.3.0" /> | |||||
<PackageReference Include="System.Threading.Thread" Version="4.3.0" /> | |||||
<PackageReference Include="System.Threading.Tasks.Extensions" Version="4.5.0" /> | |||||
<PackageReference Include="WindowsAzure.Storage" Version="9.1.0" /> | |||||
</ItemGroup> | |||||
</Project> |
@ -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 System.Reflection; | |||||
using System.Runtime.CompilerServices; | |||||
using System.Runtime.InteropServices; | |||||
// General Information about an assembly is controlled through the following | |||||
// set of attributes. Change these attribute values to modify the information | |||||
// associated with an assembly. | |||||
[assembly: AssemblyConfiguration("")] | |||||
[assembly: AssemblyCompany("")] | |||||
[assembly: AssemblyProduct("HealthChecks.Azure")] | |||||
[assembly: AssemblyTrademark("")] | |||||
// Setting ComVisible to false makes the types in this assembly not visible | |||||
// to COM components. If you need to access a type in this assembly from | |||||
// COM, set the ComVisible attribute to true on that type. | |||||
[assembly: ComVisible(false)] | |||||
// The following GUID is for the ID of the typelib if this project is exposed to COM | |||||
[assembly: Guid("0c4158b7-7153-4d2e-abfa-4ce07d44f75f")] |
@ -1,54 +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.Data; | |||||
using System.Data.SqlClient; | |||||
namespace Microsoft.Extensions.HealthChecks | |||||
{ | |||||
// REVIEW: What are the appropriate guards for these functions? | |||||
public static class HealthCheckBuilderSqlServerExtensions | |||||
{ | |||||
public static HealthCheckBuilder AddSqlCheck(this HealthCheckBuilder builder, string name, string connectionString) | |||||
{ | |||||
Guard.ArgumentNotNull(nameof(builder), builder); | |||||
return AddSqlCheck(builder, name, connectionString, builder.DefaultCacheDuration); | |||||
} | |||||
public static HealthCheckBuilder AddSqlCheck(this HealthCheckBuilder builder, string name, string connectionString, TimeSpan cacheDuration) | |||||
{ | |||||
builder.AddCheck($"SqlCheck({name})", async () => | |||||
{ | |||||
try | |||||
{ | |||||
//TODO: There is probably a much better way to do this. | |||||
using (var connection = new SqlConnection(connectionString)) | |||||
{ | |||||
connection.Open(); | |||||
using (var command = connection.CreateCommand()) | |||||
{ | |||||
command.CommandType = CommandType.Text; | |||||
command.CommandText = "SELECT 1"; | |||||
var result = (int)await command.ExecuteScalarAsync().ConfigureAwait(false); | |||||
if (result == 1) | |||||
{ | |||||
return HealthCheckResult.Healthy($"SqlCheck({name}): Healthy"); | |||||
} | |||||
return HealthCheckResult.Unhealthy($"SqlCheck({name}): Unhealthy"); | |||||
} | |||||
} | |||||
} | |||||
catch (Exception ex) | |||||
{ | |||||
return HealthCheckResult.Unhealthy($"SqlCheck({name}): Exception during check: {ex.GetType().FullName}"); | |||||
} | |||||
}, cacheDuration); | |||||
return builder; | |||||
} | |||||
} | |||||
} |
@ -1,19 +0,0 @@ | |||||
<Project Sdk="Microsoft.NET.Sdk"> | |||||
<PropertyGroup> | |||||
<TargetFramework>netstandard2.0</TargetFramework> | |||||
</PropertyGroup> | |||||
<ItemGroup> | |||||
<Compile Include="..\common\Guard.cs" Link="Internal\Guard.cs" /> | |||||
</ItemGroup> | |||||
<ItemGroup> | |||||
<PackageReference Include="System.Data.SqlClient" Version="4.6.0-preview3-27014-02" /> | |||||
</ItemGroup> | |||||
<ItemGroup> | |||||
<ProjectReference Include="..\Microsoft.Extensions.HealthChecks\Microsoft.Extensions.HealthChecks.csproj" /> | |||||
</ItemGroup> | |||||
</Project> |
@ -1,109 +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.Reflection; | |||||
using System.Threading; | |||||
using System.Threading.Tasks; | |||||
using Microsoft.Extensions.DependencyInjection; | |||||
namespace Microsoft.Extensions.HealthChecks | |||||
{ | |||||
public abstract class CachedHealthCheck | |||||
{ | |||||
private static readonly TypeInfo HealthCheckTypeInfo = typeof(IHealthCheck).GetTypeInfo(); | |||||
private volatile int _writerCount; | |||||
public CachedHealthCheck(string name, TimeSpan cacheDuration) | |||||
{ | |||||
Guard.ArgumentNotNullOrEmpty(nameof(name), name); | |||||
Guard.ArgumentValid(cacheDuration.TotalMilliseconds >= 0, nameof(cacheDuration), "Cache duration must be zero (disabled) or greater than zero."); | |||||
Name = name; | |||||
CacheDuration = cacheDuration; | |||||
} | |||||
public IHealthCheckResult CachedResult { get; internal set; } | |||||
public TimeSpan CacheDuration { get; } | |||||
public DateTimeOffset CacheExpiration { get; internal set; } | |||||
public string Name { get; } | |||||
protected virtual DateTimeOffset UtcNow => DateTimeOffset.UtcNow; | |||||
protected abstract IHealthCheck Resolve(IServiceProvider serviceProvider); | |||||
public async ValueTask<IHealthCheckResult> RunAsync(IServiceProvider serviceProvider, CancellationToken cancellationToken = default(CancellationToken)) | |||||
{ | |||||
while (CacheExpiration <= UtcNow) | |||||
{ | |||||
// Can't use a standard lock here because of async, so we'll use this flag to determine when we should write a value, | |||||
// and the waiters who aren't allowed to write will just spin wait for the new value. | |||||
if (Interlocked.Exchange(ref _writerCount, 1) != 0) | |||||
{ | |||||
await Task.Delay(5, cancellationToken).ConfigureAwait(false); | |||||
continue; | |||||
} | |||||
try | |||||
{ | |||||
var check = Resolve(serviceProvider); | |||||
CachedResult = await check.CheckAsync(cancellationToken); | |||||
} | |||||
catch (OperationCanceledException) | |||||
{ | |||||
CachedResult = HealthCheckResult.Unhealthy("The health check operation timed out"); | |||||
} | |||||
catch (Exception ex) | |||||
{ | |||||
CachedResult = HealthCheckResult.Unhealthy($"Exception during check: {ex.GetType().FullName}"); | |||||
} | |||||
CacheExpiration = UtcNow + CacheDuration; | |||||
_writerCount = 0; | |||||
break; | |||||
} | |||||
return CachedResult; | |||||
} | |||||
public static CachedHealthCheck FromHealthCheck(string name, TimeSpan cacheDuration, IHealthCheck healthCheck) | |||||
{ | |||||
Guard.ArgumentNotNull(nameof(healthCheck), healthCheck); | |||||
return new TypeOrHealthCheck_HealthCheck(name, cacheDuration, healthCheck); | |||||
} | |||||
public static CachedHealthCheck FromType(string name, TimeSpan cacheDuration, Type healthCheckType) | |||||
{ | |||||
Guard.ArgumentNotNull(nameof(healthCheckType), healthCheckType); | |||||
Guard.ArgumentValid(HealthCheckTypeInfo.IsAssignableFrom(healthCheckType.GetTypeInfo()), nameof(healthCheckType), $"Health check must implement '{typeof(IHealthCheck).FullName}'."); | |||||
return new TypeOrHealthCheck_Type(name, cacheDuration, healthCheckType); | |||||
} | |||||
class TypeOrHealthCheck_HealthCheck : CachedHealthCheck | |||||
{ | |||||
private readonly IHealthCheck _healthCheck; | |||||
public TypeOrHealthCheck_HealthCheck(string name, TimeSpan cacheDuration, IHealthCheck healthCheck) : base(name, cacheDuration) | |||||
=> _healthCheck = healthCheck; | |||||
protected override IHealthCheck Resolve(IServiceProvider serviceProvider) => _healthCheck; | |||||
} | |||||
class TypeOrHealthCheck_Type : CachedHealthCheck | |||||
{ | |||||
private readonly Type _healthCheckType; | |||||
public TypeOrHealthCheck_Type(string name, TimeSpan cacheDuration, Type healthCheckType) : base(name, cacheDuration) | |||||
=> _healthCheckType = healthCheckType; | |||||
protected override IHealthCheck Resolve(IServiceProvider serviceProvider) | |||||
=> (IHealthCheck)serviceProvider.GetRequiredService(_healthCheckType); | |||||
} | |||||
} | |||||
} |
@ -1,19 +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.HealthChecks | |||||
{ | |||||
public static class CachedHealthCheckExtensions | |||||
{ | |||||
public static ValueTask<IHealthCheckResult> RunAsync(this CachedHealthCheck check, IServiceProvider serviceProvider) | |||||
{ | |||||
Guard.ArgumentNotNull(nameof(check), check); | |||||
return check.RunAsync(serviceProvider, CancellationToken.None); | |||||
} | |||||
} | |||||
} |
@ -1,13 +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. | |||||
namespace Microsoft.Extensions.HealthChecks | |||||
{ | |||||
public enum CheckStatus | |||||
{ | |||||
Unknown, | |||||
Unhealthy, | |||||
Healthy, | |||||
Warning | |||||
} | |||||
} |
@ -1,116 +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.HealthChecks | |||||
{ | |||||
public static partial class HealthCheckBuilderExtensions | |||||
{ | |||||
// Lambda versions of AddCheck for Func/Func<Task>/Func<ValueTask> | |||||
public static HealthCheckBuilder AddCheck(this HealthCheckBuilder builder, string name, Func<IHealthCheckResult> check) | |||||
{ | |||||
Guard.ArgumentNotNull(nameof(builder), builder); | |||||
return builder.AddCheck(name, HealthCheck.FromCheck(check), builder.DefaultCacheDuration); | |||||
} | |||||
public static HealthCheckBuilder AddCheck(this HealthCheckBuilder builder, string name, Func<CancellationToken, IHealthCheckResult> check) | |||||
{ | |||||
Guard.ArgumentNotNull(nameof(builder), builder); | |||||
return builder.AddCheck(name, HealthCheck.FromCheck(check), builder.DefaultCacheDuration); | |||||
} | |||||
public static HealthCheckBuilder AddCheck(this HealthCheckBuilder builder, string name, Func<IHealthCheckResult> check, TimeSpan cacheDuration) | |||||
{ | |||||
Guard.ArgumentNotNull(nameof(builder), builder); | |||||
return builder.AddCheck(name, HealthCheck.FromCheck(check), cacheDuration); | |||||
} | |||||
public static HealthCheckBuilder AddCheck(this HealthCheckBuilder builder, string name, Func<CancellationToken, IHealthCheckResult> check, TimeSpan cacheDuration) | |||||
{ | |||||
Guard.ArgumentNotNull(nameof(builder), builder); | |||||
return builder.AddCheck(name, HealthCheck.FromCheck(check), cacheDuration); | |||||
} | |||||
public static HealthCheckBuilder AddCheck(this HealthCheckBuilder builder, string name, Func<Task<IHealthCheckResult>> check) | |||||
{ | |||||
Guard.ArgumentNotNull(nameof(builder), builder); | |||||
return builder.AddCheck(name, HealthCheck.FromTaskCheck(check), builder.DefaultCacheDuration); | |||||
} | |||||
public static HealthCheckBuilder AddCheck(this HealthCheckBuilder builder, string name, Func<CancellationToken, Task<IHealthCheckResult>> check) | |||||
{ | |||||
Guard.ArgumentNotNull(nameof(builder), builder); | |||||
return builder.AddCheck(name, HealthCheck.FromTaskCheck(check), builder.DefaultCacheDuration); | |||||
} | |||||
public static HealthCheckBuilder AddCheck(this HealthCheckBuilder builder, string name, Func<Task<IHealthCheckResult>> check, TimeSpan cacheDuration) | |||||
{ | |||||
Guard.ArgumentNotNull(nameof(builder), builder); | |||||
return builder.AddCheck(name, HealthCheck.FromTaskCheck(check), cacheDuration); | |||||
} | |||||
public static HealthCheckBuilder AddCheck(this HealthCheckBuilder builder, string name, Func<CancellationToken, Task<IHealthCheckResult>> check, TimeSpan cacheDuration) | |||||
{ | |||||
Guard.ArgumentNotNull(nameof(builder), builder); | |||||
return builder.AddCheck(name, HealthCheck.FromTaskCheck(check), cacheDuration); | |||||
} | |||||
public static HealthCheckBuilder AddValueTaskCheck(this HealthCheckBuilder builder, string name, Func<ValueTask<IHealthCheckResult>> check) | |||||
{ | |||||
Guard.ArgumentNotNull(nameof(builder), builder); | |||||
return builder.AddCheck(name, HealthCheck.FromValueTaskCheck(check), builder.DefaultCacheDuration); | |||||
} | |||||
public static HealthCheckBuilder AddValueTaskCheck(this HealthCheckBuilder builder, string name, Func<CancellationToken, ValueTask<IHealthCheckResult>> check) | |||||
{ | |||||
Guard.ArgumentNotNull(nameof(builder), builder); | |||||
return builder.AddCheck(name, HealthCheck.FromValueTaskCheck(check), builder.DefaultCacheDuration); | |||||
} | |||||
public static HealthCheckBuilder AddValueTaskCheck(this HealthCheckBuilder builder, string name, Func<ValueTask<IHealthCheckResult>> check, TimeSpan cacheDuration) | |||||
{ | |||||
Guard.ArgumentNotNull(nameof(builder), builder); | |||||
return builder.AddCheck(name, HealthCheck.FromValueTaskCheck(check), cacheDuration); | |||||
} | |||||
public static HealthCheckBuilder AddValueTaskCheck(this HealthCheckBuilder builder, string name, Func<CancellationToken, ValueTask<IHealthCheckResult>> check, TimeSpan cacheDuration) | |||||
{ | |||||
Guard.ArgumentNotNull(nameof(builder), builder); | |||||
return builder.AddCheck(name, HealthCheck.FromValueTaskCheck(check), cacheDuration); | |||||
} | |||||
// IHealthCheck versions of AddCheck | |||||
public static HealthCheckBuilder AddCheck(this HealthCheckBuilder builder, string checkName, IHealthCheck check) | |||||
{ | |||||
Guard.ArgumentNotNull(nameof(builder), builder); | |||||
return builder.AddCheck(checkName, check, builder.DefaultCacheDuration); | |||||
} | |||||
// Type versions of AddCheck | |||||
public static HealthCheckBuilder AddCheck<TCheck>(this HealthCheckBuilder builder, string name) where TCheck : class, IHealthCheck | |||||
{ | |||||
Guard.ArgumentNotNull(nameof(builder), builder); | |||||
return builder.AddCheck<TCheck>(name, builder.DefaultCacheDuration); | |||||
} | |||||
} | |||||
} |
@ -1,69 +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; | |||||
namespace Microsoft.Extensions.HealthChecks | |||||
{ | |||||
public static partial class HealthCheckBuilderExtensions | |||||
{ | |||||
// Numeric checks | |||||
public static HealthCheckBuilder AddMinValueCheck<T>(this HealthCheckBuilder builder, string name, T minValue, Func<T> currentValueFunc) where T : IComparable<T> | |||||
{ | |||||
Guard.ArgumentNotNull(nameof(builder), builder); | |||||
return AddMinValueCheck(builder, name, minValue, currentValueFunc, builder.DefaultCacheDuration); | |||||
} | |||||
public static HealthCheckBuilder AddMinValueCheck<T>(this HealthCheckBuilder builder, string name, T minValue, Func<T> currentValueFunc, TimeSpan cacheDuration) | |||||
where T : IComparable<T> | |||||
{ | |||||
Guard.ArgumentNotNull(nameof(builder), builder); | |||||
Guard.ArgumentNotNullOrEmpty(nameof(name), name); | |||||
Guard.ArgumentNotNull(nameof(currentValueFunc), currentValueFunc); | |||||
builder.AddCheck(name, () => | |||||
{ | |||||
var currentValue = currentValueFunc(); | |||||
var status = currentValue.CompareTo(minValue) >= 0 ? CheckStatus.Healthy : CheckStatus.Unhealthy; | |||||
return HealthCheckResult.FromStatus( | |||||
status, | |||||
$"min={minValue}, current={currentValue}", | |||||
new Dictionary<string, object> { { "min", minValue }, { "current", currentValue } } | |||||
); | |||||
}, cacheDuration); | |||||
return builder; | |||||
} | |||||
public static HealthCheckBuilder AddMaxValueCheck<T>(this HealthCheckBuilder builder, string name, T maxValue, Func<T> currentValueFunc) where T : IComparable<T> | |||||
{ | |||||
Guard.ArgumentNotNull(nameof(builder), builder); | |||||
return AddMaxValueCheck(builder, name, maxValue, currentValueFunc, builder.DefaultCacheDuration); | |||||
} | |||||
public static HealthCheckBuilder AddMaxValueCheck<T>(this HealthCheckBuilder builder, string name, T maxValue, Func<T> currentValueFunc, TimeSpan cacheDuration) | |||||
where T : IComparable<T> | |||||
{ | |||||
Guard.ArgumentNotNull(nameof(builder), builder); | |||||
Guard.ArgumentNotNullOrEmpty(nameof(name), name); | |||||
Guard.ArgumentNotNull(nameof(currentValueFunc), currentValueFunc); | |||||
builder.AddCheck(name, () => | |||||
{ | |||||
var currentValue = currentValueFunc(); | |||||
var status = currentValue.CompareTo(maxValue) <= 0 ? CheckStatus.Healthy : CheckStatus.Unhealthy; | |||||
return HealthCheckResult.FromStatus( | |||||
status, | |||||
$"max={maxValue}, current={currentValue}", | |||||
new Dictionary<string, object> { { "max", maxValue }, { "current", currentValue } } | |||||
); | |||||
}, cacheDuration); | |||||
return builder; | |||||
} | |||||
} | |||||
} |
@ -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; | |||||
using System.Diagnostics; | |||||
namespace Microsoft.Extensions.HealthChecks | |||||
{ | |||||
public static partial class HealthCheckBuilderExtensions | |||||
{ | |||||
// System checks | |||||
public static HealthCheckBuilder AddPrivateMemorySizeCheck(this HealthCheckBuilder builder, long maxSize) | |||||
=> AddMaxValueCheck(builder, $"PrivateMemorySize({maxSize})", maxSize, () => Process.GetCurrentProcess().PrivateMemorySize64); | |||||
public static HealthCheckBuilder AddPrivateMemorySizeCheck(this HealthCheckBuilder builder, long maxSize, TimeSpan cacheDuration) | |||||
=> AddMaxValueCheck(builder, $"PrivateMemorySize({maxSize})", maxSize, () => Process.GetCurrentProcess().PrivateMemorySize64, cacheDuration); | |||||
public static HealthCheckBuilder AddVirtualMemorySizeCheck(this HealthCheckBuilder builder, long maxSize) | |||||
=> AddMaxValueCheck(builder, $"VirtualMemorySize({maxSize})", maxSize, () => Process.GetCurrentProcess().VirtualMemorySize64); | |||||
public static HealthCheckBuilder AddVirtualMemorySizeCheck(this HealthCheckBuilder builder, long maxSize, TimeSpan cacheDuration) | |||||
=> AddMaxValueCheck(builder, $"VirtualMemorySize({maxSize})", maxSize, () => Process.GetCurrentProcess().VirtualMemorySize64, cacheDuration); | |||||
public static HealthCheckBuilder AddWorkingSetCheck(this HealthCheckBuilder builder, long maxSize) | |||||
=> AddMaxValueCheck(builder, $"WorkingSet({maxSize})", maxSize, () => Process.GetCurrentProcess().WorkingSet64); | |||||
public static HealthCheckBuilder AddWorkingSetCheck(this HealthCheckBuilder builder, long maxSize, TimeSpan cacheDuration) | |||||
=> AddMaxValueCheck(builder, $"WorkingSet({maxSize})", maxSize, () => Process.GetCurrentProcess().WorkingSet64, cacheDuration); | |||||
} | |||||
} |
@ -1,83 +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.Net.Http; | |||||
using System.Threading.Tasks; | |||||
using Microsoft.Extensions.HealthChecks.Internal; | |||||
namespace Microsoft.Extensions.HealthChecks | |||||
{ | |||||
public static partial class HealthCheckBuilderExtensions | |||||
{ | |||||
// Default URL check | |||||
public static HealthCheckBuilder AddUrlCheck(this HealthCheckBuilder builder, string url) | |||||
{ | |||||
Guard.ArgumentNotNull(nameof(builder), builder); | |||||
return AddUrlCheck(builder, url, builder.DefaultCacheDuration); | |||||
} | |||||
public static HealthCheckBuilder AddUrlCheck(this HealthCheckBuilder builder, string url, TimeSpan cacheDuration) | |||||
=> AddUrlCheck(builder, url, response => UrlChecker.DefaultUrlCheck(response), cacheDuration); | |||||
// Func returning IHealthCheckResult | |||||
public static HealthCheckBuilder AddUrlCheck(this HealthCheckBuilder builder, string url, Func<HttpResponseMessage, IHealthCheckResult> checkFunc) | |||||
{ | |||||
Guard.ArgumentNotNull(nameof(builder), builder); | |||||
return AddUrlCheck(builder, url, checkFunc, builder.DefaultCacheDuration); | |||||
} | |||||
public static HealthCheckBuilder AddUrlCheck(this HealthCheckBuilder builder, string url, | |||||
Func<HttpResponseMessage, IHealthCheckResult> checkFunc, | |||||
TimeSpan cacheDuration) | |||||
{ | |||||
Guard.ArgumentNotNull(nameof(checkFunc), checkFunc); | |||||
return AddUrlCheck(builder, url, response => new ValueTask<IHealthCheckResult>(checkFunc(response)), cacheDuration); | |||||
} | |||||
// Func returning Task<IHealthCheckResult> | |||||
public static HealthCheckBuilder AddUrlCheck(this HealthCheckBuilder builder, string url, Func<HttpResponseMessage, Task<IHealthCheckResult>> checkFunc) | |||||
{ | |||||
Guard.ArgumentNotNull(nameof(builder), builder); | |||||
return AddUrlCheck(builder, url, checkFunc, builder.DefaultCacheDuration); | |||||
} | |||||
public static HealthCheckBuilder AddUrlCheck(this HealthCheckBuilder builder, string url, | |||||
Func<HttpResponseMessage, Task<IHealthCheckResult>> checkFunc, | |||||
TimeSpan cacheDuration) | |||||
{ | |||||
Guard.ArgumentNotNull(nameof(checkFunc), checkFunc); | |||||
return AddUrlCheck(builder, url, response => new ValueTask<IHealthCheckResult>(checkFunc(response)), cacheDuration); | |||||
} | |||||
// Func returning ValueTask<IHealthCheckResult> | |||||
public static HealthCheckBuilder AddUrlCheck(this HealthCheckBuilder builder, string url, Func<HttpResponseMessage, ValueTask<IHealthCheckResult>> checkFunc) | |||||
{ | |||||
Guard.ArgumentNotNull(nameof(builder), builder); | |||||
return AddUrlCheck(builder, url, checkFunc, builder.DefaultCacheDuration); | |||||
} | |||||
public static HealthCheckBuilder AddUrlCheck(this HealthCheckBuilder builder, string url, | |||||
Func<HttpResponseMessage, ValueTask<IHealthCheckResult>> checkFunc, | |||||
TimeSpan cacheDuration) | |||||
{ | |||||
Guard.ArgumentNotNull(nameof(builder), builder); | |||||
Guard.ArgumentNotNullOrEmpty(nameof(url), url); | |||||
Guard.ArgumentNotNull(nameof(checkFunc), checkFunc); | |||||
var urlCheck = new UrlChecker(checkFunc, url); | |||||
builder.AddCheck($"UrlCheck({url})", () => urlCheck.CheckAsync(), cacheDuration); | |||||
return builder; | |||||
} | |||||
} | |||||
} |
@ -1,86 +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.Linq; | |||||
namespace Microsoft.Extensions.HealthChecks | |||||
{ | |||||
/// <summary> | |||||
/// Represents a composite health check result built from several results. | |||||
/// </summary> | |||||
public class CompositeHealthCheckResult : IHealthCheckResult | |||||
{ | |||||
private static readonly IReadOnlyDictionary<string, object> _emptyData = new Dictionary<string, object>(); | |||||
private readonly CheckStatus _initialStatus; | |||||
private readonly CheckStatus _partiallyHealthyStatus; | |||||
private readonly Dictionary<string, IHealthCheckResult> _results = new Dictionary<string, IHealthCheckResult>(StringComparer.OrdinalIgnoreCase); | |||||
public CompositeHealthCheckResult(CheckStatus partiallyHealthyStatus = CheckStatus.Warning, | |||||
CheckStatus initialStatus = CheckStatus.Unknown) | |||||
{ | |||||
_partiallyHealthyStatus = partiallyHealthyStatus; | |||||
_initialStatus = initialStatus; | |||||
} | |||||
public CheckStatus CheckStatus | |||||
{ | |||||
get | |||||
{ | |||||
var checkStatuses = new HashSet<CheckStatus>(_results.Select(x => x.Value.CheckStatus)); | |||||
if (checkStatuses.Count == 0) | |||||
{ | |||||
return _initialStatus; | |||||
} | |||||
if (checkStatuses.Count == 1) | |||||
{ | |||||
return checkStatuses.First(); | |||||
} | |||||
if (checkStatuses.Contains(CheckStatus.Healthy)) | |||||
{ | |||||
return _partiallyHealthyStatus; | |||||
} | |||||
return CheckStatus.Unhealthy; | |||||
} | |||||
} | |||||
public string Description => string.Join(Environment.NewLine, _results.Select(r => $"{r.Key}: {r.Value.Description}")); | |||||
public IReadOnlyDictionary<string, object> Data | |||||
{ | |||||
get | |||||
{ | |||||
var result = new Dictionary<string, object>(); | |||||
foreach (var kvp in _results) | |||||
result.Add(kvp.Key, kvp.Value.Data); | |||||
return result; | |||||
} | |||||
} | |||||
public IReadOnlyDictionary<string, IHealthCheckResult> Results => _results; | |||||
public void Add(string name, CheckStatus status, string description) | |||||
=> Add(name, status, description, null); | |||||
public void Add(string name, CheckStatus status, string description, Dictionary<string, object> data) | |||||
{ | |||||
Guard.ArgumentNotNullOrEmpty(nameof(name), name); | |||||
Guard.ArgumentValid(status != CheckStatus.Unknown, nameof(status), "Cannot add 'Unknown' status to composite health check result."); | |||||
Guard.ArgumentNotNullOrEmpty(nameof(description), description); | |||||
_results.Add(name, HealthCheckResult.FromStatus(status, description, data)); | |||||
} | |||||
public void Add(string name, IHealthCheckResult checkResult) | |||||
{ | |||||
Guard.ArgumentNotNullOrEmpty(nameof(name), name); | |||||
Guard.ArgumentNotNull(nameof(checkResult), checkResult); | |||||
_results.Add(name, checkResult); | |||||
} | |||||
} | |||||
} |
@ -1,42 +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.HealthChecks | |||||
{ | |||||
public class HealthCheck : IHealthCheck | |||||
{ | |||||
protected HealthCheck(Func<CancellationToken, ValueTask<IHealthCheckResult>> check) | |||||
{ | |||||
Guard.ArgumentNotNull(nameof(check), check); | |||||
Check = check; | |||||
} | |||||
protected Func<CancellationToken, ValueTask<IHealthCheckResult>> Check { get; } | |||||
public ValueTask<IHealthCheckResult> CheckAsync(CancellationToken cancellationToken = default(CancellationToken)) | |||||
=> Check(cancellationToken); | |||||
public static HealthCheck FromCheck(Func<IHealthCheckResult> check) | |||||
=> new HealthCheck(token => new ValueTask<IHealthCheckResult>(check())); | |||||
public static HealthCheck FromCheck(Func<CancellationToken, IHealthCheckResult> check) | |||||
=> new HealthCheck(token => new ValueTask<IHealthCheckResult>(check(token))); | |||||
public static HealthCheck FromTaskCheck(Func<Task<IHealthCheckResult>> check) | |||||
=> new HealthCheck(token => new ValueTask<IHealthCheckResult>(check())); | |||||
public static HealthCheck FromTaskCheck(Func<CancellationToken, Task<IHealthCheckResult>> check) | |||||
=> new HealthCheck(token => new ValueTask<IHealthCheckResult>(check(token))); | |||||
public static HealthCheck FromValueTaskCheck(Func<ValueTask<IHealthCheckResult>> check) | |||||
=> new HealthCheck(token => check()); | |||||
public static HealthCheck FromValueTaskCheck(Func<CancellationToken, ValueTask<IHealthCheckResult>> check) | |||||
=> new HealthCheck(check); | |||||
} | |||||
} |
@ -1,135 +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; | |||||
namespace Microsoft.Extensions.HealthChecks | |||||
{ | |||||
public class HealthCheckBuilder | |||||
{ | |||||
private readonly Dictionary<string, CachedHealthCheck> _checksByName; | |||||
private readonly HealthCheckGroup _currentGroup; | |||||
private readonly Dictionary<string, HealthCheckGroup> _groups; | |||||
public HealthCheckBuilder() | |||||
{ | |||||
_checksByName = new Dictionary<string, CachedHealthCheck>(StringComparer.OrdinalIgnoreCase); | |||||
_currentGroup = new HealthCheckGroup(string.Empty, CheckStatus.Unhealthy); | |||||
_groups = new Dictionary<string, HealthCheckGroup>(StringComparer.OrdinalIgnoreCase) | |||||
{ | |||||
[string.Empty] = _currentGroup | |||||
}; | |||||
DefaultCacheDuration = TimeSpan.FromMinutes(5); | |||||
} | |||||
/// <summary> | |||||
/// This constructor should only be used when creating a grouped health check builder. | |||||
/// </summary> | |||||
public HealthCheckBuilder(HealthCheckBuilder rootBuilder, HealthCheckGroup currentGroup) | |||||
{ | |||||
Guard.ArgumentNotNull(nameof(rootBuilder), rootBuilder); | |||||
Guard.ArgumentNotNull(nameof(currentGroup), currentGroup); | |||||
_checksByName = rootBuilder._checksByName; | |||||
_currentGroup = currentGroup; | |||||
_groups = rootBuilder._groups; | |||||
DefaultCacheDuration = rootBuilder.DefaultCacheDuration; | |||||
} | |||||
/// <summary> | |||||
/// Gets the registered checks, indexed by check name. | |||||
/// </summary> | |||||
public IReadOnlyDictionary<string, CachedHealthCheck> ChecksByName => _checksByName; | |||||
/// <summary> | |||||
/// Gets the current default cache duration used when registering checks. | |||||
/// </summary> | |||||
public TimeSpan DefaultCacheDuration { get; private set; } | |||||
/// <summary> | |||||
/// Gets the registered groups, indexed by group name. The root group's name is <see cref="string.Empty"/>. | |||||
/// </summary> | |||||
public IReadOnlyDictionary<string, HealthCheckGroup> Groups => _groups; | |||||
/// <summary> | |||||
/// Registers a health check type that will later be resolved via dependency | |||||
/// injection. | |||||
/// </summary> | |||||
public HealthCheckBuilder AddCheck<TCheck>(string checkName, TimeSpan cacheDuration) where TCheck : class, IHealthCheck | |||||
{ | |||||
Guard.ArgumentNotNullOrEmpty(nameof(checkName), checkName); | |||||
Guard.ArgumentValid(!_checksByName.ContainsKey(checkName), nameof(checkName), $"A check with name '{checkName}' has already been registered."); | |||||
var namedCheck = CachedHealthCheck.FromType(checkName, cacheDuration, typeof(TCheck)); | |||||
_checksByName.Add(checkName, namedCheck); | |||||
_currentGroup.ChecksInternal.Add(namedCheck); | |||||
return this; | |||||
} | |||||
/// <summary> | |||||
/// Registers a concrete health check to the builder. | |||||
/// </summary> | |||||
public HealthCheckBuilder AddCheck(string checkName, IHealthCheck check, TimeSpan cacheDuration) | |||||
{ | |||||
Guard.ArgumentNotNullOrEmpty(nameof(checkName), checkName); | |||||
Guard.ArgumentNotNull(nameof(check), check); | |||||
Guard.ArgumentValid(!_checksByName.ContainsKey(checkName), nameof(checkName), $"A check with name '{checkName}' has already been registered."); | |||||
var namedCheck = CachedHealthCheck.FromHealthCheck(checkName, cacheDuration, check); | |||||
_checksByName.Add(checkName, namedCheck); | |||||
_currentGroup.ChecksInternal.Add(namedCheck); | |||||
return this; | |||||
} | |||||
/// <summary> | |||||
/// Creates a new health check group, to which you can add one or more health | |||||
/// checks. Uses <see cref="CheckStatus.Unhealthy"/> when the group is | |||||
/// partially successful. | |||||
/// </summary> | |||||
public HealthCheckBuilder AddHealthCheckGroup(string groupName, Action<HealthCheckBuilder> groupChecks) | |||||
=> AddHealthCheckGroup(groupName, groupChecks, CheckStatus.Unhealthy); | |||||
/// <summary> | |||||
/// Creates a new health check group, to which you can add one or more health | |||||
/// checks. | |||||
/// </summary> | |||||
public HealthCheckBuilder AddHealthCheckGroup(string groupName, Action<HealthCheckBuilder> groupChecks, CheckStatus partialSuccessStatus) | |||||
{ | |||||
Guard.ArgumentNotNullOrEmpty(nameof(groupName), groupName); | |||||
Guard.ArgumentNotNull(nameof(groupChecks), groupChecks); | |||||
Guard.ArgumentValid(partialSuccessStatus != CheckStatus.Unknown, nameof(partialSuccessStatus), "Check status 'Unknown' is not valid for partial success."); | |||||
Guard.ArgumentValid(!_groups.ContainsKey(groupName), nameof(groupName), $"A group with name '{groupName}' has already been registered."); | |||||
Guard.OperationValid(_currentGroup.GroupName == string.Empty, "Nested groups are not supported by HealthCheckBuilder."); | |||||
var group = new HealthCheckGroup(groupName, partialSuccessStatus); | |||||
_groups.Add(groupName, group); | |||||
var innerBuilder = new HealthCheckBuilder(this, group); | |||||
groupChecks(innerBuilder); | |||||
return this; | |||||
} | |||||
public HealthCheckBuilder WithDefaultCacheDuration(TimeSpan duration) | |||||
{ | |||||
Guard.ArgumentValid(duration >= TimeSpan.Zero, nameof(duration), "Duration must be zero (disabled) or a positive duration."); | |||||
DefaultCacheDuration = duration; | |||||
return this; | |||||
} | |||||
public HealthCheckBuilder WithPartialSuccessStatus(CheckStatus partiallyHealthyStatus) | |||||
{ | |||||
_currentGroup.PartiallyHealthyStatus = partiallyHealthyStatus; | |||||
return this; | |||||
} | |||||
} | |||||
} |
@ -1,37 +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; | |||||
namespace Microsoft.Extensions.HealthChecks | |||||
{ | |||||
public class HealthCheckGroup | |||||
{ | |||||
private CheckStatus _partialSuccessStatus; | |||||
public HealthCheckGroup(string groupName, CheckStatus partialSuccessStatus) | |||||
{ | |||||
Guard.ArgumentNotNull(nameof(groupName), groupName); | |||||
GroupName = groupName; | |||||
PartiallyHealthyStatus = partialSuccessStatus; | |||||
} | |||||
public IReadOnlyList<CachedHealthCheck> Checks => ChecksInternal.AsReadOnly(); | |||||
internal List<CachedHealthCheck> ChecksInternal { get; } = new List<CachedHealthCheck>(); | |||||
public string GroupName { get; } | |||||
public CheckStatus PartiallyHealthyStatus | |||||
{ | |||||
get => _partialSuccessStatus; | |||||
internal set | |||||
{ | |||||
Guard.ArgumentValid(value != CheckStatus.Unknown, nameof(value), "Check status 'Unknown' is not valid for partial success."); | |||||
_partialSuccessStatus = value; | |||||
} | |||||
} | |||||
} | |||||
} |
@ -1,53 +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; | |||||
namespace Microsoft.Extensions.HealthChecks | |||||
{ | |||||
public class HealthCheckResult : IHealthCheckResult | |||||
{ | |||||
private static readonly IReadOnlyDictionary<string, object> _emptyData = new Dictionary<string, object>(); | |||||
public CheckStatus CheckStatus { get; } | |||||
public IReadOnlyDictionary<string, object> Data { get; } | |||||
public string Description { get; } | |||||
private HealthCheckResult(CheckStatus checkStatus, string description, IReadOnlyDictionary<string, object> data) | |||||
{ | |||||
CheckStatus = checkStatus; | |||||
Description = description; | |||||
Data = data ?? _emptyData; | |||||
} | |||||
public static HealthCheckResult Unhealthy(string description) | |||||
=> new HealthCheckResult(CheckStatus.Unhealthy, description, null); | |||||
public static HealthCheckResult Unhealthy(string description, IReadOnlyDictionary<string, object> data) | |||||
=> new HealthCheckResult(CheckStatus.Unhealthy, description, data); | |||||
public static HealthCheckResult Healthy(string description) | |||||
=> new HealthCheckResult(CheckStatus.Healthy, description, null); | |||||
public static HealthCheckResult Healthy(string description, IReadOnlyDictionary<string, object> data) | |||||
=> new HealthCheckResult(CheckStatus.Healthy, description, data); | |||||
public static HealthCheckResult Warning(string description) | |||||
=> new HealthCheckResult(CheckStatus.Warning, description, null); | |||||
public static HealthCheckResult Warning(string description, IReadOnlyDictionary<string, object> data) | |||||
=> new HealthCheckResult(CheckStatus.Warning, description, data); | |||||
public static HealthCheckResult Unknown(string description) | |||||
=> new HealthCheckResult(CheckStatus.Unknown, description, null); | |||||
public static HealthCheckResult Unknown(string description, IReadOnlyDictionary<string, object> data) | |||||
=> new HealthCheckResult(CheckStatus.Unknown, description, data); | |||||
public static HealthCheckResult FromStatus(CheckStatus status, string description) | |||||
=> new HealthCheckResult(status, description, null); | |||||
public static HealthCheckResult FromStatus(CheckStatus status, string description, IReadOnlyDictionary<string, object> data) | |||||
=> new HealthCheckResult(status, description, data); | |||||
} | |||||
} |
@ -1,12 +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; | |||||
namespace Microsoft.Extensions.HealthChecks | |||||
{ | |||||
public class HealthCheckResults | |||||
{ | |||||
public IList<IHealthCheckResult> CheckResults { get; } = new List<IHealthCheckResult>(); | |||||
} | |||||
} |
@ -1,119 +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.Linq; | |||||
using System.Threading; | |||||
using System.Threading.Tasks; | |||||
using Microsoft.Extensions.DependencyInjection; | |||||
namespace Microsoft.Extensions.HealthChecks | |||||
{ | |||||
public class HealthCheckService : IHealthCheckService | |||||
{ | |||||
private readonly HealthCheckBuilder _builder; | |||||
private readonly IReadOnlyList<HealthCheckGroup> _groups; | |||||
private readonly HealthCheckGroup _root; | |||||
private readonly IServiceProvider _serviceProvider; | |||||
private readonly IServiceScopeFactory _serviceScopeFactory; | |||||
public HealthCheckService(HealthCheckBuilder builder, IServiceProvider serviceProvider, IServiceScopeFactory serviceScopeFactory) | |||||
{ | |||||
_builder = builder; | |||||
_groups = GetGroups().Where(group => group.GroupName != string.Empty).ToList(); | |||||
_root = GetGroup(string.Empty); | |||||
_serviceProvider = serviceProvider; | |||||
_serviceScopeFactory = serviceScopeFactory; | |||||
} | |||||
public async Task<CompositeHealthCheckResult> CheckHealthAsync(CancellationToken cancellationToken = default(CancellationToken)) | |||||
{ | |||||
using (var scope = GetServiceScope()) | |||||
{ | |||||
var scopeServiceProvider = scope.ServiceProvider; | |||||
var groupTasks = _groups.Select(group => new { Group = group, Task = RunGroupAsync(scopeServiceProvider, group, cancellationToken) }).ToList(); | |||||
var result = await RunGroupAsync(scopeServiceProvider, _root, cancellationToken).ConfigureAwait(false); | |||||
await Task.WhenAll(groupTasks.Select(x => x.Task)); | |||||
foreach (var groupTask in groupTasks) | |||||
{ | |||||
result.Add($"Group({groupTask.Group.GroupName})", groupTask.Task.Result); | |||||
} | |||||
return result; | |||||
} | |||||
} | |||||
public IReadOnlyList<CachedHealthCheck> GetAllChecks() | |||||
=> _builder.ChecksByName.Values.ToList().AsReadOnly(); | |||||
public CachedHealthCheck GetCheck(string checkName) | |||||
=> _builder.ChecksByName[checkName]; | |||||
public HealthCheckGroup GetGroup(string groupName) | |||||
=> _builder.Groups[groupName]; | |||||
public IReadOnlyList<HealthCheckGroup> GetGroups() | |||||
=> _builder.Groups.Values.ToList().AsReadOnly(); | |||||
private IServiceScope GetServiceScope() | |||||
=> _serviceScopeFactory == null ? new UnscopedServiceProvider(_serviceProvider) : _serviceScopeFactory.CreateScope(); | |||||
public async ValueTask<IHealthCheckResult> RunCheckAsync(CachedHealthCheck healthCheck, CancellationToken cancellationToken = default(CancellationToken)) | |||||
{ | |||||
using (var scope = GetServiceScope()) | |||||
{ | |||||
return await RunCheckAsync(scope.ServiceProvider, healthCheck, cancellationToken).ConfigureAwait(false); | |||||
} | |||||
} | |||||
/// <summary> | |||||
/// Uses the provided service provider and executes the provided check. | |||||
/// </summary> | |||||
public ValueTask<IHealthCheckResult> RunCheckAsync(IServiceProvider serviceProvider, CachedHealthCheck healthCheck, CancellationToken cancellationToken = default(CancellationToken)) | |||||
{ | |||||
Guard.ArgumentNotNull(nameof(serviceProvider), serviceProvider); | |||||
Guard.ArgumentNotNull(nameof(healthCheck), healthCheck); | |||||
return healthCheck.RunAsync(serviceProvider, cancellationToken); | |||||
} | |||||
/// <summary> | |||||
/// Creates a new resolution scope from the default service provider and executes the checks in the given group. | |||||
/// </summary> | |||||
public async Task<CompositeHealthCheckResult> RunGroupAsync(HealthCheckGroup group, CancellationToken cancellationToken = default(CancellationToken)) | |||||
{ | |||||
using (var scope = GetServiceScope()) | |||||
return await RunGroupAsync(scope.ServiceProvider, group, cancellationToken).ConfigureAwait(false); | |||||
} | |||||
/// <summary> | |||||
/// Uses the provided service provider and executes the checks in the given group. | |||||
/// </summary> | |||||
public async Task<CompositeHealthCheckResult> RunGroupAsync(IServiceProvider serviceProvider, HealthCheckGroup group, CancellationToken cancellationToken = default(CancellationToken)) | |||||
{ | |||||
var result = new CompositeHealthCheckResult(group.PartiallyHealthyStatus); | |||||
var checkTasks = group.Checks.Select(check => new { Check = check, Task = check.RunAsync(serviceProvider, cancellationToken).AsTask() }).ToList(); | |||||
await Task.WhenAll(checkTasks.Select(checkTask => checkTask.Task)); | |||||
foreach (var checkTask in checkTasks) | |||||
{ | |||||
result.Add(checkTask.Check.Name, checkTask.Task.Result); | |||||
} | |||||
return result; | |||||
} | |||||
private class UnscopedServiceProvider : IServiceScope | |||||
{ | |||||
public UnscopedServiceProvider(IServiceProvider serviceProvider) | |||||
=> ServiceProvider = serviceProvider; | |||||
public IServiceProvider ServiceProvider { get; } | |||||
public void Dispose() { } | |||||
} | |||||
} | |||||
} |
@ -1,30 +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.Linq; | |||||
using Microsoft.Extensions.HealthChecks; | |||||
namespace Microsoft.Extensions.DependencyInjection | |||||
{ | |||||
public static class HealthCheckServiceCollectionExtensions | |||||
{ | |||||
private static readonly Type HealthCheckServiceInterface = typeof(IHealthCheckService); | |||||
public static IServiceCollection AddHealthChecks(this IServiceCollection services, Action<HealthCheckBuilder> checks) | |||||
{ | |||||
Guard.OperationValid(!services.Any(descriptor => descriptor.ServiceType == HealthCheckServiceInterface), "AddHealthChecks may only be called once."); | |||||
var builder = new HealthCheckBuilder(); | |||||
services.AddSingleton<IHealthCheckService, HealthCheckService>(serviceProvider => | |||||
{ | |||||
var serviceScopeFactory = serviceProvider.GetService<IServiceScopeFactory>(); | |||||
return new HealthCheckService(builder, serviceProvider, serviceScopeFactory); | |||||
}); | |||||
checks(builder); | |||||
return services; | |||||
} | |||||
} | |||||
} |
@ -1,13 +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.Threading; | |||||
using System.Threading.Tasks; | |||||
namespace Microsoft.Extensions.HealthChecks | |||||
{ | |||||
public interface IHealthCheck | |||||
{ | |||||
ValueTask<IHealthCheckResult> CheckAsync(CancellationToken cancellationToken = default(CancellationToken)); | |||||
} | |||||
} |
@ -1,14 +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; | |||||
namespace Microsoft.Extensions.HealthChecks | |||||
{ | |||||
public interface IHealthCheckResult | |||||
{ | |||||
CheckStatus CheckStatus { get; } | |||||
string Description { get; } | |||||
IReadOnlyDictionary<string, object> Data { get; } | |||||
} | |||||
} |
@ -1,58 +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.Threading; | |||||
using System.Threading.Tasks; | |||||
namespace Microsoft.Extensions.HealthChecks | |||||
{ | |||||
public interface IHealthCheckService | |||||
{ | |||||
/// <summary> | |||||
/// Runs all registered health checks. | |||||
/// </summary> | |||||
Task<CompositeHealthCheckResult> CheckHealthAsync(CancellationToken cancellationToken = default(CancellationToken)); | |||||
/// <summary> | |||||
/// Gets all registered health checks as a flat list. | |||||
/// </summary> | |||||
IReadOnlyList<CachedHealthCheck> GetAllChecks(); | |||||
/// <summary> | |||||
/// Gets a health check by name. | |||||
/// </summary> | |||||
CachedHealthCheck GetCheck(string checkName); | |||||
/// <summary> | |||||
/// Gets all health checks in a group. | |||||
/// </summary> | |||||
HealthCheckGroup GetGroup(string groupName); | |||||
/// <summary> | |||||
/// Gets all the health check groups. | |||||
/// </summary> | |||||
IReadOnlyList<HealthCheckGroup> GetGroups(); | |||||
/// <summary> | |||||
/// Creates a new resolution scope from the default service provider and executes the provided check. | |||||
/// </summary> | |||||
ValueTask<IHealthCheckResult> RunCheckAsync(CachedHealthCheck healthCheck, CancellationToken cancellationToken = default(CancellationToken)); | |||||
/// <summary> | |||||
/// Uses the provided service provider and executes the provided check. | |||||
/// </summary> | |||||
ValueTask<IHealthCheckResult> RunCheckAsync(IServiceProvider serviceProvider, CachedHealthCheck healthCheck, CancellationToken cancellationToken = default(CancellationToken)); | |||||
/// <summary> | |||||
/// Creates a new resolution scope from the default service provider and executes the checks in the given group. | |||||
/// </summary> | |||||
Task<CompositeHealthCheckResult> RunGroupAsync(HealthCheckGroup group, CancellationToken cancellationToken = default(CancellationToken)); | |||||
/// <summary> | |||||
/// Uses the provided service provider and executes the checks in the given group. | |||||
/// </summary> | |||||
Task<CompositeHealthCheckResult> RunGroupAsync(IServiceProvider serviceProvider, HealthCheckGroup group, CancellationToken cancellationToken = default(CancellationToken)); | |||||
} | |||||
} |
@ -1,68 +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.Net.Http; | |||||
using System.Net.Http.Headers; | |||||
using System.Threading.Tasks; | |||||
namespace Microsoft.Extensions.HealthChecks.Internal | |||||
{ | |||||
public class UrlChecker | |||||
{ | |||||
private readonly Func<HttpResponseMessage, ValueTask<IHealthCheckResult>> _checkFunc; | |||||
private readonly string _url; | |||||
public UrlChecker(Func<HttpResponseMessage, ValueTask<IHealthCheckResult>> checkFunc, string url) | |||||
{ | |||||
Guard.ArgumentNotNull(nameof(checkFunc), checkFunc); | |||||
Guard.ArgumentNotNullOrEmpty(nameof(url), url); | |||||
_checkFunc = checkFunc; | |||||
_url = url; | |||||
} | |||||
public CheckStatus PartiallyHealthyStatus { get; set; } = CheckStatus.Warning; | |||||
public async Task<IHealthCheckResult> CheckAsync() | |||||
{ | |||||
using (var httpClient = CreateHttpClient()) | |||||
{ | |||||
try | |||||
{ | |||||
var response = await httpClient.GetAsync(_url).ConfigureAwait(false); | |||||
return await _checkFunc(response); | |||||
} | |||||
catch (Exception ex) | |||||
{ | |||||
var data = new Dictionary<string, object> { { "url", _url } }; | |||||
return HealthCheckResult.Unhealthy($"Exception during check: {ex.GetType().FullName}", data); | |||||
} | |||||
} | |||||
} | |||||
private HttpClient CreateHttpClient() | |||||
{ | |||||
var httpClient = GetHttpClient(); | |||||
httpClient.DefaultRequestHeaders.CacheControl = new CacheControlHeaderValue { NoCache = true }; | |||||
return httpClient; | |||||
} | |||||
public static async ValueTask<IHealthCheckResult> DefaultUrlCheck(HttpResponseMessage response) | |||||
{ | |||||
var status = response.IsSuccessStatusCode ? CheckStatus.Healthy : CheckStatus.Unhealthy; | |||||
var data = new Dictionary<string, object> | |||||
{ | |||||
{ "url", response.RequestMessage.RequestUri.ToString() }, | |||||
{ "status", (int)response.StatusCode }, | |||||
{ "reason", response.ReasonPhrase }, | |||||
{ "body", await response.Content?.ReadAsStringAsync() } | |||||
}; | |||||
return HealthCheckResult.FromStatus(status, $"status code {response.StatusCode} ({(int)response.StatusCode})", data); | |||||
} | |||||
protected virtual HttpClient GetHttpClient() | |||||
=> new HttpClient(); | |||||
} | |||||
} |
@ -1,20 +0,0 @@ | |||||
<Project Sdk="Microsoft.NET.Sdk"> | |||||
<PropertyGroup> | |||||
<TargetFramework>netstandard2.0</TargetFramework> | |||||
</PropertyGroup> | |||||
<ItemGroup> | |||||
<Compile Include="..\common\Guard.cs" Link="Internal\Guard.cs" /> | |||||
</ItemGroup> | |||||
<ItemGroup> | |||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="2.2.0-preview3-35497" /> | |||||
<PackageReference Include="Newtonsoft.Json" Version="11.0.2" /> | |||||
<PackageReference Include="System.Diagnostics.Process" Version="4.3.0" /> | |||||
<PackageReference Include="System.Threading.Tasks.Parallel" Version="4.3.0" /> | |||||
<PackageReference Include="System.Threading.Thread" Version="4.3.0" /> | |||||
<PackageReference Include="System.Threading.Tasks.Extensions" Version="4.5.0" /> | |||||
</ItemGroup> | |||||
</Project> |
@ -1,57 +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; | |||||
static class Guard | |||||
{ | |||||
public static void ArgumentNotNull(string argumentName, object value) | |||||
{ | |||||
if (value == null) | |||||
{ | |||||
throw new ArgumentNullException(argumentName); | |||||
} | |||||
} | |||||
public static void ArgumentNotNullOrEmpty(string argumentName, string value) | |||||
{ | |||||
if (value == null) | |||||
{ | |||||
throw new ArgumentNullException(argumentName); | |||||
} | |||||
if (string.IsNullOrEmpty(value)) | |||||
{ | |||||
throw new ArgumentException("Value cannot be an empty string.", argumentName); | |||||
} | |||||
} | |||||
// Use IReadOnlyCollection<T> instead of IEnumerable<T> to discourage double enumeration | |||||
public static void ArgumentNotNullOrEmpty<T>(string argumentName, IReadOnlyCollection<T> items) | |||||
{ | |||||
if (items == null) | |||||
{ | |||||
throw new ArgumentNullException(argumentName); | |||||
} | |||||
if (items.Count == 0) | |||||
{ | |||||
throw new ArgumentException("Collection must contain at least one item.", argumentName); | |||||
} | |||||
} | |||||
public static void ArgumentValid(bool valid, string argumentName, string exceptionMessage) | |||||
{ | |||||
if (!valid) | |||||
{ | |||||
throw new ArgumentException(exceptionMessage, argumentName); | |||||
} | |||||
} | |||||
public static void OperationValid(bool valid, string exceptionMessage) | |||||
{ | |||||
if (!valid) | |||||
{ | |||||
throw new InvalidOperationException(exceptionMessage); | |||||
} | |||||
} | |||||
} |
@ -1,18 +0,0 @@ | |||||
using Microsoft.Extensions.HealthChecks; | |||||
using System; | |||||
namespace WebStatus.Extensions | |||||
{ | |||||
public static class HealthCheckBuilderExtensions | |||||
{ | |||||
public static HealthCheckBuilder AddUrlCheckIfNotNull(this HealthCheckBuilder builder, string url, TimeSpan cacheDuration) | |||||
{ | |||||
if (!string.IsNullOrEmpty(url)) | |||||
{ | |||||
builder.AddUrlCheck(url, cacheDuration); | |||||
} | |||||
return builder; | |||||
} | |||||
} | |||||
} |
@ -1,23 +0,0 @@ | |||||
using Microsoft.Extensions.HealthChecks; | |||||
using System.Collections.Generic; | |||||
using System.Linq; | |||||
namespace WebStatus.Viewmodels | |||||
{ | |||||
public class HealthStatusViewModel | |||||
{ | |||||
private readonly CheckStatus _overall; | |||||
private readonly Dictionary<string, IHealthCheckResult> _results; | |||||
public CheckStatus OverallStatus => _overall; | |||||
public IEnumerable<NamedCheckResult> Results => _results.Select(kvp => new NamedCheckResult(kvp.Key, kvp.Value)); | |||||
private HealthStatusViewModel() => _results = new Dictionary<string, IHealthCheckResult>(); | |||||
public HealthStatusViewModel(CheckStatus overall) : this() => _overall = overall; | |||||
public void AddResult(string name, IHealthCheckResult result) => _results.Add(name, result); | |||||
} | |||||
} |
@ -1,17 +0,0 @@ | |||||
using Microsoft.Extensions.HealthChecks; | |||||
namespace WebStatus.Viewmodels | |||||
{ | |||||
public class NamedCheckResult | |||||
{ | |||||
public string Name { get; } | |||||
public IHealthCheckResult Result { get; } | |||||
public NamedCheckResult(string name, IHealthCheckResult result) | |||||
{ | |||||
Name = name; | |||||
Result = result; | |||||
} | |||||
} | |||||
} |
@ -1,51 +0,0 @@ | |||||
@using Microsoft.AspNetCore.Html | |||||
@using Microsoft.Extensions.HealthChecks | |||||
@model WebStatus.Viewmodels.HealthStatusViewModel | |||||
@{ | |||||
ViewData["Title"] = "System Status"; | |||||
} | |||||
@functions | |||||
{ | |||||
static readonly string[] LabelClass = new[] { "default", "danger", "success", "warning" }; | |||||
public HtmlString StatusLabel(CheckStatus status) | |||||
{ | |||||
return new HtmlString($@"<span class=""label label-{LabelClass[(int) status]}"">{status}</span>"); | |||||
} | |||||
} | |||||
<style>.label {font-size: 100%}</style> | |||||
<div class="row"> | |||||
<div class="col-md-12"> | |||||
<h2 class="overall-status-title">Overall Status: @StatusLabel(Model.OverallStatus)</h2> | |||||
</div> | |||||
</div> | |||||
<div class="list-group-status"> | |||||
@foreach (var result in Model.Results) | |||||
{ | |||||
<div class="row list-group-status-item"> | |||||
<div class="col-md-9"> | |||||
<h4 class="list-group-status-item-title">@result.Name</h4> | |||||
<p class="list-group-item-text"> | |||||
@if (result.Result.Data.ContainsKey("url")) | |||||
{ | |||||
<p>@result.Result.Data["url"]</p> | |||||
} | |||||
<p class="text-@(LabelClass[(int)result.Result.CheckStatus])" style="font-weight:bold"> | |||||
@result.Result.Description | |||||
</p> | |||||
</p> | |||||
</div> | |||||
<div class="col-md-3"> | |||||
<h3>@StatusLabel(result.Result.CheckStatus)</h3> | |||||
</div> | |||||
</div> | |||||
} | |||||
</div> |
@ -1,14 +0,0 @@ | |||||
@{ | |||||
ViewData["Title"] = "Error"; | |||||
} | |||||
<h1 class="text-danger">Error.</h1> | |||||
<h2 class="text-danger">An error occurred while processing your request.</h2> | |||||
<h3>Development Mode</h3> | |||||
<p> | |||||
Swapping to <strong>Development</strong> environment will display more detailed information about the error that occurred. | |||||
</p> | |||||
<p> | |||||
<strong>Development environment should not be enabled in deployed applications</strong>, as it can result in sensitive information from exceptions being displayed to end users. For local debugging, development environment can be enabled by setting the <strong>ASPNETCORE_ENVIRONMENT</strong> environment variable to <strong>Development</strong>, and restarting the application. | |||||
</p> |
@ -1,60 +0,0 @@ | |||||
<!DOCTYPE html> | |||||
<html> | |||||
<head> | |||||
<meta charset="utf-8" /> | |||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> | |||||
<title>@ViewData["Title"] - WebStatus</title> | |||||
<environment names="Development"> | |||||
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.css" /> | |||||
<link rel="stylesheet" href="~/css/site.css" /> | |||||
</environment> | |||||
<environment names="Staging,Production"> | |||||
<link rel="stylesheet" href="https://ajax.aspnetcdn.com/ajax/bootstrap/4.1.1/css/bootstrap.min.css" | |||||
asp-fallback-href="~/lib/bootstrap/dist/css/bootstrap.min.css" | |||||
asp-fallback-test-class="sr-only" asp-fallback-test-property="position" asp-fallback-test-value="absolute" /> | |||||
<link rel="stylesheet" href="~/css/site.min.css" asp-append-version="true" /> | |||||
</environment> | |||||
@if (ViewBag.RefreshSeconds != null && ViewBag.RefreshSeconds > 0) | |||||
{ | |||||
<meta http-equiv="refresh" content="@ViewBag.RefreshSeconds"> | |||||
} | |||||
</head> | |||||
<body> | |||||
<nav class="navbar navbar-dark bg-dark fixed-top"> | |||||
<a asp-area="" asp-controller="Home" asp-action="Index" class="navbar-brand"> | |||||
 <b>eShopOnContainers</b> WebStatus | |||||
</a> | |||||
</nav> | |||||
<br /> | |||||
<div class="container body-content"> | |||||
@RenderBody() | |||||
</div> | |||||
<br /> | |||||
<div id="footer"> | |||||
<p> © 2017 - WebStatus</p> | |||||
</div> | |||||
<environment names="Development"> | |||||
<script src="~/lib/jquery/jquery.js"></script> | |||||
<script src="~/lib/bootstrap/dist/js/bootstrap.js"></script> | |||||
</environment> | |||||
<environment names="Staging,Production"> | |||||
<script src="https://ajax.aspnetcdn.com/ajax/jquery/jquery-2.2.0.min.js" | |||||
asp-fallback-src="~/lib/jquery/jquery.min.js" | |||||
asp-fallback-test="window.jQuery" | |||||
crossorigin="anonymous" | |||||
integrity="sha384-K+ctZQ+LL8q6tP7I94W+qzQsfRV2a+AfHIi9k8z8l9ggpc8X+Ytst4yBo/hH+8Fk"> | |||||
</script> | |||||
<script src="https://ajax.aspnetcdn.com/ajax/bootstrap/4.1.1/bootstrap.min.js" | |||||
asp-fallback-src="~/lib/bootstrap/dist/js/bootstrap.min.js" | |||||
asp-fallback-test="window.jQuery && window.jQuery.fn && window.jQuery.fn.modal" | |||||
crossorigin="anonymous" | |||||
integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa"> | |||||
</script> | |||||
</environment> | |||||
</body> | |||||
</html> |
@ -1,2 +0,0 @@ | |||||
@using WebStatus | |||||
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers |
@ -1,3 +0,0 @@ | |||||
@{ | |||||
Layout = "_Layout"; | |||||
} |
@ -1,21 +1,84 @@ | |||||
{ | { | ||||
"Logging": { | |||||
"IncludeScopes": false, | |||||
"LogLevel": { | |||||
"Default": "Debug", | |||||
"System": "Information", | |||||
"Microsoft": "Information" | |||||
} | |||||
}, | |||||
"OrderingUrl": "http://localhost:5102/hc", | |||||
"OrderingBackgroundTasksUrl": "http://localhost:5111/hc", | |||||
"BasketUrl": "http://localhost:5103/hc", | |||||
"CatalogUrl": "http://localhost:5101/hc", | |||||
"IdentityUrl": "http://localhost:5105/hc", | |||||
"MarketingUrl": "http://localhost:5110/hc", | |||||
"LocationsUrl": "http://localhost:5109/hc", | |||||
"PaymentUrl": "http://localhost:5108/hc", | |||||
"ApplicationInsights": { | |||||
"InstrumentationKey": "" | |||||
"HealthChecks-UI": { | |||||
"HealthChecks": [ | |||||
{ | |||||
"Name": "Ordering HTTP Check", | |||||
"Uri": "http://localhost:5102/hc" | |||||
}, | |||||
{ | |||||
"Name": "Ordering HTTP Background Check", | |||||
"Uri": "http://localhost:5111/hc" | |||||
}, | |||||
{ | |||||
"Name": "Basket HTTP Check", | |||||
"Uri": "http://localhost:5103/hc" | |||||
}, | |||||
{ | |||||
"Name": "Catalog HTTP Check", | |||||
"Uri": "http://localhost:5101/hc" | |||||
}, | |||||
{ | |||||
"Name": "Identity HTTP Check", | |||||
"Uri": "http://localhost:5105/hc" | |||||
}, | |||||
{ | |||||
"Name": "Marketing HTTP Check", | |||||
"Uri": "http://localhost:5110/hc" | |||||
}, | |||||
{ | |||||
"Name": "Locations HTTP Check", | |||||
"Uri": "http://localhost:5109/hc" | |||||
}, | |||||
{ | |||||
"Name": "Payments HTTP Check", | |||||
"Uri": "http://localhost:5108/hc" | |||||
}, | |||||
{ | |||||
"Name": "WebMVC HTTP Check", | |||||
"Uri": "http://localhost:5100/hc" | |||||
}, | |||||
{ | |||||
"Name": "WebSPA HTTP Check", | |||||
"Uri": "http://localhost:5104/hc" | |||||
}, | |||||
{ | |||||
"Name": "SignalR HTTP Check", | |||||
"Uri": "http://localhost:5112/hc" | |||||
}, | |||||
{ | |||||
"Name": "Mobile Shopping API GW HTTP Check", | |||||
"Uri": "http://localhost:5200/hc" | |||||
}, | |||||
{ | |||||
"Name": "Mobile Marketing API GW HTTP Check", | |||||
"Uri": "http://localhost:5201/hc" | |||||
}, | |||||
{ | |||||
"Name": "Web Shopping API GW HTTP Check", | |||||
"Uri": "http://localhost:5202/hc" | |||||
}, | |||||
{ | |||||
"Name": "Web Marketing API GW HTTP Check", | |||||
"Uri": "http://localhost:5203/hc" | |||||
}, | |||||
{ | |||||
"Name": "Mobile Shopping Aggregator HTTP Check", | |||||
"Uri": "http://localhost:5120/hc" | |||||
}, | |||||
{ | |||||
"Name": "Web Shopping Aggregator HTTP Check", | |||||
"Uri": "http://localhost:5121/hc" | |||||
} | |||||
], | |||||
"Webhooks": [ | |||||
{ | |||||
"Name": "", | |||||
"Uri": "", | |||||
"Payload": "", | |||||
"RestoredPayload": "" | |||||
} | |||||
], | |||||
"EvaluationTimeOnSeconds": 10, | |||||
"MinimumSecondsBetweenFailureNotifications": 60 | |||||
} | } | ||||
} | } |