Merge pull request #458 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 10:24:44 -07:00 committed by GitHub
commit 0973a723fe
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 48 additions and 262 deletions

View File

@ -6,6 +6,8 @@ using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Diagnostics.HealthChecks;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace HealthChecksSample
{
@ -45,23 +47,16 @@ namespace HealthChecksSample
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>");
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,68 +0,0 @@
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

@ -7,7 +7,7 @@ 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
{

View File

@ -11,6 +11,7 @@
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="$(MicrosoftExtensionsLoggingConsolePackageVersion)" />
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="$(MicrosoftAspNetCoreServerKestrelPackageVersion)" />
<PackageReference Include="Microsoft.AspNetCore.StaticFiles" Version="$(MicrosoftAspNetCoreStaticFilesPackageVersion)" />
<PackageReference Include="Newtonsoft.Json" Version="$(NewtonsoftJsonPackageVersion)" />
</ItemGroup>
<ItemGroup>

View File

@ -48,12 +48,7 @@ namespace HealthChecksSample
// 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,
});
app.UseHealthChecks("/health/ready");
// The liveness check uses an 'identity' health check that always returns healty
app.UseHealthChecks("/health/live", new HealthCheckOptions()
@ -63,10 +58,6 @@ namespace HealthChecksSample
{
"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) =>

View File

@ -16,7 +16,6 @@ namespace HealthChecksSample
{
{ "", typeof(BasicStartup) },
{ "basic", typeof(BasicStartup) },
{ "detailed", typeof(DetailedStatusStartup) },
{ "writer", typeof(CustomWriterStartup) },
{ "liveness", typeof(LivenessProbeStartup) },
};

View File

@ -20,7 +20,13 @@ namespace Microsoft.AspNetCore.Builder
/// <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"/>.
/// <para>
/// This method will use <see cref="MapExtensions.Map(IApplicationBuilder, PathString, Action{IApplicationBuilder})"/> to
/// listen to health checks requests on the specified URL path.
/// </para>
/// <para>
/// The health check middleware will use default settings from <see cref="IOptions{HealthCheckOptions}"/>.
/// </para>
/// </remarks>
public static IApplicationBuilder UseHealthChecks(this IApplicationBuilder app, PathString path)
{
@ -44,6 +50,12 @@ namespace Microsoft.AspNetCore.Builder
/// <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>
/// <remarks>
/// <para>
/// This method will use <see cref="MapExtensions.Map(IApplicationBuilder, PathString, Action{IApplicationBuilder})"/> to
/// listen to health checks requests on the specified URL path.
/// </para>
/// </remarks>
public static IApplicationBuilder UseHealthChecks(this IApplicationBuilder app, PathString path, HealthCheckOptions options)
{
if (app == null)

View File

@ -1,56 +1,18 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
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
internal 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,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Description>ASP.NET Core middleware for returning the results of Health Checks in the application</Description>
@ -16,7 +16,6 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="$(NewtonsoftJsonPackageVersion)" />
<PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="$(MicrosoftAspNetCoreHttpAbstractionsPackageVersion)" />
<PackageReference Include="Microsoft.Extensions.Options" Version="$(MicrosoftExtensionsOptionsPackageVersion)" />
</ItemGroup>

View File

@ -1,12 +1,10 @@
// 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
@ -37,22 +35,6 @@ namespace Microsoft.AspNetCore.Diagnostics.HealthChecks
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());
@ -63,20 +45,6 @@ namespace Microsoft.AspNetCore.Diagnostics.HealthChecks
[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>();
@ -85,33 +53,13 @@ namespace Microsoft.AspNetCore.Diagnostics.HealthChecks
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());
Assert.Equal("text/plain", response.Content.Headers.ContentType.ToString());
Assert.Equal("Healthy", 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>();
@ -120,8 +68,8 @@ namespace Microsoft.AspNetCore.Diagnostics.HealthChecks
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());
Assert.Equal("text/plain", response.Content.Headers.ContentType.ToString());
Assert.Equal("Unhealthy", await response.Content.ReadAsStringAsync());
}
}
}

View File

@ -7,6 +7,7 @@ using System.Net;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.TestHost;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Diagnostics.HealthChecks;
@ -201,94 +202,40 @@ namespace Microsoft.AspNetCore.Diagnostics.HealthChecks
}
[Fact]
public async Task DetailedJsonReturnsEmptyHealthyResponseIfNoHealthChecksRegistered()
{
var expectedJson = JsonConvert.SerializeObject(new
{
status = "Healthy",
results = new { }
}, Formatting.Indented);
var builder = new WebHostBuilder()
.Configure(app =>
{
app.UseHealthChecks("/health", new HealthCheckOptions()
{
ResponseWriter = HealthCheckResponseWriters.WriteDetailedJson,
});
})
.ConfigureServices(services =>
{
services.AddHealthChecks();
});
var server = new TestServer(builder);
var client = server.CreateClient();
var response = await client.GetAsync("/health");
var result = await response.Content.ReadAsStringAsync();
Assert.Equal(expectedJson, result);
}
[Fact]
public async Task DetailedJsonReturnsResultsFromHealthChecks()
public async Task CanUseCustomWriter()
{
var expectedJson = JsonConvert.SerializeObject(new
{
status = "Unhealthy",
results = new
{
Foo = new
{
status = "Healthy",
description = "Good to go!",
data = new { }
},
Bar = new
{
status = "Degraded",
description = "Feeling a bit off.",
data = new { someUsefulAttribute = 42 }
},
Baz = new
{
status = "Unhealthy",
description = "Not feeling good at all",
data = new { }
},
Boz = new
{
status = "Unhealthy",
description = string.Empty,
data = new { }
},
},
}, Formatting.Indented);
});
var builder = new WebHostBuilder()
.Configure(app =>
{
app.UseHealthChecks("/health", new HealthCheckOptions()
{
ResponseWriter = HealthCheckResponseWriters.WriteDetailedJson,
ResponseWriter = (c, r) =>
{
var json = JsonConvert.SerializeObject(new { status = r.Status.ToString(), });
c.Response.ContentType = "application/json";
return c.Response.WriteAsync(json);
},
});
})
.ConfigureServices(services =>
{
services.AddHealthChecks()
.AddCheck("Foo", () => Task.FromResult(HealthCheckResult.Healthy("Good to go!")))
.AddCheck("Bar", () => Task.FromResult(HealthCheckResult.Degraded("Feeling a bit off.", new Dictionary<string, object>()
{
{ "someUsefulAttribute", 42 }
})))
.AddCheck("Baz", () => Task.FromResult(HealthCheckResult.Unhealthy("Not feeling good at all", new Exception("Bad times"))))
.AddCheck("Boz", () => Task.FromResult(HealthCheckResult.Unhealthy(new Exception("Very bad times"))));
.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("application/json", response.Content.Headers.ContentType.ToString());
var result = await response.Content.ReadAsStringAsync();
Assert.Equal(expectedJson, result);
}