From 4549b84cb563de5e173849cf920498c3bdb735a1 Mon Sep 17 00:00:00 2001 From: Ryan Nowak Date: Fri, 3 Aug 2018 09:40:45 -0700 Subject: [PATCH] Remove JSON output (#457) * Remove JSON output and convert to sample GlennC and I made the decisison to turn the JSON output into a sample rather than something we support out of the box. We wouldn't tell customers to definitely use it and we don't want to introduce more coupling to JSON.NET. --- .../HealthChecksSample/CustomWriterStartup.cs | 29 +++---- .../DetailedStatusStartup.cs | 68 ---------------- .../HealthChecksSample/GCInfoHealthCheck.cs | 2 +- .../HealthChecksSample.csproj | 1 + .../LivenessProbeStartup.cs | 11 +-- samples/HealthChecksSample/Program.cs | 1 - ...HealthCheckApplicationBuilderExtensions.cs | 14 +++- .../HealthCheckResponseWriters.cs | 40 +-------- ...AspNetCore.Diagnostics.HealthChecks.csproj | 3 +- .../HealthCheckMiddlewareSampleTest.cs | 60 +------------- .../HealthCheckMiddlewareTests.cs | 81 ++++--------------- 11 files changed, 48 insertions(+), 262 deletions(-) delete mode 100644 samples/HealthChecksSample/DetailedStatusStartup.cs diff --git a/samples/HealthChecksSample/CustomWriterStartup.cs b/samples/HealthChecksSample/CustomWriterStartup.cs index 87e58b7b7d..97003068fc 100644 --- a/samples/HealthChecksSample/CustomWriterStartup.cs +++ b/samples/HealthChecksSample/CustomWriterStartup.cs @@ -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($@" - - -

- Everything is {result.Status} -

- - - - - - {string.Join("", result.Results.Select(kvp => $""))} - -
NameStatus
{kvp.Key}{kvp.Value.Status}
- -"); + 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)); } } } diff --git a/samples/HealthChecksSample/DetailedStatusStartup.cs b/samples/HealthChecksSample/DetailedStatusStartup.cs deleted file mode 100644 index 76475c5db5..0000000000 --- a/samples/HealthChecksSample/DetailedStatusStartup.cs +++ /dev/null @@ -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() - { - { "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"); - }); - } - } -} diff --git a/samples/HealthChecksSample/GCInfoHealthCheck.cs b/samples/HealthChecksSample/GCInfoHealthCheck.cs index 644fdb0a32..f37ade8980 100644 --- a/samples/HealthChecksSample/GCInfoHealthCheck.cs +++ b/samples/HealthChecksSample/GCInfoHealthCheck.cs @@ -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 { diff --git a/samples/HealthChecksSample/HealthChecksSample.csproj b/samples/HealthChecksSample/HealthChecksSample.csproj index 2f3c0e6aaa..c8a2fded73 100644 --- a/samples/HealthChecksSample/HealthChecksSample.csproj +++ b/samples/HealthChecksSample/HealthChecksSample.csproj @@ -11,6 +11,7 @@ + diff --git a/samples/HealthChecksSample/LivenessProbeStartup.cs b/samples/HealthChecksSample/LivenessProbeStartup.cs index d7645b5f0e..3a4078759f 100644 --- a/samples/HealthChecksSample/LivenessProbeStartup.cs +++ b/samples/HealthChecksSample/LivenessProbeStartup.cs @@ -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) => diff --git a/samples/HealthChecksSample/Program.cs b/samples/HealthChecksSample/Program.cs index 4d9455ed2d..b4e8874f22 100644 --- a/samples/HealthChecksSample/Program.cs +++ b/samples/HealthChecksSample/Program.cs @@ -16,7 +16,6 @@ namespace HealthChecksSample { { "", typeof(BasicStartup) }, { "basic", typeof(BasicStartup) }, - { "detailed", typeof(DetailedStatusStartup) }, { "writer", typeof(CustomWriterStartup) }, { "liveness", typeof(LivenessProbeStartup) }, }; diff --git a/src/Microsoft.AspNetCore.Diagnostics.HealthChecks/Builder/HealthCheckApplicationBuilderExtensions.cs b/src/Microsoft.AspNetCore.Diagnostics.HealthChecks/Builder/HealthCheckApplicationBuilderExtensions.cs index f1f47b3ba5..e6239a3488 100644 --- a/src/Microsoft.AspNetCore.Diagnostics.HealthChecks/Builder/HealthCheckApplicationBuilderExtensions.cs +++ b/src/Microsoft.AspNetCore.Diagnostics.HealthChecks/Builder/HealthCheckApplicationBuilderExtensions.cs @@ -20,7 +20,13 @@ namespace Microsoft.AspNetCore.Builder /// The path on which to provide health check status. /// A reference to the after the operation has completed. /// - /// The health check middleware will use default settings other than the provided . + /// + /// This method will use to + /// listen to health checks requests on the specified URL path. + /// + /// + /// The health check middleware will use default settings from . + /// /// public static IApplicationBuilder UseHealthChecks(this IApplicationBuilder app, PathString path) { @@ -44,6 +50,12 @@ namespace Microsoft.AspNetCore.Builder /// The path on which to provide health check status. /// A used to configure the middleware. /// A reference to the after the operation has completed. + /// + /// + /// This method will use to + /// listen to health checks requests on the specified URL path. + /// + /// public static IApplicationBuilder UseHealthChecks(this IApplicationBuilder app, PathString path, HealthCheckOptions options) { if (app == null) diff --git a/src/Microsoft.AspNetCore.Diagnostics.HealthChecks/HealthCheckResponseWriters.cs b/src/Microsoft.AspNetCore.Diagnostics.HealthChecks/HealthCheckResponseWriters.cs index 95ddf00ac8..a74072f63f 100644 --- a/src/Microsoft.AspNetCore.Diagnostics.HealthChecks/HealthCheckResponseWriters.cs +++ b/src/Microsoft.AspNetCore.Diagnostics.HealthChecks/HealthCheckResponseWriters.cs @@ -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)); - } } } diff --git a/src/Microsoft.AspNetCore.Diagnostics.HealthChecks/Microsoft.AspNetCore.Diagnostics.HealthChecks.csproj b/src/Microsoft.AspNetCore.Diagnostics.HealthChecks/Microsoft.AspNetCore.Diagnostics.HealthChecks.csproj index 76a24a4c47..b7dd85d4a6 100644 --- a/src/Microsoft.AspNetCore.Diagnostics.HealthChecks/Microsoft.AspNetCore.Diagnostics.HealthChecks.csproj +++ b/src/Microsoft.AspNetCore.Diagnostics.HealthChecks/Microsoft.AspNetCore.Diagnostics.HealthChecks.csproj @@ -1,4 +1,4 @@ - + ASP.NET Core middleware for returning the results of Health Checks in the application @@ -16,7 +16,6 @@ - diff --git a/test/Microsoft.AspNetCore.Diagnostics.HealthChecks.Tests/HealthCheckMiddlewareSampleTest.cs b/test/Microsoft.AspNetCore.Diagnostics.HealthChecks.Tests/HealthCheckMiddlewareSampleTest.cs index 4e716ed3b9..bffed9ed66 100644 --- a/test/Microsoft.AspNetCore.Diagnostics.HealthChecks.Tests/HealthCheckMiddlewareSampleTest.cs +++ b/test/Microsoft.AspNetCore.Diagnostics.HealthChecks.Tests/HealthCheckMiddlewareSampleTest.cs @@ -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(); - - 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(); @@ -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(); @@ -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()); } } } diff --git a/test/Microsoft.AspNetCore.Diagnostics.HealthChecks.Tests/HealthCheckMiddlewareTests.cs b/test/Microsoft.AspNetCore.Diagnostics.HealthChecks.Tests/HealthCheckMiddlewareTests.cs index 74d23c81bf..0555ab6f7a 100644 --- a/test/Microsoft.AspNetCore.Diagnostics.HealthChecks.Tests/HealthCheckMiddlewareTests.cs +++ b/test/Microsoft.AspNetCore.Diagnostics.HealthChecks.Tests/HealthCheckMiddlewareTests.cs @@ -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() - { - { "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); }