Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added Tags to health checks #70

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 14 additions & 10 deletions Src/Metrics/Core/HealthCheck.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,35 +8,39 @@ public struct Result
{
public readonly string Name;
public readonly HealthCheckResult Check;
public readonly MetricTags Tags;

public Result(string name, HealthCheckResult check)
public Result(string name, HealthCheckResult check, MetricTags tags = default(MetricTags))
{
this.Name = name;
this.Check = check;
this.Tags = tags;
}
}

private readonly Func<HealthCheckResult> check;

protected HealthCheck(string name)
: this(name, () => { })
protected HealthCheck(string name, MetricTags tags = default(MetricTags))
: this(name, () => { }, tags)
{ }

public HealthCheck(string name, Action check)
: this(name, () => { check(); return string.Empty; })
public HealthCheck(string name, Action check, MetricTags tags = default(MetricTags))
: this(name, () => { check(); return string.Empty; }, tags)
{ }

public HealthCheck(string name, Func<string> check)
: this(name, () => HealthCheckResult.Healthy(check()))
public HealthCheck(string name, Func<string> check, MetricTags tags = default(MetricTags))
: this(name, () => HealthCheckResult.Healthy(check()), tags)
{ }

public HealthCheck(string name, Func<HealthCheckResult> check)
public HealthCheck(string name, Func<HealthCheckResult> check, MetricTags tags = default(MetricTags))
{
this.Name = name;
this.check = check;
this.Tags = tags;
}

public string Name { get; }
public MetricTags Tags { get; set; }

protected virtual HealthCheckResult Check()
{
Expand All @@ -47,11 +51,11 @@ public Result Execute()
{
try
{
return new Result(this.Name, this.Check());
return new Result(this.Name, this.Check(), Tags);
}
catch (Exception x)
{
return new Result(this.Name, HealthCheckResult.Unhealthy(x));
return new Result(this.Name, HealthCheckResult.Unhealthy(x), Tags);
}
}
}
Expand Down
15 changes: 9 additions & 6 deletions Src/Metrics/HealthChecks.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,10 @@ public static class HealthChecks
/// </summary>
/// <param name="name">Name of the health check.</param>
/// <param name="check">Action to execute.</param>
public static void RegisterHealthCheck(string name, Action check)
/// <param name="tags">Optional set of tags that can be associated with the health check.</param>
public static void RegisterHealthCheck(string name, Action check, MetricTags tags = default(MetricTags))
{
RegisterHealthCheck(new HealthCheck(name, check));
RegisterHealthCheck(new HealthCheck(name, check, tags));
}

/// <summary>
Expand All @@ -57,9 +58,10 @@ public static void RegisterHealthCheck(string name, Action check)
/// </summary>
/// <param name="name">Name of the health check.</param>
/// <param name="check">Function to execute.</param>
public static void RegisterHealthCheck(string name, Func<string> check)
/// <param name="tags">Optional set of tags that can be associated with the health check.</param>
public static void RegisterHealthCheck(string name, Func<string> check, MetricTags tags = default(MetricTags))
{
RegisterHealthCheck(new HealthCheck(name, check));
RegisterHealthCheck(new HealthCheck(name, check, tags));
}

/// <summary>
Expand All @@ -68,9 +70,10 @@ public static void RegisterHealthCheck(string name, Func<string> check)
/// </summary>
/// <param name="name">Name of the health check.</param>
/// <param name="check">Function to execute</param>
public static void RegisterHealthCheck(string name, Func<HealthCheckResult> check)
/// <param name="tags">Optional set of tags that can be associated with the health check.</param>
public static void RegisterHealthCheck(string name, Func<HealthCheckResult> check, MetricTags tags = default(MetricTags))
{
RegisterHealthCheck(new HealthCheck(name, check));
RegisterHealthCheck(new HealthCheck(name, check, tags));
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

namespace Metrics.Json
{
public class JsonHealthChecks
public class JsonHealthChecksV1
{
public const int Version = 1;
public const string HealthChecksMimeType = "application/vnd.metrics.net.v1.health+json";
Expand All @@ -15,26 +15,26 @@ public class JsonHealthChecks
public static string BuildJson(HealthStatus status) { return BuildJson(status, Clock.Default, indented: false); }
public static string BuildJson(HealthStatus status, Clock clock, bool indented = true)
{
return new JsonHealthChecks()
return new JsonHealthChecksV1()
.AddVersion(Version)
.AddTimestamp(Clock.Default)
.AddObject(status)
.GetJson(indented);
}

public JsonHealthChecks AddVersion(int version)
public JsonHealthChecksV1 AddVersion(int version)
{
root.Add(new JsonProperty("Version", version.ToString(CultureInfo.InvariantCulture)));
return this;
}

public JsonHealthChecks AddTimestamp(Clock clock)
public JsonHealthChecksV1 AddTimestamp(Clock clock)
{
root.Add(new JsonProperty("Timestamp", Clock.FormatTimestamp(clock.UTCDateTime)));
return this;
}

public JsonHealthChecks AddObject(HealthStatus status)
public JsonHealthChecksV1 AddObject(HealthStatus status)
{
var properties = new List<JsonProperty>() { new JsonProperty("IsHealthy", status.IsHealthy) };
var unhealty = status.Results.Where(r => !r.Check.IsHealthy)
Expand Down
65 changes: 65 additions & 0 deletions Src/Metrics/Json/JsonHealthChecksV2.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using Metrics.Core;
using Metrics.Utils;

namespace Metrics.Json
{
public class JsonHealthChecksV2
{
public const int Version = 2;
public const string HealthChecksMimeType = "application/vnd.metrics.net.v2.health+json";

private readonly List<JsonProperty> root = new List<JsonProperty>();

public static string BuildJson(HealthStatus status) { return BuildJson(status, Clock.Default, indented: false); }
public static string BuildJson(HealthStatus status, Clock clock, bool indented = true)
{
return new JsonHealthChecksV2()
.AddVersion(Version)
.AddTimestamp(Clock.Default)
.AddObject(status)
.GetJson(indented);
}

public JsonHealthChecksV2 AddVersion(int version)
{
root.Add(new JsonProperty("Version", version.ToString(CultureInfo.InvariantCulture)));
return this;
}

public JsonHealthChecksV2 AddTimestamp(Clock clock)
{
root.Add(new JsonProperty("Timestamp", Clock.FormatTimestamp(clock.UTCDateTime)));
return this;
}

public JsonHealthChecksV2 AddObject(HealthStatus status)
{
var properties = new List<JsonProperty>() { new JsonProperty("IsHealthy", status.IsHealthy) };
var unhealty = status.Results.Where(r => !r.Check.IsHealthy);
properties.Add(new JsonProperty("Unhealthy", CreateHealthJsonObject(unhealty)));
var healthy = status.Results.Where(r => r.Check.IsHealthy);
properties.Add(new JsonProperty("Healthy", CreateHealthJsonObject(healthy)));
this.root.AddRange(properties);
return this;
}

private IEnumerable<JsonObject> CreateHealthJsonObject(IEnumerable<HealthCheck.Result> results)
{
return results.Select(r => new JsonObject(new List<JsonProperty>()
{
new JsonProperty("Name", r.Name),
new JsonProperty("Message", r.Check.Message),
new JsonProperty("Tags", r.Tags.Tags)
}));
}

public string GetJson(bool indented = true)
{
return new JsonObject(root).AsJson(indented);
}
}
}
3 changes: 2 additions & 1 deletion Src/Metrics/Metrics.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@
<Compile Include="Core\DefaultDataProvider.cs" />
<Compile Include="Core\DefaultRegistryDataProvider.cs" />
<Compile Include="Core\SimpleMeter.cs" />
<Compile Include="Json\JsonHealthChecksV2.cs" />
<Compile Include="Json\JsonMetric.cs" />
<Compile Include="Json\JsonMetricsContext.cs" />
<Compile Include="Json\JsonTimer.cs" />
Expand All @@ -96,7 +97,7 @@
<Compile Include="Json\JsonGauge.cs" />
<Compile Include="Json\JsonCounter.cs" />
<Compile Include="Core\ReadOnlyMetricsContext.cs" />
<Compile Include="Json\JsonHealthChecks.cs" />
<Compile Include="Json\JsonHealthChecksV1.cs" />
<Compile Include="MetricData\ConstantValue.cs" />
<Compile Include="MetricData\CounterValue.cs" />
<Compile Include="MetricData\EnvironmentEntry.cs" />
Expand Down
60 changes: 51 additions & 9 deletions Src/Metrics/Reporters/EndpointReporterConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,22 +15,62 @@ public static MetricsEndpointReports WithTextReport(this MetricsEndpointReports
return reports.WithEndpointReport(endpoint, (d, h, c) => new MetricsEndpointResponse(StringReport.RenderMetrics(d, h), "text/plain"));
}

#region HealthChecks

public static MetricsEndpointReports WithJsonHealthV1Report(this MetricsEndpointReports reports, string endpoint, bool alwaysReturnOkStatusCode = false)
{
return reports.WithEndpointReport(endpoint, (d, h, r) => GetHealthResponse(h, JsonHealthChecksV1.BuildJson, JsonHealthChecksV1.HealthChecksMimeType, alwaysReturnOkStatusCode));
}

public static MetricsEndpointReports WithJsonHealthV2Report(this MetricsEndpointReports reports, string endpoint, bool alwaysReturnOkStatusCode = false)
{
return reports.WithEndpointReport(endpoint, (d, h, r) => GetHealthResponse(h, JsonHealthChecksV2.BuildJson, JsonHealthChecksV2.HealthChecksMimeType, alwaysReturnOkStatusCode));
}

public static MetricsEndpointReports WithJsonHealthReport(this MetricsEndpointReports reports, string endpoint, bool alwaysReturnOkStatusCode = false)
{
return reports.WithEndpointReport(endpoint, (d, h, r) => GetHealthResponse(h, alwaysReturnOkStatusCode));
return reports.WithEndpointReport(endpoint, (d, h, r) => GetHealthResponse(h, GetJsonHealthCreator(r), GetJsonHealthMimeType(r), alwaysReturnOkStatusCode));
}

private static MetricsEndpointResponse GetHealthResponse(Func<HealthStatus> healthStatus, bool alwaysReturnOkStatusCode)
private static MetricsEndpointResponse GetHealthResponse(Func<HealthStatus> healthStatus, Func<HealthStatus, string> jsonCreator, string healthMimeType, bool alwaysReturnOkStatusCode)
{
var status = healthStatus();
var json = JsonHealthChecks.BuildJson(status);
var json = jsonCreator(status);

var httpStatus = status.IsHealthy || alwaysReturnOkStatusCode ? 200 : 500;
var httpStatusDescription = status.IsHealthy || alwaysReturnOkStatusCode ? "OK" : "Internal Server Error";

return new MetricsEndpointResponse(json, JsonHealthChecks.HealthChecksMimeType, Encoding.UTF8, httpStatus, httpStatusDescription);
return new MetricsEndpointResponse(json, healthMimeType, Encoding.UTF8, httpStatus, httpStatusDescription);
}

private static Func<HealthStatus, string> GetJsonHealthCreator(MetricsEndpointRequest request)
{
return IsJsonHealthV2(request)
? (Func<HealthStatus, string>)JsonHealthChecksV2.BuildJson
: JsonHealthChecksV1.BuildJson;
}

private static string GetJsonHealthMimeType(MetricsEndpointRequest request)
{
return IsJsonHealthV2(request)
? JsonHealthChecksV2.HealthChecksMimeType
: JsonHealthChecksV1.HealthChecksMimeType;
}

private static bool IsJsonHealthV2(MetricsEndpointRequest request)
{
string[] acceptHeader;
if (request.Headers.TryGetValue("Accept", out acceptHeader))
{
return acceptHeader.Contains(JsonHealthChecksV2.HealthChecksMimeType);
}
return false;
}

#endregion HealthChecks

#region MetricsData

public static MetricsEndpointReports WithJsonV1Report(this MetricsEndpointReports reports, string endpoint)
{
return reports.WithEndpointReport(endpoint, GetJsonV1Response);
Expand Down Expand Up @@ -58,11 +98,6 @@ public static MetricsEndpointReports WithJsonReport(this MetricsEndpointReports
return reports.WithEndpointReport(endpoint, GetJsonResponse);
}

public static MetricsEndpointReports WithPing(this MetricsEndpointReports reports)
{
return reports.WithEndpointReport("/ping", (d, h, r) => new MetricsEndpointResponse("pong", "text/plain"));
}

private static MetricsEndpointResponse GetJsonResponse(MetricsData data, Func<HealthStatus> healthStatus, MetricsEndpointRequest request)
{
string[] acceptHeader;
Expand All @@ -75,5 +110,12 @@ private static MetricsEndpointResponse GetJsonResponse(MetricsData data, Func<He

return GetJsonV1Response(data, healthStatus, request);
}

#endregion MetricsData

public static MetricsEndpointReports WithPing(this MetricsEndpointReports reports)
{
return reports.WithEndpointReport("/ping", (d, h, r) => new MetricsEndpointResponse("pong", "text/plain"));
}
}
}
3 changes: 2 additions & 1 deletion Src/Metrics/Reporters/MetricsEndpointReports.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,9 @@ private void RegisterDefaultEndpoints()
{
this
.WithTextReport("/text")
.WithJsonHealthV1Report("/v1/health")
.WithJsonHealthV2Report("/v2/health")
.WithJsonHealthReport("/health")
.WithJsonHealthReport("/v1/health")
.WithJsonV1Report("/v1/json")
.WithJsonV2Report("/v2/json")
.WithJsonReport("/json")
Expand Down