├── xunit.runner.json ├── global.json ├── tools └── packages.config ├── sandbox └── HealthSandbox │ ├── appsettings.json │ ├── SampleHealthStatusReporter.cs │ ├── HealthChecks │ ├── SampleHealthCheck.cs │ ├── SampleCachedHealthCheck.cs │ └── SampleQuiteTimeHealthCheck.cs │ └── HealthSandbox.csproj ├── ISSUE_TEMPLATE.md ├── NuGet.config ├── version.props ├── test ├── App.Metrics.Health.Formatters.Json.Facts │ ├── JsonFiles │ │ ├── healthstatus_null_healthy.json │ │ ├── healthstatus_null_unhealthy.json │ │ └── healthstatus.json │ ├── App.Metrics.Health.Formatters.Json.Facts.csproj │ └── Helpers │ │ └── TestHelperExtensions.cs ├── App.Metrics.Health.Facts │ ├── TestHelpers │ │ ├── IDatabase.cs │ │ ├── Database.cs │ │ ├── SampleHealthCheck.cs │ │ ├── IgnoreAttributeHealthCheck.cs │ │ ├── TestIgnoreAttributeHealthCheck.cs │ │ ├── DatabaseHealthCheck.cs │ │ └── TestReporter.cs │ ├── App.Metrics.Health.Facts.csproj │ ├── App.Metrics.Health.Facts.csproj.DotSettings │ ├── HealthCheckResultTests.cs │ ├── Builders │ │ ├── HealthReportingBuilderTests.cs │ │ ├── HealthBuilderTests.cs │ │ ├── HealthConfigurationBuilderTests.cs │ │ └── HealthCheckBuilderTests.cs │ └── HealthCheckRunnerTests.cs ├── App.Metrics.Health.Formatters.Ascii.Facts │ ├── App.Metrics.Health.Formatters.Ascii.Facts.csproj │ └── HealthStatusTextWriterTests.cs ├── App.Metrics.Health.Checks.Process.Facts │ └── App.Metrics.Health.Checks.Process.Facts.csproj └── Directory.Build.props ├── src ├── Directory.Build.props ├── App.Metrics.Health.Abstractions │ ├── IHealth.cs │ ├── App.Metrics.Health.Abstractions.csproj.DotSettings │ ├── Internal │ │ ├── ExcludeFromCodeCoverageAttribute.cs │ │ ├── AppMetricsHealthTaskHelper.cs │ │ └── HealthReporterCollection.cs │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── Serialization │ │ ├── IHealthStatusWriter.cs │ │ └── HealthStatusSerializer.cs │ ├── Formatters │ │ ├── IHealthOutputFormatter.cs │ │ ├── HealthMediaTypeValue.cs │ │ └── HealthFormatterCollection.cs │ ├── IReportHealthStatus.cs │ ├── HealthCheckStatusExtensions.cs │ ├── HealthCheckStatus.cs │ ├── IRunHealthChecks.cs │ ├── Builder │ │ ├── IHealthCheckBuilder.cs │ │ ├── Extensions │ │ │ ├── CacheHealthCheckBuilderExtensions.cs │ │ │ └── QuiteTimeHealthCheckBuilderExtensions.cs │ │ ├── IHealthBuilder.cs │ │ ├── IHealthReportingBuilder.cs │ │ ├── IHealthConfigurationBuilder.cs │ │ └── IHealthOutputFormattingBuilder.cs │ ├── App.Metrics.Health.Abstractions.csproj │ ├── HealthOptions.cs │ ├── HealthStatus.cs │ ├── IHealthRoot.cs │ ├── HealthConstants.cs │ └── HealthCheckCached.cs ├── App.Metrics.Health.Core │ ├── App.Metrics.Health.Core.csproj.DotSettings │ ├── Internal │ │ ├── DefaultHealth.cs │ │ ├── Extensions │ │ │ ├── HealthOptionsExtensions.cs │ │ │ └── AppMetricsHealthLoggerExtensions.cs │ │ ├── NoOp │ │ │ └── NoOpHealthCheckRunner.cs │ │ ├── DefaultHealthCheckRunner.cs │ │ └── KeyValuePairHealthOptions.cs │ ├── App.Metrics.Health.Core.csproj │ ├── HealthRoot.cs │ └── Builder │ │ ├── HealthReportingBuilder.cs │ │ ├── HealthCheckBuilder.cs │ │ ├── HealthOutputFormattingBuilder.cs │ │ ├── HealthConfigurationBuilder.cs │ │ └── HealthBuilder.cs ├── App.Metrics.Health.Formatters.Ascii │ ├── App.Metrics.Health.Formatters.Ascii.csproj.DotSettings │ ├── Internal │ │ └── HealthStatusFormatterConstants.cs │ ├── App.Metrics.Health.Formatters.Ascii.csproj │ ├── HealthTextOptions.cs │ ├── Builder │ │ └── HealthTextOutputFormatterBuilder.cs │ ├── HealthStatusTextOutputFormatter.cs │ └── HealthStatusTextWriter.cs ├── App.Metrics.Health.Formatters.Json │ ├── App.Metrics.Health.Formatters.Json.csproj.DotSettings │ ├── HealthJsonOptions.cs │ ├── HealthStatusData.cs │ ├── App.Metrics.Health.Formatters.Json.csproj │ ├── DefaultJsonSerializerSettings.cs │ ├── Builder │ │ └── HealthJsonOutputFormatterBuilder.cs │ ├── HealthStatusJsonOutputFormatter.cs │ └── Converters │ │ └── HealthStatusConverter.cs ├── App.Metrics.Health.Reporting.Metrics │ ├── Internal │ │ ├── HealthReportingConstants.cs │ │ └── ApplicationHealthMetricRegistry.cs │ ├── HealthAsMetricsOptions.cs │ ├── App.Metrics.Health.Reporting.Metrics.csproj │ ├── Builder │ │ └── HealthResultsAsMetricsBuilderExtensions.cs │ └── HealthResultsAsMetricsReporter.cs ├── App.Metrics.Health.Reporting.Slack │ ├── Internal │ │ ├── SlackAttachmentFields.cs │ │ ├── SlackPayload.cs │ │ ├── SlackAttachment.cs │ │ ├── DefaultJsonSerializerSettings.cs │ │ └── JsonContent.cs │ ├── App.Metrics.Health.Reporting.Slack.csproj │ ├── Builder │ │ └── SlackHealthAlerterBuilderExtensions.cs │ └── SlackHealthAlertOptions.cs ├── App.Metrics.Health.Checks.Http │ └── App.Metrics.Health.Checks.Http.csproj ├── App.Metrics.Health │ ├── App.Metrics.Health.csproj │ └── AppMetricsHealth.cs ├── App.Metrics.Health.Checks.Sql │ └── App.Metrics.Health.Checks.Sql.csproj ├── App.Metrics.Health.Checks.Network │ ├── App.Metrics.Health.Checks.Network.csproj │ └── PingHealthCheckBuilderExtensions.cs └── App.Metrics.Health.Checks.Process │ └── App.Metrics.Health.Checks.Process.csproj ├── benchmarks ├── App.Metrics.Health.Benchmarks │ ├── Support │ │ ├── BenchmarksAssemblyMarker.cs │ │ ├── TestConfigs.cs │ │ ├── BenchmarkTestRunner.cs │ │ ├── OutputLogger.cs │ │ ├── SimpleBenchmarkRunner.cs │ │ └── BenchmarkTestExecutor.cs │ ├── Configs │ │ └── DefaultConfig.cs │ ├── DefaultBenchmarkBase.cs │ ├── BenchmarkDotNetBenchmarks │ │ └── MeasureReadHealthStatusBenchmark.cs │ ├── XunitHarness │ │ └── Health.cs │ ├── Fixtures │ │ └── HealthTestFixture.cs │ ├── Facts │ │ └── HealthCheck.cs │ └── App.Metrics.Health.Benchmarks.csproj └── App.Metrics.Benchmarks.Runner │ ├── BenchmarkDotNet.Artifacts │ └── results │ │ └── MeasureReadHealthStatusBenchmark-report-github.md │ ├── App.Metrics.Benchmarks.Runner.csproj │ └── Program.cs ├── GitReleaseManager.yaml ├── run-benchmarks.ps1 ├── app-metrics.licenseheader ├── stylecop.json ├── PULL_REQUEST_TEMPLATE.md ├── .travis.yml ├── appveyor.yml ├── .gitignore ├── CONTRIBUTING.md └── .gitattributes /xunit.runner.json: -------------------------------------------------------------------------------- 1 | { 2 | } -------------------------------------------------------------------------------- /global.json: -------------------------------------------------------------------------------- 1 | { 2 | "projects": [ "src", "test", "benchmarks", "sandbox" ], 3 | "sdk": { 4 | "version": "2.1.300" 5 | } 6 | } -------------------------------------------------------------------------------- /tools/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /sandbox/HealthSandbox/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "HealthOptions": { 3 | "Enabled": true, 4 | "ApplicationName": "http://health.local.com" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | Before logging a new issue, please have a quick read through the [contribution guidlines](https://github.com/alhardy/AppMetrics/blob/master/CONTRIBUTING.md). 2 | -------------------------------------------------------------------------------- /NuGet.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /version.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 2.1.0 5 | 6 | 7 | -------------------------------------------------------------------------------- /test/App.Metrics.Health.Formatters.Json.Facts/JsonFiles/healthstatus_null_healthy.json: -------------------------------------------------------------------------------- 1 | { 2 | "healthy": { 3 | "test_one_healthy": "first check was good", 4 | "test_two_healthy": "second check was good" 5 | }, 6 | "status": "Healthy" 7 | } -------------------------------------------------------------------------------- /src/Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /test/App.Metrics.Health.Formatters.Json.Facts/JsonFiles/healthstatus_null_unhealthy.json: -------------------------------------------------------------------------------- 1 | { 2 | "status": "Unhealthy", 3 | "unhealthy": { 4 | "test_three_unhealthy": "something failed", 5 | "test_four_unhealthy": "something else failed" 6 | } 7 | } -------------------------------------------------------------------------------- /test/App.Metrics.Health.Facts/TestHelpers/IDatabase.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) App Metrics Contributors. All rights reserved. 3 | // 4 | 5 | namespace App.Metrics.Health.Facts.TestHelpers 6 | { 7 | public interface IDatabase 8 | { 9 | void Ping(); 10 | } 11 | } -------------------------------------------------------------------------------- /test/App.Metrics.Health.Facts/App.Metrics.Health.Facts.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $(StandardTest) 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /test/App.Metrics.Health.Facts/TestHelpers/Database.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) App Metrics Contributors. All rights reserved. 3 | // 4 | 5 | namespace App.Metrics.Health.Facts.TestHelpers 6 | { 7 | public class Database : IDatabase 8 | { 9 | public void Ping() { } 10 | } 11 | } -------------------------------------------------------------------------------- /benchmarks/App.Metrics.Health.Benchmarks/Support/BenchmarksAssemblyMarker.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) App Metrics Contributors. All rights reserved. 3 | // 4 | 5 | namespace App.Metrics.Health.Benchmarks.Support 6 | { 7 | public class BenchmarksAssemblyMarker 8 | { 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/App.Metrics.Health.Abstractions/IHealth.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) App Metrics Contributors. All rights reserved. 3 | // 4 | 5 | using System.Collections.Generic; 6 | 7 | namespace App.Metrics.Health 8 | { 9 | public interface IHealth 10 | { 11 | IEnumerable Checks { get; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /test/App.Metrics.Health.Formatters.Json.Facts/JsonFiles/healthstatus.json: -------------------------------------------------------------------------------- 1 | { 2 | "degraded": { 3 | "test_five_degraded": "degrading service" 4 | }, 5 | "healthy": { 6 | "test_one_healthy": "first check was good", 7 | "test_two_healthy": "second check was good" 8 | }, 9 | "status": "Unhealthy", 10 | "unhealthy": { 11 | "test_three_unhealthy": "something failed", 12 | "test_four_unhealthy": "something else failed" 13 | } 14 | } -------------------------------------------------------------------------------- /src/App.Metrics.Health.Core/App.Metrics.Health.Core.csproj.DotSettings: -------------------------------------------------------------------------------- 1 | 2 | CSharp71 -------------------------------------------------------------------------------- /test/App.Metrics.Health.Facts/App.Metrics.Health.Facts.csproj.DotSettings: -------------------------------------------------------------------------------- 1 | 2 | CSharp71 -------------------------------------------------------------------------------- /src/App.Metrics.Health.Abstractions/App.Metrics.Health.Abstractions.csproj.DotSettings: -------------------------------------------------------------------------------- 1 | 2 | CSharp71 -------------------------------------------------------------------------------- /src/App.Metrics.Health.Formatters.Ascii/App.Metrics.Health.Formatters.Ascii.csproj.DotSettings: -------------------------------------------------------------------------------- 1 | 2 | CSharp71 -------------------------------------------------------------------------------- /src/App.Metrics.Health.Formatters.Json/App.Metrics.Health.Formatters.Json.csproj.DotSettings: -------------------------------------------------------------------------------- 1 | 2 | CSharp71 -------------------------------------------------------------------------------- /src/App.Metrics.Health.Formatters.Json/HealthJsonOptions.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) App Metrics Contributors. All rights reserved. 3 | // 4 | 5 | using Newtonsoft.Json; 6 | 7 | namespace App.Metrics.Health.Formatters.Json 8 | { 9 | public class HealthJsonOptions 10 | { 11 | public JsonSerializerSettings SerializerSettings { get; } = 12 | DefaultJsonSerializerSettings.CreateSerializerSettings(); 13 | } 14 | } -------------------------------------------------------------------------------- /test/App.Metrics.Health.Formatters.Ascii.Facts/App.Metrics.Health.Formatters.Ascii.Facts.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $(StandardTest) 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /test/App.Metrics.Health.Checks.Process.Facts/App.Metrics.Health.Checks.Process.Facts.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $(StandardTest) 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/App.Metrics.Health.Reporting.Metrics/Internal/HealthReportingConstants.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) App Metrics Contributors. All rights reserved. 3 | // 4 | 5 | namespace App.Metrics.Health.Reporting.Metrics.Internal 6 | { 7 | public static class HealthReportingConstants 8 | { 9 | public static class TagKeys 10 | { 11 | public const string HealthCheckName = "health_check_name"; 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /GitReleaseManager.yaml: -------------------------------------------------------------------------------- 1 | create: 2 | include-footer: false 3 | footer-heading: 4 | footer-content: 5 | footer-includes-milestone: false 6 | milestone-replace-text: 7 | export: 8 | include-created-date-in-title: false 9 | created-date-string-format: 10 | perform-regex-removal: false 11 | regex-text: 12 | multiline-regex: false 13 | issue-labels-include: 14 | - bug 15 | - new feature 16 | - enhancement 17 | - breaking change 18 | - documentation 19 | - refactoring 20 | - investigation 21 | issue-labels-exclude: 22 | - wontfix 23 | - duplicate -------------------------------------------------------------------------------- /src/App.Metrics.Health.Abstractions/Internal/ExcludeFromCodeCoverageAttribute.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) App Metrics Contributors. All rights reserved. 3 | // 4 | 5 | #if NETSTANDARD1_1 || NETSTANDARD1_3 || NETSTANDARD1_6 6 | 7 | // ReSharper disable CheckNamespace 8 | namespace System.Diagnostics.CodeAnalysis 9 | // ReSharper restore CheckNamespace 10 | { 11 | internal class ExcludeFromCodeCoverageAttribute : Attribute { } 12 | } 13 | 14 | #endif 15 | -------------------------------------------------------------------------------- /src/App.Metrics.Health.Formatters.Ascii/Internal/HealthStatusFormatterConstants.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) App Metrics Contributors. All rights reserved. 3 | // 4 | 5 | namespace App.Metrics.Health.Formatters.Ascii.Internal 6 | { 7 | public class HealthStatusFormatterConstants 8 | { 9 | public static class OutputFormatting 10 | { 11 | public const int Padding = 20; 12 | public const string Separator = "="; 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /run-benchmarks.ps1: -------------------------------------------------------------------------------- 1 | Push-Location $PSScriptRoot 2 | 3 | #.\build.cmd 4 | 5 | Remove-Item .\benchmark-results -Force -Recurse 6 | 7 | foreach ($test in ls benchmarks/*.Benchmarks) { 8 | Push-Location $test 9 | 10 | Remove-Item BenchmarkDotNet.Artifacts -Force -Recurse 11 | 12 | echo "perf: Running benchmark test project in $test" 13 | 14 | & dotnet test -c Release 15 | if($LASTEXITCODE -ne 0) { exit 2 } 16 | 17 | Pop-Location 18 | 19 | Copy-Item $test\BenchmarkDotNet.Artifacts\results -Destination .\benchmark-results -Recurse -Force -Filter "*.md" 20 | } 21 | 22 | 23 | Pop-Location -------------------------------------------------------------------------------- /src/App.Metrics.Health.Abstractions/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) App Metrics Contributors. All rights reserved. 3 | // 4 | 5 | using System.Runtime.CompilerServices; 6 | 7 | [assembly: InternalsVisibleTo("App.Metrics.Health.Core, PublicKey=00240000048000009400000006020000002400005253413100040000010001000961061aa9f970163db728a9792c1cfb7be51f2f986054c676345f9e43f95af5f3114b1962d10888a3ea1dff99bf56bce565f887cb4b004fc44ccb7335700260012a65b9cdd090e6b60c8c67c434ca49563c82c66695f8dc0776770bfaf481ef816767b7dd67d083960a6fcfe33c3c0e1cc198fe7a13b3283133d21b3435ebbb")] -------------------------------------------------------------------------------- /src/App.Metrics.Health.Abstractions/Serialization/IHealthStatusWriter.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) App Metrics Contributors. All rights reserved. 3 | // 4 | 5 | using System; 6 | 7 | namespace App.Metrics.Health.Serialization 8 | { 9 | public interface IHealthStatusWriter : IDisposable 10 | { 11 | /// 12 | /// Writes the specified . 13 | /// 14 | /// The health status to write. 15 | void Write(HealthStatus healthStatus); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/App.Metrics.Health.Core/Internal/DefaultHealth.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) App Metrics Contributors. All rights reserved. 3 | // 4 | 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | 8 | namespace App.Metrics.Health.Internal 9 | { 10 | public class DefaultHealth : IHealth 11 | { 12 | public DefaultHealth(IEnumerable checks) 13 | { 14 | Checks = checks ?? Enumerable.Empty(); 15 | } 16 | 17 | /// 18 | public IEnumerable Checks { get; } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/App.Metrics.Health.Abstractions/Formatters/IHealthOutputFormatter.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) App Metrics Contributors. All rights reserved. 3 | // 4 | 5 | using System.IO; 6 | using System.Threading; 7 | using System.Threading.Tasks; 8 | 9 | namespace App.Metrics.Health.Formatters 10 | { 11 | public interface IHealthOutputFormatter 12 | { 13 | HealthMediaTypeValue MediaType { get; } 14 | 15 | Task WriteAsync( 16 | Stream output, 17 | HealthStatus healthStatus, 18 | CancellationToken cancellationToken = default); 19 | } 20 | } -------------------------------------------------------------------------------- /app-metrics.licenseheader: -------------------------------------------------------------------------------- 1 | extensions: designer.cs generated.cs 2 | extensions: .cs .cpp .h 3 | // Copyright (c) Allan hardy. All rights reserved. 4 | // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. 5 | 6 | extensions: .cshtml 7 | @* 8 | // Copyright (c) Allan hardy. All rights reserved. 9 | // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. 10 | *@ 11 | 12 | extensions: .xml .config .xsd 13 | -------------------------------------------------------------------------------- /benchmarks/App.Metrics.Health.Benchmarks/Configs/DefaultConfig.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) App Metrics Contributors. All rights reserved. 3 | // 4 | 5 | using BenchmarkDotNet.Configs; 6 | using BenchmarkDotNet.Diagnosers; 7 | using BenchmarkDotNet.Exporters; 8 | using BenchmarkDotNet.Jobs; 9 | 10 | namespace App.Metrics.Health.Benchmarks.Configs 11 | { 12 | public class DefaultConfig : ManualConfig 13 | { 14 | public DefaultConfig() 15 | { 16 | Add(Job.Core); 17 | Add(MarkdownExporter.GitHub); 18 | Add(new MemoryDiagnoser()); 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /src/App.Metrics.Health.Formatters.Ascii/App.Metrics.Health.Formatters.Ascii.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | App Metrics Formatters for health check results to plain text. 5 | netstandard1.6 6 | $(TargetFrameworks);net452 7 | true 8 | appmetrics;healthchecks;ascii 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /benchmarks/App.Metrics.Benchmarks.Runner/BenchmarkDotNet.Artifacts/results/MeasureReadHealthStatusBenchmark-report-github.md: -------------------------------------------------------------------------------- 1 | ``` ini 2 | 3 | BenchmarkDotNet=v0.10.8, OS=Windows 10 Redstone 2 (10.0.15063) 4 | Processor=Intel Core i7-2600 CPU 3.40GHz (Sandy Bridge), ProcessorCount=8 5 | Frequency=3312790 Hz, Resolution=301.8604 ns, Timer=TSC 6 | dotnet cli version=2.0.0 7 | [Host] : .NET Core 4.6.00001.0, 64bit RyuJIT 8 | Core : .NET Core 4.6.00001.0, 64bit RyuJIT 9 | 10 | Job=Core Runtime=Core 11 | 12 | ``` 13 | | Method | Mean | Error | StdDev | Gen 0 | Allocated | 14 | |----------- |---------:|----------:|----------:|-------:|----------:| 15 | | ReadHealth | 1.441 us | 0.0069 us | 0.0061 us | 0.1984 | 840 B | 16 | -------------------------------------------------------------------------------- /src/App.Metrics.Health.Abstractions/Internal/AppMetricsHealthTaskHelper.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) App Metrics Contributors. All rights reserved. 3 | // 4 | 5 | #if !NETSTANDARD1_6 6 | 7 | using System.Threading.Tasks; 8 | 9 | namespace App.Metrics.Health.Internal 10 | { 11 | public static class AppMetricsHealthTaskHelper 12 | { 13 | private static Task _completedTask; 14 | 15 | private struct VoidTaskResult { } 16 | 17 | public static Task CompletedTask() 18 | { 19 | return _completedTask ?? (_completedTask = Task.FromResult(default(VoidTaskResult))); 20 | } 21 | } 22 | } 23 | #endif -------------------------------------------------------------------------------- /src/App.Metrics.Health.Reporting.Slack/Internal/SlackAttachmentFields.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) App Metrics Contributors. All rights reserved. 3 | // 4 | 5 | namespace App.Metrics.Health.Reporting.Slack.Internal 6 | { 7 | internal class SlackAttachmentFields 8 | { 9 | public string Title { get; set; } 10 | 11 | public string Value { get; set; } 12 | 13 | public bool Short { get; set; } 14 | 15 | public string ImageUrl { get; set; } 16 | 17 | public string ThumbUrl { get; set; } 18 | 19 | public string Footer { get; set; } 20 | 21 | public string FooterIcon { get; set; } 22 | } 23 | } -------------------------------------------------------------------------------- /test/App.Metrics.Health.Facts/TestHelpers/SampleHealthCheck.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) App Metrics Contributors. All rights reserved. 3 | // 4 | 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | 8 | namespace App.Metrics.Health.Facts.TestHelpers 9 | { 10 | public class SampleHealthCheck : HealthCheck 11 | { 12 | public SampleHealthCheck() 13 | : base("SampleHealthCheck") { } 14 | 15 | protected override ValueTask CheckAsync(CancellationToken token = default) 16 | { 17 | return new ValueTask(HealthCheckResult.Healthy("OK")); 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /stylecop.json: -------------------------------------------------------------------------------- 1 | { 2 | // ACTION REQUIRED: This file was automatically added to your project, but it 3 | // will not take effect until additional steps are taken to enable it. See the 4 | // following page for additional information: 5 | // 6 | // https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/EnableConfiguration.md 7 | "$schema": 8 | "https://raw.githubusercontent.com/DotNetAnalyzers/StyleCopAnalyzers/master/StyleCop.Analyzers/StyleCop.Analyzers/Settings/stylecop.schema.json", 9 | "settings": { 10 | "documentationRules": { 11 | "companyName": "App Metrics Contributors", 12 | "copyrightText": "Copyright (c) {companyName}. All rights reserved.", 13 | "xmlHeader": true 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /benchmarks/App.Metrics.Health.Benchmarks/DefaultBenchmarkBase.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) App Metrics Contributors. All rights reserved. 3 | // 4 | 5 | using App.Metrics.Health.Benchmarks.Configs; 6 | using App.Metrics.Health.Benchmarks.Fixtures; 7 | using BenchmarkDotNet.Attributes; 8 | 9 | namespace App.Metrics.Health.Benchmarks 10 | { 11 | [Config(typeof(DefaultConfig))] 12 | public abstract class DefaultBenchmarkBase 13 | { 14 | protected HealthTestFixture FixtureWithHealth { get; private set; } 15 | 16 | [GlobalSetup] 17 | public virtual void Setup() 18 | { 19 | FixtureWithHealth = new HealthTestFixture(); 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /test/App.Metrics.Health.Formatters.Json.Facts/App.Metrics.Health.Formatters.Json.Facts.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $(StandardTest) 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /benchmarks/App.Metrics.Health.Benchmarks/BenchmarkDotNetBenchmarks/MeasureReadHealthStatusBenchmark.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) App Metrics Contributors. All rights reserved. 3 | // 4 | 5 | using System.Diagnostics; 6 | using BenchmarkDotNet.Attributes; 7 | 8 | namespace App.Metrics.Health.Benchmarks.BenchmarkDotNetBenchmarks 9 | { 10 | public class MeasureReadHealthStatusBenchmark : DefaultBenchmarkBase 11 | { 12 | [Benchmark] 13 | public void ReadHealth() 14 | { 15 | var result = FixtureWithHealth.HealthCheckRunner.ReadAsync().GetAwaiter().GetResult(); 16 | Debug.WriteLine("Health status: " + result.Status); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/App.Metrics.Health.Reporting.Slack/Internal/SlackPayload.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) App Metrics Contributors. All rights reserved. 3 | // 4 | 5 | using System.Collections.Generic; 6 | 7 | namespace App.Metrics.Health.Reporting.Slack.Internal 8 | { 9 | internal class SlackPayload 10 | { 11 | public SlackPayload() 12 | { 13 | Attachments = new List(); 14 | } 15 | 16 | public string Text { get; set; } 17 | 18 | public string Channel { get; set; } 19 | 20 | public string UserName { get; set; } 21 | 22 | public string IconEmoji { get; set; } 23 | 24 | public List Attachments { get; set; } 25 | } 26 | } -------------------------------------------------------------------------------- /src/App.Metrics.Health.Abstractions/IReportHealthStatus.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) App Metrics Contributors. All rights reserved. 3 | // 4 | 5 | using System; 6 | using System.Threading; 7 | using System.Threading.Tasks; 8 | 9 | namespace App.Metrics.Health 10 | { 11 | public interface IReportHealthStatus 12 | { 13 | /// 14 | /// Gets interval to flush metrics values. Defaults to 15 | /// . 16 | /// 17 | TimeSpan ReportInterval { get; set; } 18 | 19 | Task ReportAsync(HealthOptions options, HealthStatus status, CancellationToken cancellationToken = default); 20 | } 21 | } -------------------------------------------------------------------------------- /src/App.Metrics.Health.Core/Internal/Extensions/HealthOptionsExtensions.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) App Metrics Contributors. All rights reserved. 3 | // 4 | 5 | using System.Collections.Generic; 6 | 7 | namespace App.Metrics.Health.Internal.Extensions 8 | { 9 | public static class HealthOptionsExtensions 10 | { 11 | public static IEnumerable> ToKeyValue(this HealthOptions options) 12 | { 13 | var result = new Dictionary 14 | { 15 | [KeyValuePairHealthOptions.EnabledDirective] = options.Enabled.ToString() 16 | }; 17 | 18 | return result; 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /test/App.Metrics.Health.Facts/TestHelpers/IgnoreAttributeHealthCheck.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) App Metrics Contributors. All rights reserved. 3 | // 4 | 5 | using System; 6 | using System.Threading; 7 | using System.Threading.Tasks; 8 | 9 | namespace App.Metrics.Health.Facts.TestHelpers 10 | { 11 | [Obsolete] 12 | public class IgnoreAttributeHealthCheck : HealthCheck 13 | { 14 | public IgnoreAttributeHealthCheck() 15 | : base("Referencing Assembly - Sample Healthy") { } 16 | 17 | protected override ValueTask CheckAsync(CancellationToken token = default) 18 | { 19 | return new ValueTask(HealthCheckResult.Healthy("OK")); 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /benchmarks/App.Metrics.Health.Benchmarks/XunitHarness/Health.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) App Metrics Contributors. All rights reserved. 3 | // 4 | 5 | using App.Metrics.Health.Benchmarks.BenchmarkDotNetBenchmarks; 6 | using App.Metrics.Health.Benchmarks.Support; 7 | using Xunit; 8 | using Xunit.Abstractions; 9 | 10 | namespace App.Metrics.Health.Benchmarks.XunitHarness 11 | { 12 | [Trait("Benchmark-Health", "ReadStatus")] 13 | public class Health 14 | { 15 | private readonly ITestOutputHelper _output; 16 | 17 | public Health(ITestOutputHelper output) { _output = output; } 18 | 19 | [Fact] 20 | public void CostOfReadingHealth() { BenchmarkTestRunner.CanCompileAndRun(_output); } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /test/App.Metrics.Health.Facts/TestHelpers/TestIgnoreAttributeHealthCheck.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) App Metrics Contributors. All rights reserved. 3 | // 4 | 5 | using System; 6 | using System.Threading; 7 | using System.Threading.Tasks; 8 | 9 | namespace App.Metrics.Health.Facts.TestHelpers 10 | { 11 | [Obsolete] 12 | public class TestIgnoreAttributeHealthCheck : HealthCheck 13 | { 14 | public TestIgnoreAttributeHealthCheck() 15 | : base("Referencing Assembly - Sample Healthy") { } 16 | 17 | protected override ValueTask CheckAsync(CancellationToken token = default) 18 | { 19 | return new ValueTask(HealthCheckResult.Healthy("OK")); 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /src/App.Metrics.Health.Abstractions/HealthCheckStatusExtensions.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) App Metrics Contributors. All rights reserved. 3 | // 4 | 5 | namespace App.Metrics.Health 6 | { 7 | public static class HealthCheckStatusExtensions 8 | { 9 | public static bool IsDegraded(this HealthCheckStatus status) { return status == HealthCheckStatus.Degraded; } 10 | 11 | public static bool IsHealthy(this HealthCheckStatus status) { return status == HealthCheckStatus.Healthy; } 12 | 13 | public static bool IsIgnored(this HealthCheckStatus status) { return status == HealthCheckStatus.Ignored; } 14 | 15 | public static bool IsUnhealthy(this HealthCheckStatus status) { return status == HealthCheckStatus.Unhealthy; } 16 | } 17 | } -------------------------------------------------------------------------------- /src/App.Metrics.Health.Formatters.Json/HealthStatusData.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) App Metrics Contributors. All rights reserved. 3 | // 4 | 5 | using System.Collections.Generic; 6 | 7 | namespace App.Metrics.Health.Formatters.Json 8 | { 9 | public sealed class HealthStatusData 10 | { 11 | public Dictionary Healthy { get; set; } = new Dictionary(); 12 | 13 | public Dictionary Degraded { get; set; } = new Dictionary(); 14 | 15 | public Dictionary Unhealthy { get; set; } = new Dictionary(); 16 | 17 | public Dictionary Ignored { get; set; } = new Dictionary(); 18 | 19 | public string Status { get; set; } 20 | } 21 | } -------------------------------------------------------------------------------- /src/App.Metrics.Health.Formatters.Json/App.Metrics.Health.Formatters.Json.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | App Metrics Formatters for health check results to JSON using Newtonsoft.Json. 5 | netstandard1.6 6 | $(TargetFrameworks);net452 7 | true 8 | appmetrics;healthcheck;json 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /sandbox/HealthSandbox/SampleHealthStatusReporter.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) App Metrics Contributors. All rights reserved. 3 | // 4 | 5 | using System; 6 | using System.Threading; 7 | using System.Threading.Tasks; 8 | using App.Metrics.Health; 9 | 10 | namespace HealthSandbox 11 | { 12 | public class SampleHealthStatusReporter : IReportHealthStatus 13 | { 14 | /// 15 | public TimeSpan ReportInterval { get; set; } 16 | 17 | /// 18 | public Task ReportAsync(HealthOptions options, HealthStatus status, CancellationToken cancellationToken = default) 19 | { 20 | Console.WriteLine($"{options.ApplicationName} - Overall status: {status.Status}"); 21 | 22 | return Task.CompletedTask; 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /benchmarks/App.Metrics.Health.Benchmarks/Fixtures/HealthTestFixture.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) App Metrics Contributors. All rights reserved. 3 | // 4 | 5 | using System; 6 | using System.Threading.Tasks; 7 | using App.Metrics.Health.Builder; 8 | 9 | namespace App.Metrics.Health.Benchmarks.Fixtures 10 | { 11 | public class HealthTestFixture : IDisposable 12 | { 13 | public HealthTestFixture() 14 | { 15 | var health = new HealthBuilder() 16 | .HealthChecks.AddCheck("test", () => new ValueTask(HealthCheckResult.Healthy())) 17 | .Build(); 18 | 19 | HealthCheckRunner = health.HealthCheckRunner; 20 | } 21 | 22 | public IRunHealthChecks HealthCheckRunner { get; } 23 | 24 | public void Dispose() { } 25 | } 26 | } -------------------------------------------------------------------------------- /benchmarks/App.Metrics.Benchmarks.Runner/App.Metrics.Benchmarks.Runner.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | exe 7 | netcoreapp2.1 8 | $(TargetFrameworks);net461 9 | latest 10 | true 11 | true 12 | false 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | Thanks for helping out :+1: 2 | 3 | Before submitting a pull request, please have a quick read through the [contribution guidlines](https://github.com/alhardy/AppMetrics/blob/master/CONTRIBUTING.md) and provide the following information, where appropriate replace the `[ ]` with a `[X]` 4 | 5 | ### The issue or feature being addressed 6 | 7 | - link to the issue/feature using it's github issue number #number (if an issue/feature does not exist, please create it first) 8 | 9 | ### Details on the issue fix or feature implementation 10 | 11 | - provide some details here 12 | 13 | 14 | ### Confirm the following 15 | 16 | - [ ] I have ensured that I have merged the latest changes from the dev branch 17 | - [ ] I have successfully run a [local build](https://github.com/alhardy/AppMetrics#how-to-build) 18 | - [ ] I have included unit tests for the issue/feature 19 | - [ ] I have included the github issue number in my commits -------------------------------------------------------------------------------- /src/App.Metrics.Health.Abstractions/HealthCheckStatus.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) App Metrics Contributors. All rights reserved. 3 | // 4 | 5 | namespace App.Metrics.Health 6 | { 7 | /// 8 | /// Possible status values of a health check result 9 | /// 10 | public enum HealthCheckStatus 11 | { 12 | /// 13 | /// The check is healthy 14 | /// 15 | Healthy, 16 | 17 | /// 18 | /// The check is degraded, failing but not critical 19 | /// 20 | Degraded, 21 | 22 | /// 23 | /// The check is unhealthy 24 | /// 25 | Unhealthy, 26 | 27 | /// 28 | /// The check was ignored 29 | /// 30 | Ignored 31 | } 32 | } -------------------------------------------------------------------------------- /src/App.Metrics.Health.Reporting.Metrics/HealthAsMetricsOptions.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) App Metrics Contributors. All rights reserved. 3 | // 4 | 5 | using System; 6 | 7 | namespace App.Metrics.Health.Reporting.Metrics 8 | { 9 | public class HealthAsMetricsOptions 10 | { 11 | /// 12 | /// Gets or sets the health status reporting interval. 13 | /// 14 | /// 15 | /// If not set reporting interval will be set to the . 16 | /// 17 | /// 18 | /// The to wait between reporting health status. 19 | /// 20 | public TimeSpan ReportInterval { get; set; } 21 | 22 | public bool Enabled { get; set; } = true; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/App.Metrics.Health.Checks.Http/App.Metrics.Health.Checks.Http.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | App Metrics HTTP health checks. 5 | netstandard1.6 6 | $(TargetFrameworks);net452 7 | true 8 | appmetrics;healthchecks;http 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /test/Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | netcoreapp2.1 6 | $(StandardTest);net461 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/App.Metrics.Health/App.Metrics.Health.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | App Metrics Health is an open-source .NET Standard library used to define and report results on user defined health checks. 5 | netstandard1.6 6 | $(TargetFrameworks);net452 7 | true 8 | appmetrics;healthchecks 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /test/App.Metrics.Health.Facts/TestHelpers/DatabaseHealthCheck.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) App Metrics Contributors. All rights reserved. 3 | // 4 | 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | 8 | namespace App.Metrics.Health.Facts.TestHelpers 9 | { 10 | public class DatabaseHealthCheck : HealthCheck 11 | { 12 | private readonly IDatabase _database; 13 | 14 | public DatabaseHealthCheck(IDatabase database) 15 | : base("DatabaseCheck") 16 | { 17 | _database = database; 18 | } 19 | 20 | protected override ValueTask CheckAsync(CancellationToken token = default) 21 | { 22 | // exceptions will be caught and the result will be unhealthy 23 | _database.Ping(); 24 | 25 | return new ValueTask(HealthCheckResult.Healthy()); 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | notifications: 2 | email: 3 | recipients: 4 | - al_hardy@live.com.au 5 | 6 | language: csharp 7 | dotnet: 2.0.0-preview1-005977 8 | os: 9 | # - osx 10 | - linux 11 | before_script: 12 | - chmod a+x ./build.sh 13 | script: 14 | - ./build.sh 15 | 16 | # .NET CLI require Ubuntu 14.04 17 | sudo: required 18 | dist: trusty 19 | env: 20 | global: 21 | - DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true 22 | - DOTNET_CLI_TELEMETRY_OPTOUT: 1 23 | addons: 24 | apt: 25 | packages: 26 | - gettext 27 | - libcurl4-openssl-dev 28 | - libicu-dev 29 | - libssl-dev 30 | - libunwind8 31 | 32 | # .NET CLI require OSX 10.10 33 | # osx_image: xcode7.1 34 | # before_install: 35 | # - if test "$TRAVIS_OS_NAME" == "osx"; then brew update; brew install openssl; ln -s /usr/local/opt/openssl/lib/libcrypto.1.0.0.dylib /usr/local/lib/; ln -s /usr/local/opt/openssl/lib/libssl.1.0.0.dylib /usr/local/lib/; fi 36 | 37 | mono: 38 | - 4.6.0 39 | 40 | cache: 41 | directories: 42 | - src/packages 43 | - tools -------------------------------------------------------------------------------- /benchmarks/App.Metrics.Health.Benchmarks/Facts/HealthCheck.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) App Metrics Contributors. All rights reserved. 3 | // 4 | 5 | using System.Diagnostics; 6 | using App.Metrics.Health.Benchmarks.Fixtures; 7 | using App.Metrics.Health.Benchmarks.Support; 8 | using Xunit; 9 | 10 | namespace App.Metrics.Health.Benchmarks.Facts 11 | { 12 | public class HealthCheck : IClassFixture 13 | { 14 | private readonly HealthTestFixture _fixture; 15 | 16 | public HealthCheck(HealthTestFixture fixture) { _fixture = fixture; } 17 | 18 | [Fact] 19 | public void ReadHealthStatus() 20 | { 21 | SimpleBenchmarkRunner.Run( 22 | async () => 23 | { 24 | var result = await _fixture.HealthCheckRunner.ReadAsync(); 25 | Debug.WriteLine("Health status: " + result.Status); 26 | }); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/App.Metrics.Health.Abstractions/Serialization/HealthStatusSerializer.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) App Metrics Contributors. All rights reserved. 3 | // 4 | 5 | namespace App.Metrics.Health.Serialization 6 | { 7 | /// 8 | /// Serializes into the different formats. 9 | /// 10 | public class HealthStatusSerializer 11 | { 12 | /// 13 | /// Serializes the specified and writes the health status using the specified 14 | /// . 15 | /// 16 | /// The used to write the health status. 17 | /// The to serilize. 18 | public void Serialize(IHealthStatusWriter writer, HealthStatus healthStatus) { writer.Write(healthStatus); } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /benchmarks/App.Metrics.Benchmarks.Runner/Program.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) App Metrics Contributors. All rights reserved. 3 | // 4 | 5 | using System; 6 | using System.Reflection; 7 | using App.Metrics.Health.Benchmarks.Support; 8 | using BenchmarkDotNet.Running; 9 | 10 | namespace App.Metrics.Benchmarks.Runner 11 | { 12 | // ReSharper disable UnusedMember.Global 13 | public class Program 14 | { 15 | public static void Main(string[] args) 16 | { 17 | ConsoleKeyInfo keyInfo; 18 | 19 | do 20 | { 21 | BenchmarkSwitcher.FromAssembly(typeof(BenchmarksAssemblyMarker).GetTypeInfo().Assembly).Run(args); 22 | 23 | Console.WriteLine("Press ESC to quit, otherwise any key to continue..."); 24 | 25 | keyInfo = Console.ReadKey(true); 26 | } 27 | while (keyInfo.Key != ConsoleKey.Escape); 28 | } 29 | } 30 | 31 | // ReSharper restore UnusedMember.Global 32 | } -------------------------------------------------------------------------------- /src/App.Metrics.Health.Abstractions/IRunHealthChecks.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) App Metrics Contributors. All rights reserved. 3 | // 4 | 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | 8 | namespace App.Metrics.Health 9 | { 10 | /// 11 | /// Provides access to the current health status of the application by executing registered s 12 | /// 13 | public interface IRunHealthChecks 14 | { 15 | /// 16 | /// Executes all registered health checks within the application 17 | /// 18 | /// The cancellation token. 19 | /// 20 | /// The current health status of the application. A single health check failure will result in an un-healthy 21 | /// result 22 | /// 23 | ValueTask ReadAsync(CancellationToken cancellationToken = default); 24 | } 25 | } -------------------------------------------------------------------------------- /benchmarks/App.Metrics.Health.Benchmarks/App.Metrics.Health.Benchmarks.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | netcoreapp2.1 7 | $(TargetFrameworks);net461 8 | latest 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/App.Metrics.Health.Checks.Sql/App.Metrics.Health.Checks.Sql.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | App Metrics SQL health checks. 5 | netstandard1.6 6 | $(TargetFrameworks);net452 7 | true 8 | appmetrics;healthchecks;SQL 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/App.Metrics.Health.Core/App.Metrics.Health.Core.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | App Metrics health core components. 5 | netstandard1.6 6 | $(TargetFrameworks);net452 7 | App.Metrics.Health 8 | true 9 | appmetrics;healthchecks 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/App.Metrics.Health.Checks.Network/App.Metrics.Health.Checks.Network.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | App Metrics network health checks. 5 | netstandard1.6 6 | $(TargetFrameworks);net452 7 | true 8 | appmetrics;healthchecks;http 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/App.Metrics.Health.Checks.Process/App.Metrics.Health.Checks.Process.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | App Metrics process health checks. 5 | netstandard1.6 6 | $(TargetFrameworks);net452 7 | true 8 | appmetrics;healthchecks;http 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/App.Metrics.Health.Reporting.Slack/Internal/SlackAttachment.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) App Metrics Contributors. All rights reserved. 3 | // 4 | 5 | using System.Collections.Generic; 6 | 7 | namespace App.Metrics.Health.Reporting.Slack.Internal 8 | { 9 | internal class SlackAttachment 10 | { 11 | public SlackAttachment() 12 | { 13 | Fields = new List(); 14 | } 15 | 16 | public string Fallback { get; set; } 17 | 18 | public string Color { get; set; } 19 | 20 | public string PreText { get; set; } 21 | 22 | public string AuthorName { get; set; } 23 | 24 | public string AuthorLink { get; set; } 25 | 26 | public string AuthorIcon { get; set; } 27 | 28 | public string Title { get; set; } 29 | 30 | public string TitleLink { get; set; } 31 | 32 | public string Text { get; set; } 33 | 34 | public double Ts { get; set; } 35 | 36 | public List Fields { get; set; } 37 | } 38 | } -------------------------------------------------------------------------------- /src/App.Metrics.Health.Reporting.Slack/App.Metrics.Health.Reporting.Slack.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Reports App Metrics Health Status Alerts to Slack. 5 | netstandard1.6 6 | $(TargetFrameworks);net452 7 | true 8 | appmetrics;healthchecks;slack;alerts;reporting 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/App.Metrics.Health.Reporting.Metrics/App.Metrics.Health.Reporting.Metrics.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Report App Metrics Health Checks as metrics allowing results to be flush for example to the configured metrics reporter. 5 | netstandard1.6 6 | $(TargetFrameworks);net452 7 | true 8 | appmetrics;healthchecks;metrics;reporting 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/App.Metrics.Health.Core/Internal/NoOp/NoOpHealthCheckRunner.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) App Metrics Contributors. All rights reserved. 3 | // 4 | 5 | using System.Diagnostics.CodeAnalysis; 6 | using System.IO; 7 | using System.Linq; 8 | using System.Threading; 9 | using System.Threading.Tasks; 10 | #if !NETSTANDARD1_6 11 | 12 | #endif 13 | 14 | namespace App.Metrics.Health.Internal.NoOp 15 | { 16 | [ExcludeFromCodeCoverage] 17 | public sealed class NoOpHealthCheckRunner : IRunHealthChecks 18 | { 19 | /// 20 | public ValueTask ReadAsync(CancellationToken cancellationToken = default) 21 | { 22 | return new ValueTask(new HealthStatus(Enumerable.Empty())); 23 | } 24 | 25 | public Task FormatAsync(Stream output, HealthStatus status, CancellationToken cancellationToken = default) 26 | { 27 | #if NETSTANDARD1_6 28 | return Task.CompletedTask; 29 | #else 30 | return AppMetricsHealthTaskHelper.CompletedTask(); 31 | #endif 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /sandbox/HealthSandbox/HealthChecks/SampleHealthCheck.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) App Metrics Contributors. All rights reserved. 3 | // 4 | 5 | using System; 6 | using System.Threading; 7 | using System.Threading.Tasks; 8 | using App.Metrics.Health; 9 | 10 | namespace HealthSandbox.HealthChecks 11 | { 12 | public class SampleHealthCheck : HealthCheck 13 | { 14 | public SampleHealthCheck() 15 | : base("Random Health Check") 16 | { 17 | } 18 | 19 | /// 20 | protected override ValueTask CheckAsync(CancellationToken cancellationToken = default) 21 | { 22 | if (DateTime.UtcNow.Second <= 20) 23 | { 24 | return new ValueTask(HealthCheckResult.Degraded()); 25 | } 26 | 27 | if (DateTime.UtcNow.Second >= 40) 28 | { 29 | return new ValueTask(HealthCheckResult.Unhealthy()); 30 | } 31 | 32 | return new ValueTask(HealthCheckResult.Healthy()); 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /src/App.Metrics.Health.Abstractions/Builder/IHealthCheckBuilder.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) App Metrics Contributors. All rights reserved. 3 | // 4 | 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Threading; 8 | using System.Threading.Tasks; 9 | 10 | // ReSharper disable CheckNamespace 11 | namespace App.Metrics.Health 12 | // ReSharper restore CheckNamespace 13 | { 14 | public interface IHealthCheckBuilder 15 | { 16 | /// 17 | /// Gets the where App Metrics Health is configured. 18 | /// 19 | IHealthBuilder Builder { get; } 20 | 21 | IHealthBuilder AddCheck(string name, Func> check); 22 | 23 | IHealthBuilder AddCheck(string name, Func> check); 24 | 25 | IHealthBuilder AddCheck(THealthCheck check) 26 | where THealthCheck : HealthCheck; 27 | 28 | IHealthBuilder AddCheck() 29 | where THealthCheck : HealthCheck, new(); 30 | 31 | IHealthBuilder AddChecks(IEnumerable checks); 32 | } 33 | } -------------------------------------------------------------------------------- /src/App.Metrics.Health.Formatters.Ascii/HealthTextOptions.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) App Metrics Contributors. All rights reserved. 3 | // 4 | 5 | using System.Text; 6 | 7 | namespace App.Metrics.Health.Formatters.Ascii 8 | { 9 | public class HealthTextOptions 10 | { 11 | public HealthTextOptions() 12 | { 13 | Padding = 20; 14 | Separator = "="; 15 | Encoding = Encoding.UTF8; 16 | } 17 | 18 | /// 19 | /// Gets or sets the padding to apply on health check labels and messages 20 | /// 21 | /// 22 | /// The padding to apply on health check labels and messages 23 | /// 24 | public int Padding { get; set; } 25 | 26 | /// 27 | /// Gets or sets the separator to use between on health check labels and messages/status 28 | /// 29 | /// 30 | /// The separator to apply between on health check labels and messages/status 31 | /// 32 | public string Separator { get; set; } 33 | 34 | public Encoding Encoding { get; set; } 35 | } 36 | } -------------------------------------------------------------------------------- /src/App.Metrics.Health.Reporting.Metrics/Builder/HealthResultsAsMetricsBuilderExtensions.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) App Metrics Contributors. All rights reserved. 3 | // 4 | 5 | using App.Metrics.Health.Reporting.Metrics; 6 | 7 | // ReSharper disable CheckNamespace 8 | namespace App.Metrics.Health 9 | // ReSharper restore CheckNamespace 10 | { 11 | public static class HealthResultsAsMetricsBuilderExtensions 12 | { 13 | public static IHealthBuilder ToMetrics( 14 | this IHealthReportingBuilder healthReportingBuilder, 15 | IMetrics metrics) 16 | { 17 | healthReportingBuilder.Using(new HealthResultsAsMetricsReporter(metrics)); 18 | 19 | return healthReportingBuilder.Builder; 20 | } 21 | 22 | public static IHealthBuilder ToMetrics( 23 | this IHealthReportingBuilder healthReportingBuilder, 24 | IMetrics metrics, 25 | HealthAsMetricsOptions options) 26 | { 27 | healthReportingBuilder.Using(new HealthResultsAsMetricsReporter(metrics, options)); 28 | 29 | return healthReportingBuilder.Builder; 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /sandbox/HealthSandbox/HealthChecks/SampleCachedHealthCheck.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) App Metrics Contributors. All rights reserved. 3 | // 4 | 5 | using System; 6 | using System.Threading; 7 | using System.Threading.Tasks; 8 | using App.Metrics.Health; 9 | 10 | namespace HealthSandbox.HealthChecks 11 | { 12 | public class SampleCachedHealthCheck : HealthCheck 13 | { 14 | public SampleCachedHealthCheck() 15 | : base("Random Health Check - 1 Min Cache", cacheDuration: TimeSpan.FromMinutes(1)) 16 | { 17 | } 18 | 19 | /// 20 | protected override ValueTask CheckAsync(CancellationToken cancellationToken = default) 21 | { 22 | if (DateTime.UtcNow.Second <= 20) 23 | { 24 | return new ValueTask(HealthCheckResult.Degraded()); 25 | } 26 | 27 | if (DateTime.UtcNow.Second >= 40) 28 | { 29 | return new ValueTask(HealthCheckResult.Unhealthy()); 30 | } 31 | 32 | return new ValueTask(HealthCheckResult.Healthy()); 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /src/App.Metrics.Health.Abstractions/Builder/Extensions/CacheHealthCheckBuilderExtensions.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) App Metrics Contributors. All rights reserved. 3 | // 4 | 5 | using System; 6 | using System.Threading; 7 | using System.Threading.Tasks; 8 | 9 | // ReSharper disable CheckNamespace 10 | namespace App.Metrics.Health 11 | // ReSharper restore CheckNamespace 12 | { 13 | public static class CacheHealthCheckBuilderExtensions 14 | { 15 | public static IHealthBuilder AddCachedCheck( 16 | this IHealthCheckBuilder builder, 17 | string name, 18 | Func> check, 19 | TimeSpan cacheDuration) 20 | { 21 | builder.AddCheck(new HealthCheck(name, check, cacheDuration)); 22 | 23 | return builder.Builder; 24 | } 25 | 26 | public static IHealthBuilder AddCachedCheck( 27 | this IHealthCheckBuilder builder, 28 | string name, 29 | Func> check, 30 | TimeSpan cacheDuration) 31 | { 32 | builder.AddCheck(new HealthCheck(name, check, cacheDuration)); 33 | 34 | return builder.Builder; 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/App.Metrics.Health.Reporting.Slack/Builder/SlackHealthAlerterBuilderExtensions.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) App Metrics Contributors. All rights reserved. 3 | // 4 | 5 | using System; 6 | using App.Metrics.Health.Reporting.Slack; 7 | 8 | // ReSharper disable CheckNamespace 9 | namespace App.Metrics.Health 10 | // ReSharper restore CheckNamespace 11 | { 12 | public static class SlackHealthAlerterBuilderExtensions 13 | { 14 | public static IHealthBuilder ToSlack( 15 | this IHealthReportingBuilder healthReportingBuilder, 16 | Action optionsSetup) 17 | { 18 | var options = new SlackHealthAlertOptions(); 19 | 20 | optionsSetup(options); 21 | 22 | healthReportingBuilder.Using(new SlackIncomingWebHookHealthAlerter(options)); 23 | 24 | return healthReportingBuilder.Builder; 25 | } 26 | 27 | public static IHealthBuilder ToSlack( 28 | this IHealthReportingBuilder healthReportingBuilder, 29 | SlackHealthAlertOptions options) 30 | { 31 | healthReportingBuilder.Using(new SlackIncomingWebHookHealthAlerter(options)); 32 | 33 | return healthReportingBuilder.Builder; 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /src/App.Metrics.Health.Abstractions/Builder/Extensions/QuiteTimeHealthCheckBuilderExtensions.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) App Metrics Contributors. All rights reserved. 3 | // 4 | 5 | using System; 6 | using System.Threading; 7 | using System.Threading.Tasks; 8 | 9 | // ReSharper disable CheckNamespace 10 | namespace App.Metrics.Health 11 | // ReSharper restore CheckNamespace 12 | { 13 | public static class QuiteTimeHealthCheckBuilderExtensions 14 | { 15 | public static IHealthBuilder AddQuiteTimeCheck( 16 | this IHealthCheckBuilder builder, 17 | string name, 18 | Func> check, 19 | HealthCheck.QuiteTime quiteTime) 20 | { 21 | builder.AddCheck(new HealthCheck(name, check, quiteTime)); 22 | 23 | return builder.Builder; 24 | } 25 | 26 | public static IHealthBuilder AddQuiteTimeCheck( 27 | this IHealthCheckBuilder builder, 28 | string name, 29 | Func> check, 30 | HealthCheck.QuiteTime quiteTime) 31 | { 32 | builder.AddCheck(new HealthCheck(name, check, quiteTime)); 33 | 34 | return builder.Builder; 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /sandbox/HealthSandbox/HealthChecks/SampleQuiteTimeHealthCheck.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) App Metrics Contributors. All rights reserved. 3 | // 4 | 5 | using System; 6 | using System.Threading; 7 | using System.Threading.Tasks; 8 | using App.Metrics.Health; 9 | 10 | namespace HealthSandbox.HealthChecks 11 | { 12 | public class SampleQuiteTimeHealthCheck : HealthCheck 13 | { 14 | private static readonly QuiteTime QuiteAt = new QuiteTime(new TimeSpan(11, 0, 0), new TimeSpan(13, 0, 0), new[] { DayOfWeek.Monday }); 15 | 16 | public SampleQuiteTimeHealthCheck() 17 | : base("Random Health Check - Quite Time", quiteTime: QuiteAt) 18 | { 19 | } 20 | 21 | /// 22 | protected override ValueTask CheckAsync(CancellationToken cancellationToken = default) 23 | { 24 | if (DateTime.UtcNow.Second <= 20) 25 | { 26 | return new ValueTask(HealthCheckResult.Degraded()); 27 | } 28 | 29 | if (DateTime.UtcNow.Second >= 40) 30 | { 31 | return new ValueTask(HealthCheckResult.Unhealthy()); 32 | } 33 | 34 | return new ValueTask(HealthCheckResult.Healthy()); 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /benchmarks/App.Metrics.Health.Benchmarks/Support/TestConfigs.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) App Metrics Contributors. All rights reserved. 3 | // 4 | 5 | using BenchmarkDotNet.Configs; 6 | using BenchmarkDotNet.Engines; 7 | using BenchmarkDotNet.Jobs; 8 | 9 | // ReSharper disable UnusedMember.Global 10 | namespace App.Metrics.Health.Benchmarks.Support 11 | { 12 | #pragma warning disable SA1649, SA1402 13 | public class SingleRunFastConfig : ManualConfig 14 | { 15 | public SingleRunFastConfig() { Add(Job.Dry); } 16 | } 17 | 18 | public class SingleRunMediumConfig : ManualConfig 19 | { 20 | public SingleRunMediumConfig() { Add(new Job(Job.Dry) { Run = { IterationCount = 5 } }); } 21 | } 22 | 23 | public class ThroughputFastConfig : ManualConfig 24 | { 25 | public ThroughputFastConfig() { Add(new Job(Job.Dry) { Run = { RunStrategy = RunStrategy.Throughput, IterationCount = 1 } }); } 26 | } 27 | 28 | public class DiagnoserConfig : ManualConfig 29 | { 30 | public DiagnoserConfig() 31 | { 32 | // Diagnosers need enough runs to collects the statistics! 33 | Add(new Job { Run = { LaunchCount = 1, WarmupCount = 1, IterationCount = 50 } }); 34 | } 35 | } 36 | #pragma warning restore SA1649, SA1402 37 | // ReSharper restore UnusedMember.Global 38 | } -------------------------------------------------------------------------------- /benchmarks/App.Metrics.Health.Benchmarks/Support/BenchmarkTestRunner.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) App Metrics Contributors. All rights reserved. 3 | // 4 | 5 | using System.Linq; 6 | using BenchmarkDotNet.Configs; 7 | using BenchmarkDotNet.Jobs; 8 | using BenchmarkDotNet.Loggers; 9 | using BenchmarkDotNet.Running; 10 | using Xunit; 11 | using Xunit.Abstractions; 12 | 13 | namespace App.Metrics.Health.Benchmarks.Support 14 | { 15 | internal static class BenchmarkTestRunner 16 | { 17 | internal static void CanCompileAndRun(ITestOutputHelper output) 18 | { 19 | var summary = BenchmarkRunner.Run(new SingleRunFastConfig(output)); 20 | 21 | Assert.True(summary.Reports.Any()); 22 | Assert.True(summary.Reports.All(report => report.ExecuteResults.All(executeResult => executeResult.FoundExecutable))); 23 | Assert.True(summary.Reports.All(report => report.AllMeasurements.Any()), "There are no available measurements"); 24 | } 25 | 26 | private class SingleRunFastConfig : ManualConfig 27 | { 28 | internal SingleRunFastConfig(ITestOutputHelper output) 29 | { 30 | Add(Job.Dry); 31 | Add(ConsoleLogger.Default); 32 | Add(new OutputLogger(output)); 33 | } 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /src/App.Metrics.Health.Reporting.Metrics/Internal/ApplicationHealthMetricRegistry.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) App Metrics Contributors. All rights reserved. 3 | // 4 | 5 | using App.Metrics.Gauge; 6 | 7 | namespace App.Metrics.Health.Reporting.Metrics.Internal 8 | { 9 | public static class ApplicationHealthMetricRegistry 10 | { 11 | // ReSharper disable MemberCanBePrivate.Global 12 | public static readonly string Context = "Application.Health"; 13 | // ReSharper restore MemberCanBePrivate.Global 14 | 15 | public static GaugeOptions Checks => new GaugeOptions 16 | { 17 | Context = Context, 18 | Name = "Results", 19 | MeasurementUnit = Unit.Items 20 | }; 21 | 22 | public static GaugeOptions HealthGauge => new GaugeOptions 23 | { 24 | Context = Context, 25 | Name = "Score", 26 | MeasurementUnit = Unit.Custom("Health Score") 27 | }; 28 | } 29 | } -------------------------------------------------------------------------------- /src/App.Metrics.Health.Abstractions/App.Metrics.Health.Abstractions.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | App Metrics Health abstractions and interfaces. 5 | netstandard1.6 6 | $(TargetFrameworks);net452 7 | App.Metrics.Health 8 | true 9 | appmetrics;healthchecks 10 | 11 | 12 | 13 | TRACE;DEBUG;NETSTANDARD2_0;LIBLOG_PORTABLE;LIBLOG_PUBLIC 14 | 15 | 16 | 17 | TRACE;RELEASE;NETSTANDARD2_0;LIBLOG_PORTABLE;LIBLOG_PUBLIC 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | version: 1.0.{build} 2 | image: Visual Studio 2017 3 | init: 4 | - git config --global core.autocrlf input 5 | environment: 6 | COVERALLS_REPO_TOKEN: 7 | secure: 9C17ZvEksrTRexM6zsRiNkr599sd9q4oNFacSsMvyLhdME6G+FC1SqlLXJMYW2KD 8 | DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true 9 | DOTNET_CLI_TELEMETRY_OPTOUT: true 10 | CoverWith: OpenCover 11 | PreReleaseSuffix: alpha 12 | GitUser: 13 | secure: zAdAOUUof3XGDsdOBRYg7J7wZS44iL4VjI/MVGw+JnU= 14 | GitOwner: 15 | secure: n6W5JZ7Q/xfZC7b2k3+ORA== 16 | GitPassword: 17 | secure: fHangGPAk14u3V1eP3kg5EdekQoKdOsKC0F/A1mvh9IODHCZ92FGjfWb0U2aQ8bu 18 | build_script: 19 | - ps: .\build.ps1 -Target AppVeyor 20 | test: off 21 | artifacts: 22 | - path: artifacts/packages/*.nupkg 23 | - path: artifacts/coverage/*.xml 24 | - path: artifacts/test-results/*.trx 25 | - path: artifacts/resharper-reports/*.xml 26 | - path: artifacts/resharper-reports/*.html 27 | - path: artifacts/coverage/coverage.dcvr 28 | deploy: 29 | - provider: NuGet 30 | server: https://www.myget.org/F/appmetrics/api/v2/package 31 | api_key: 32 | secure: gIAiACgNj+JzXyLLTe3rLxZyrAB9RpC8Lw81xEjdOLXqotprqEwGiFWRipEqkpps 33 | skip_symbols: true 34 | symbol_server: https://www.myget.org/F/appmetrics/symbol 35 | install: 36 | - cmd: curl -O https://download.microsoft.com/download/8/8/5/88544F33-836A-49A5-8B67-451C24709A8F/dotnet-sdk-2.1.300-win-x64.exe 37 | - cmd: dotnet-sdk-2.1.300-win-x64.exe /install /quiet /norestart /log install.log 38 | skip_commits: 39 | files: 40 | - '**/*.md' -------------------------------------------------------------------------------- /test/App.Metrics.Health.Facts/TestHelpers/TestReporter.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) App Metrics Contributors. All rights reserved. 3 | // 4 | 5 | using System; 6 | using System.Threading; 7 | using System.Threading.Tasks; 8 | 9 | namespace App.Metrics.Health.Facts.TestHelpers 10 | { 11 | public class TestReporter : IReportHealthStatus 12 | { 13 | private readonly bool _pass; 14 | private readonly Exception _throwEx; 15 | 16 | public TestReporter() 17 | { 18 | } 19 | 20 | public TestReporter(bool pass = true, Exception throwEx = null) 21 | { 22 | _pass = throwEx == null && pass; 23 | _throwEx = throwEx; 24 | ReportInterval = TimeSpan.FromMilliseconds(10); 25 | } 26 | 27 | public TestReporter(TimeSpan interval, bool pass = true, Exception throwEx = null) 28 | { 29 | ReportInterval = interval; 30 | _pass = throwEx == null && pass; 31 | _throwEx = throwEx; 32 | } 33 | 34 | public TimeSpan ReportInterval { get; set; } 35 | 36 | /// 37 | public Task ReportAsync(HealthOptions options, HealthStatus status, CancellationToken cancellationToken = default) 38 | { 39 | if (_throwEx != null) 40 | { 41 | throw _throwEx; 42 | } 43 | 44 | return Task.FromResult(_pass); 45 | } 46 | } 47 | } -------------------------------------------------------------------------------- /src/App.Metrics.Health.Abstractions/HealthOptions.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) App Metrics Contributors. All rights reserved. 3 | // 4 | 5 | namespace App.Metrics.Health 6 | { 7 | /// 8 | /// Top level container for all configuration settings of Health 9 | /// 10 | public class HealthOptions 11 | { 12 | public HealthOptions() 13 | { 14 | Enabled = true; 15 | ReportingEnabled = true; 16 | } 17 | 18 | /// 19 | /// Gets or sets the application name used when reporting health status for example. 20 | /// 21 | public string ApplicationName { get; set; } 22 | 23 | /// 24 | /// Gets or sets a value indicating whether [health checks enabled]. This will also avoid registering all health 25 | /// middleware if using App.Metrics.Health.Middleware. 26 | /// 27 | /// 28 | /// true if [health enabled]; otherwise, false. 29 | /// 30 | public bool Enabled { get; set; } 31 | 32 | /// 33 | /// Gets or sets a value indicating whether [reporting enabled]. 34 | /// 35 | /// 36 | /// true if [reporting enabled]; otherwise, false. 37 | /// 38 | public bool ReportingEnabled { get; set; } = true; 39 | } 40 | } -------------------------------------------------------------------------------- /src/App.Metrics.Health/AppMetricsHealth.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) App Metrics Contributors. All rights reserved. 3 | // 4 | 5 | using App.Metrics.Health.Builder; 6 | 7 | namespace App.Metrics.Health 8 | { 9 | /// 10 | /// Provides convenience methods for creating instances of and with pre-configured defaults. 11 | /// 12 | public static class AppMetricsHealth 13 | { 14 | /// 15 | /// Initializes a new instance of the class with pre-configured defaults. 16 | /// 17 | /// 18 | /// The following defaults are applied to the returned : 19 | /// use, JSON and Plain Text health check result formatting. 20 | /// 21 | /// The initialized . 22 | public static IHealthBuilder CreateDefaultBuilder() 23 | { 24 | var builder = new HealthBuilder() 25 | .Configuration 26 | .Configure( 27 | options => 28 | { 29 | options.Enabled = true; 30 | }) 31 | .OutputHealth.AsJson() 32 | .OutputHealth.AsPlainText(); 33 | 34 | return builder; 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/App.Metrics.Health.Abstractions/HealthStatus.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) App Metrics Contributors. All rights reserved. 3 | // 4 | 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | 8 | namespace App.Metrics.Health 9 | { 10 | /// 11 | /// Structure describing the status of executing all the health checks operations. 12 | /// 13 | public struct HealthStatus 14 | { 15 | public HealthStatus(IEnumerable results) 16 | { 17 | Results = results; 18 | } 19 | 20 | /// 21 | /// Gets result of each health check operation 22 | /// 23 | /// 24 | /// The health check results. 25 | /// 26 | public IEnumerable Results { get; } 27 | 28 | /// 29 | /// Gets all health checks passed. 30 | /// 31 | /// 32 | /// The status. 33 | /// 34 | public HealthCheckStatus Status 35 | { 36 | get 37 | { 38 | if (Results.Any(r => r.Check.Status.IsUnhealthy())) 39 | { 40 | return HealthCheckStatus.Unhealthy; 41 | } 42 | 43 | return Results.Any(r => r.Check.Status.IsDegraded()) 44 | ? HealthCheckStatus.Degraded 45 | : HealthCheckStatus.Healthy; 46 | } 47 | } 48 | } 49 | } -------------------------------------------------------------------------------- /src/App.Metrics.Health.Abstractions/IHealthRoot.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) App Metrics Contributors. All rights reserved. 3 | // 4 | 5 | using System.Collections.Generic; 6 | using App.Metrics.Health.Formatters; 7 | 8 | namespace App.Metrics.Health 9 | { 10 | public interface IHealthRoot : IHealth 11 | { 12 | /// 13 | /// Gets the default to use when health checks are attempted to be formatted. 14 | /// 15 | /// 16 | /// The default s that is used by this application. 17 | /// 18 | IHealthOutputFormatter DefaultOutputHealthFormatter { get; } 19 | 20 | IRunHealthChecks HealthCheckRunner { get; } 21 | 22 | HealthOptions Options { get; } 23 | 24 | /// 25 | /// Gets a list of s that are used by this application to format health 26 | /// results. 27 | /// 28 | /// 29 | /// A list of s that are used by this application. 30 | /// 31 | IReadOnlyCollection OutputHealthFormatters { get; } 32 | 33 | /// 34 | /// Gets a readonly collection of s which provide the ability to report on health 35 | /// check results. 36 | /// 37 | IReadOnlyCollection Reporters { get; } 38 | } 39 | } -------------------------------------------------------------------------------- /src/App.Metrics.Health.Abstractions/Builder/IHealthBuilder.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) App Metrics Contributors. All rights reserved. 3 | // 4 | 5 | // ReSharper disable CheckNamespace 6 | namespace App.Metrics.Health 7 | // ReSharper restore CheckNamespace 8 | { 9 | public interface IHealthBuilder 10 | { 11 | bool CanReport(); 12 | 13 | /// 14 | /// Builder for configuring core App Metrics Health options. 15 | /// 16 | IHealthConfigurationBuilder Configuration { get; } 17 | 18 | IHealthCheckBuilder HealthChecks { get; } 19 | 20 | /// 21 | /// 22 | /// Builder for configuring health check output formatting for reporting. 23 | /// 24 | /// 25 | /// Multiple formatters can be used, in which case the 26 | /// will be set to the first configured formatter. 27 | /// 28 | /// 29 | IHealthOutputFormattingBuilder OutputHealth { get; } 30 | 31 | /// 32 | /// Builder for configuring health status reporters. 33 | /// 34 | IHealthReportingBuilder Report { get; } 35 | 36 | /// 37 | /// Builds an with the services configured via an . 38 | /// 39 | /// An with services configured via an . 40 | IHealthRoot Build(); 41 | } 42 | } -------------------------------------------------------------------------------- /src/App.Metrics.Health.Abstractions/HealthConstants.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) App Metrics Contributors. All rights reserved. 3 | // 4 | 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Collections.ObjectModel; 8 | 9 | namespace App.Metrics.Health 10 | { 11 | public static class HealthConstants 12 | { 13 | public const string DegradedStatusDisplay = "Degraded"; 14 | public const string HealthyStatusDisplay = "Healthy"; 15 | public const string UnhealthyStatusDisplay = "Unhealthy"; 16 | public const string IgnoredStatusDisplay = "Ignored"; 17 | 18 | #pragma warning disable SA1008 19 | public static readonly (double healthy, double degraded, double unhealthy) HealthScore = (1.0, 0.5, 0.0); 20 | #pragma warning restore SA1008 21 | 22 | public static ReadOnlyDictionary HealthStatusDisplay => 23 | new ReadOnlyDictionary( 24 | new Dictionary 25 | { 26 | { HealthCheckStatus.Healthy, HealthyStatusDisplay }, 27 | { HealthCheckStatus.Unhealthy, UnhealthyStatusDisplay }, 28 | { HealthCheckStatus.Degraded, DegradedStatusDisplay }, 29 | { HealthCheckStatus.Ignored, IgnoredStatusDisplay } 30 | }); 31 | 32 | public static class Reporting 33 | { 34 | public static readonly TimeSpan DefaultReportInterval = TimeSpan.FromSeconds(10); 35 | public static readonly int DefaultNumberOfRunsBeforeReAlerting = 6; // Every 60secs 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /test/App.Metrics.Health.Formatters.Ascii.Facts/HealthStatusTextWriterTests.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) App Metrics Contributors. All rights reserved. 3 | // 4 | 5 | using System.IO; 6 | using System.Threading.Tasks; 7 | using App.Metrics.Health.Builder; 8 | using App.Metrics.Health.Serialization; 9 | using FluentAssertions; 10 | using Xunit; 11 | 12 | namespace App.Metrics.Health.Formatters.Ascii.Facts 13 | { 14 | public class HealthStatusTextWriterTests 15 | { 16 | [Fact] 17 | public async Task Can_apply_ascii_health_formatting() 18 | { 19 | // Arrange 20 | var health = new HealthBuilder() 21 | .OutputHealth.AsPlainText() 22 | .HealthChecks.AddCheck("test", () => new ValueTask(HealthCheckResult.Healthy())) 23 | .Build(); 24 | 25 | var serializer = new HealthStatusSerializer(); 26 | 27 | // Act 28 | var healthStatus = await health.HealthCheckRunner.ReadAsync(); 29 | 30 | using (var sw = new StringWriter()) 31 | { 32 | using (var writer = new HealthStatusTextWriter(sw)) 33 | { 34 | serializer.Serialize(writer, healthStatus); 35 | } 36 | 37 | // Assert 38 | sw.ToString().Should().Be( 39 | "# OVERALL STATUS: Healthy\n--------------------------------------------------------------\n# CHECK: test\n\n MESSAGE = OK\n STATUS = Healthy\n--------------------------------------------------------------\n"); 40 | } 41 | } 42 | } 43 | } -------------------------------------------------------------------------------- /benchmarks/App.Metrics.Health.Benchmarks/Support/OutputLogger.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) App Metrics Contributors. All rights reserved. 3 | // 4 | 5 | using System; 6 | using BenchmarkDotNet.Loggers; 7 | using Xunit.Abstractions; 8 | 9 | namespace App.Metrics.Health.Benchmarks.Support 10 | { 11 | public class OutputLogger : AccumulationLogger 12 | { 13 | private readonly ITestOutputHelper _testOutputHelper; 14 | private string _currentLine = string.Empty; 15 | 16 | public OutputLogger(ITestOutputHelper testOutputHelper) 17 | { 18 | _testOutputHelper = testOutputHelper ?? throw new ArgumentNullException(nameof(testOutputHelper)); 19 | } 20 | 21 | public override void Write(LogKind logKind, string text) 22 | { 23 | _currentLine += RemoveInvalidChars(text); 24 | base.Write(logKind, text); 25 | } 26 | 27 | public override void WriteLine() 28 | { 29 | _testOutputHelper.WriteLine(_currentLine); 30 | _currentLine = string.Empty; 31 | base.WriteLine(); 32 | } 33 | 34 | public override void WriteLine(LogKind logKind, string text) 35 | { 36 | _testOutputHelper.WriteLine(_currentLine + RemoveInvalidChars(text)); 37 | _currentLine = string.Empty; 38 | base.WriteLine(logKind, text); 39 | } 40 | 41 | private static string RemoveInvalidChars(string text) 42 | { 43 | if (string.IsNullOrEmpty(text)) 44 | { 45 | return string.Empty; 46 | } 47 | 48 | return text.Replace((char)0x1B, ' '); 49 | } 50 | } 51 | } -------------------------------------------------------------------------------- /src/App.Metrics.Health.Reporting.Slack/SlackHealthAlertOptions.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) App Metrics Contributors. All rights reserved. 3 | // 4 | 5 | using System; 6 | 7 | namespace App.Metrics.Health.Reporting.Slack 8 | { 9 | public class SlackHealthAlertOptions 10 | { 11 | public bool AlertOnDegradedChecks { get; set; } = true; 12 | 13 | public string Channel { get; set; } 14 | 15 | public string EmojiIcon { get; set; } 16 | 17 | public bool Enabled { get; set; } = true; 18 | 19 | /// 20 | /// Gets or sets the health status reporting interval. 21 | /// 22 | /// 23 | /// If not set reporting interval will be set to the . 24 | /// 25 | /// 26 | /// The to wait between reporting health status. 27 | /// 28 | public TimeSpan ReportInterval { get; set; } 29 | 30 | /// 31 | /// Gets or sets the number of report runs before re-alerting checks that have re-failed. If set to 0, failed checks will only be reported once until healthy again. 32 | /// 33 | /// 34 | /// If not set number of runs will be set to the . 35 | /// 36 | public int RunsBeforeReportExistingFailures { get; set; } = HealthConstants.Reporting.DefaultNumberOfRunsBeforeReAlerting; 37 | 38 | public string Username { get; set; } 39 | 40 | public string WebhookUrl { get; set; } 41 | } 42 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | #OS junk files 2 | [Tt]humbs.db 3 | *.DS_Store 4 | *.orig 5 | 6 | #Visual Studio files 7 | *.[Oo]bj 8 | *.user 9 | *.aps 10 | *.pch 11 | *.vspscc 12 | *.vssscc 13 | *_i.c 14 | *_p.c 15 | *.ncb 16 | *.suo 17 | *.tlb 18 | *.tlh 19 | *.bak 20 | *.[Cc]ache 21 | *.ilk 22 | *.log 23 | *.lib 24 | *.sbr 25 | *.sdf 26 | *.opensdf 27 | *.unsuccessfulbuild 28 | ipch/ 29 | [Oo]bj/ 30 | [Bb]in 31 | Ankh.NoLoad 32 | *.vsmdi 33 | *.lock.json 34 | 35 | #Tooling 36 | .idea 37 | _ReSharper*/ 38 | *.resharper 39 | [Tt]est[Rr]esult* 40 | *.sass-cache 41 | 42 | #Subversion files 43 | .svn 44 | 45 | # Office Temp Files 46 | ~$* 47 | 48 | #ncrunch 49 | *ncrunch* 50 | *crunch*.local.xml 51 | 52 | #Test files 53 | *.testsettings 54 | 55 | #Build 56 | BuildOutput 57 | BuildOutput/* 58 | WebPublish 59 | WebPublish/* 60 | 61 | #Cake 62 | tools/* 63 | !tools/packages.config 64 | 65 | .vs 66 | 67 | # visual studio database projects 68 | *.dbmdl 69 | logs/* 70 | .vs/* 71 | *.project.lock.json 72 | 73 | #Temp folders 74 | temp/ 75 | 76 | #Tools and packages 77 | .nuget 78 | .nuget/* 79 | artifacts 80 | artifacts/* 81 | packages 82 | packages/* 83 | *.lock.json 84 | 85 | #BenchMarkDotNet 86 | BDN.Generated 87 | BDN.Generated/* 88 | scripts/BenchMarkDotNet.Artifacts 89 | scripts/BenchMarkDotNet.Artifacts/* 90 | **/BenchMarkDotNet.Artifacts/* 91 | !/benchmarks/App.Metrics.Benchmarks.Runner/BenchMarkDotNet.Artifacts/* 92 | /benchmarks/App.Metrics.Benchmarks.Runner/BenchMarkDotNet.Artifacts/*.log 93 | /benchmarks/App.Metrics.Benchmarks.Runner/BenchMarkDotNet.Artifacts/results/*.csv 94 | /benchmarks/App.Metrics.Benchmarks.Runner/BenchMarkDotNet.Artifacts/results/*.html 95 | 96 | #NDepend 97 | *.ndproj 98 | NDependOut/* 99 | 100 | #DotCover 101 | Session/* 102 | Session.html 103 | -------------------------------------------------------------------------------- /src/App.Metrics.Health.Core/Internal/DefaultHealthCheckRunner.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) App Metrics Contributors. All rights reserved. 3 | // 4 | 5 | using System.Collections.Generic; 6 | using System.Diagnostics; 7 | using System.Linq; 8 | using System.Threading; 9 | using System.Threading.Tasks; 10 | using App.Metrics.Health.Logging; 11 | 12 | namespace App.Metrics.Health.Internal 13 | { 14 | public sealed class DefaultHealthCheckRunner : IRunHealthChecks 15 | { 16 | private static readonly ILog Logger = LogProvider.For(); 17 | private readonly IEnumerable _checks; 18 | 19 | /// 20 | /// Initializes a new instance of the class. 21 | /// 22 | /// The registered health checks. 23 | public DefaultHealthCheckRunner(IEnumerable checks) 24 | { 25 | _checks = checks ?? Enumerable.Empty(); 26 | } 27 | 28 | /// 29 | public async ValueTask ReadAsync(CancellationToken cancellationToken = default) 30 | { 31 | if (!_checks.Any()) 32 | { 33 | return default; 34 | } 35 | 36 | var startTimestamp = Logger.IsTraceEnabled() ? Stopwatch.GetTimestamp() : 0; 37 | 38 | Logger.HealthCheckGetStatusExecuting(); 39 | 40 | var results = await Task.WhenAll(_checks.OrderBy(v => v.Name).Select(v => v.ExecuteAsync(cancellationToken).AsTask())); 41 | 42 | var healthStatus = new HealthStatus(results); 43 | 44 | Logger.HealthCheckGetStatusExecuted(healthStatus, startTimestamp); 45 | 46 | return healthStatus; 47 | } 48 | } 49 | } -------------------------------------------------------------------------------- /benchmarks/App.Metrics.Health.Benchmarks/Support/SimpleBenchmarkRunner.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) App Metrics Contributors. All rights reserved. 3 | // 4 | 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Linq; 8 | using System.Threading; 9 | 10 | namespace App.Metrics.Health.Benchmarks.Support 11 | { 12 | public static class SimpleBenchmarkRunner 13 | { 14 | private const int NumberOfRuns = 10000; 15 | private const int WarmupRuns = 10; 16 | private static readonly int ThreadCount = Environment.ProcessorCount; 17 | 18 | public static void Run(Action action, bool warmup = false) 19 | { 20 | if (warmup) 21 | { 22 | Warmup(action); 23 | } 24 | 25 | var thread = new List(); 26 | 27 | for (var i = 0; i < ThreadCount; i++) 28 | { 29 | thread.Add( 30 | new Thread( 31 | () => 32 | { 33 | for (long j = 0; j < NumberOfRuns; j++) 34 | { 35 | action(); 36 | } 37 | })); 38 | } 39 | 40 | thread.ForEach(t => t.Start()); 41 | thread.ForEach(t => t.Join()); 42 | } 43 | 44 | private static void PerformCollection() 45 | { 46 | GC.Collect(); 47 | GC.WaitForPendingFinalizers(); 48 | GC.Collect(); 49 | Thread.Sleep(200); 50 | } 51 | 52 | private static void Warmup(Action action) 53 | { 54 | foreach (var unused in Enumerable.Range(1, WarmupRuns)) 55 | { 56 | action(); 57 | } 58 | 59 | PerformCollection(); 60 | } 61 | } 62 | } -------------------------------------------------------------------------------- /src/App.Metrics.Health.Formatters.Json/DefaultJsonSerializerSettings.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) App Metrics Contributors. All rights reserved. 3 | // 4 | 5 | using App.Metrics.Health.Formatters.Json.Converters; 6 | using Newtonsoft.Json; 7 | using Newtonsoft.Json.Serialization; 8 | 9 | namespace App.Metrics.Health.Formatters.Json 10 | { 11 | public static class DefaultJsonSerializerSettings 12 | { 13 | private const int DefaultMaxDepth = 32; 14 | 15 | private static readonly DefaultContractResolver SharedContractResolver = new DefaultContractResolver 16 | { 17 | NamingStrategy = new CamelCaseNamingStrategy() 18 | }; 19 | 20 | /// 21 | /// Creates default . 22 | /// 23 | /// Default . 24 | public static JsonSerializerSettings CreateSerializerSettings() 25 | { 26 | var settings = new JsonSerializerSettings 27 | { 28 | ContractResolver = SharedContractResolver, 29 | NullValueHandling = NullValueHandling.Ignore, 30 | MissingMemberHandling = MissingMemberHandling.Ignore, 31 | Formatting = Formatting.Indented, 32 | MaxDepth = DefaultMaxDepth, 33 | TypeNameHandling = TypeNameHandling.None 34 | }; 35 | 36 | settings.Converters.Add(new HealthStatusConverter()); 37 | 38 | return settings; 39 | } 40 | } 41 | } -------------------------------------------------------------------------------- /src/App.Metrics.Health.Core/HealthRoot.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) App Metrics Contributors. All rights reserved. 3 | // 4 | 5 | using System; 6 | using System.Collections.Generic; 7 | using App.Metrics.Health.Formatters; 8 | using App.Metrics.Health.Internal; 9 | 10 | namespace App.Metrics.Health 11 | { 12 | public class HealthRoot : IHealthRoot 13 | { 14 | private readonly IHealth _health; 15 | 16 | public HealthRoot( 17 | IHealth health, 18 | HealthOptions options, 19 | HealthFormatterCollection metricsOutputFormatters, 20 | IHealthOutputFormatter defaultMetricsOutputFormatter, 21 | IRunHealthChecks healthCheckRunner, 22 | HealthReporterCollection reporterCollection) 23 | { 24 | Options = options ?? throw new ArgumentNullException(nameof(options)); 25 | _health = health ?? throw new ArgumentNullException(nameof(health)); 26 | OutputHealthFormatters = metricsOutputFormatters ?? new HealthFormatterCollection(); 27 | DefaultOutputHealthFormatter = defaultMetricsOutputFormatter; 28 | HealthCheckRunner = healthCheckRunner; 29 | Reporters = reporterCollection ?? new HealthReporterCollection(); 30 | } 31 | 32 | /// 33 | public IReadOnlyCollection OutputHealthFormatters { get; } 34 | 35 | /// 36 | public IHealthOutputFormatter DefaultOutputHealthFormatter { get; } 37 | 38 | /// 39 | public HealthOptions Options { get; } 40 | 41 | /// 42 | public IRunHealthChecks HealthCheckRunner { get; } 43 | 44 | /// 45 | public IReadOnlyCollection Reporters { get; } 46 | 47 | /// 48 | public IEnumerable Checks => _health.Checks; 49 | } 50 | } -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## How to contribute to App Metrics 2 | 3 | Thanks for taking the time to contribute to App Metrics :+1: 4 | 5 | ### **Coding Style** 6 | 7 | Coding style is enforced using StyleCop. To automate the clean up of most rules, the solution includes a "team-shared" [ReSharper DotSettings](AppMetrics.sln.DotSettings), the ReSharper Code Cleanup profile is named `AppMetrics`. 8 | 9 | If your not familiar with ReSharper Code Cleanup, see the [code cleanup docs](https://www.jetbrains.com/help/resharper/2016.3/Code_Cleanup__Running_Code_Cleanup.html) and [settings layer docs](https://www.jetbrains.com/help/resharper/2016.3/Reference__Settings_Layers.html). 10 | 11 | ### **Have you found a bug?** 12 | 13 | **Ensure the bug was not already reported** by searching on GitHub under [Issues](https://github.com/alhardy/AppMetrics/issues). 14 | 15 | If you're unable to find an open issue related to the bug you've found go ahead and [open a new issue](https://github.com/alhardy/AppMetrics/issues/new). Be sure to include: 16 | 17 | 1. A **title and clear description** 18 | 2. As much relevant information as possible including the exact steps to reproduce the bug. 19 | 3. If possible provide a **code sample** or **unit test** demonstrating the bug. 20 | 21 | ### **Did you write a patch that fixes a bug?** 22 | 23 | * Open a new [GitHub pull request](https://help.github.com/articles/about-pull-requests/) on the `dev` branch. 24 | 25 | * Ensure the pull request description clearly describes the problem and solution. Include the relevant issue number in the commit message e.g. `git commit -m '#1 {message}` 26 | 27 | ### **Requesting a new feature?** 28 | 29 | * Suggest your feature as a [new issue](https://github.com/alhardy/AppMetrics/issues/new) to start a discussion. 30 | 31 | ### **Contributing to the documentation** 32 | 33 | App Metrics documentation is built using [Hugo](https://gohugo.io/documentation/), you can find the github repo [here](https://github.com/AppMetrics/Docs.V2.Hugo) and create a new pull request on the `master` branch. 34 | -------------------------------------------------------------------------------- /src/App.Metrics.Health.Formatters.Json/Builder/HealthJsonOutputFormatterBuilder.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) App Metrics Contributors. All rights reserved. 3 | // 4 | 5 | using System; 6 | using App.Metrics.Health.Formatters.Json; 7 | 8 | // ReSharper disable CheckNamespace 9 | namespace App.Metrics.Health 10 | // ReSharper restore CheckNamespace 11 | { 12 | /// 13 | /// Builder for configuring metric JSON output formatting using an 14 | /// . 15 | /// 16 | public static class HealthJsonOutputFormatterBuilder 17 | { 18 | /// 19 | /// Add the allowing health check results to optionally be reported as JSON. 20 | /// 21 | /// 22 | /// The used to configure JSON formatting 23 | /// options. 24 | /// 25 | /// The JSON formatting options to use. 26 | /// 27 | /// An that can be used to further configure App Metrics Health. 28 | /// 29 | public static IHealthBuilder AsJson( 30 | this IHealthOutputFormattingBuilder metricFormattingBuilder, 31 | Action setupAction = null) 32 | { 33 | if (metricFormattingBuilder == null) 34 | { 35 | throw new ArgumentNullException(nameof(metricFormattingBuilder)); 36 | } 37 | 38 | var options = new HealthJsonOptions(); 39 | 40 | setupAction?.Invoke(options); 41 | 42 | var formatter = new HealthStatusJsonOutputFormatter(options.SerializerSettings); 43 | 44 | return metricFormattingBuilder.Using(formatter); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/App.Metrics.Health.Formatters.Ascii/Builder/HealthTextOutputFormatterBuilder.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) App Metrics Contributors. All rights reserved. 3 | // 4 | 5 | using System; 6 | using App.Metrics.Health.Formatters; 7 | using App.Metrics.Health.Formatters.Ascii; 8 | 9 | // ReSharper disable CheckNamespace 10 | namespace App.Metrics.Health 11 | // ReSharper restore CheckNamespace 12 | { 13 | /// 14 | /// Builder for configuring metric plain text output formatting using an 15 | /// . 16 | /// 17 | public static class HealthTextOutputFormatterBuilder 18 | { 19 | /// 20 | /// Add the allowing health checks to optionally be reported as plain text. 21 | /// 22 | /// 23 | /// The used to configure formatting 24 | /// options. 25 | /// 26 | /// The plain text formatting options to use. 27 | /// 28 | /// An that can be used to further configure App Metrics Health. 29 | /// 30 | public static IHealthBuilder AsPlainText( 31 | this IHealthOutputFormattingBuilder healthFormattingBuilder, 32 | Action setupAction = null) 33 | { 34 | if (healthFormattingBuilder == null) 35 | { 36 | throw new ArgumentNullException(nameof(healthFormattingBuilder)); 37 | } 38 | 39 | var options = new HealthTextOptions(); 40 | 41 | setupAction?.Invoke(options); 42 | 43 | var formatter = new HealthStatusTextOutputFormatter(); 44 | 45 | return healthFormattingBuilder.Using(formatter); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/App.Metrics.Health.Formatters.Ascii/HealthStatusTextOutputFormatter.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) App Metrics Contributors. All rights reserved. 3 | // 4 | 5 | using System; 6 | using System.IO; 7 | using System.Threading; 8 | using System.Threading.Tasks; 9 | using App.Metrics.Health.Serialization; 10 | #if !NETSTANDARD1_6 11 | using App.Metrics.Health.Internal; 12 | #endif 13 | 14 | namespace App.Metrics.Health.Formatters.Ascii 15 | { 16 | public class HealthStatusTextOutputFormatter : IHealthOutputFormatter 17 | { 18 | private readonly HealthTextOptions _options; 19 | 20 | public HealthStatusTextOutputFormatter() 21 | { 22 | _options = new HealthTextOptions(); 23 | } 24 | 25 | public HealthStatusTextOutputFormatter(HealthTextOptions options) { _options = options ?? throw new ArgumentNullException(nameof(options)); } 26 | 27 | public HealthMediaTypeValue MediaType => new HealthMediaTypeValue("text", "vnd.appmetrics.health", "v1", "plain"); 28 | 29 | public Task WriteAsync( 30 | Stream output, 31 | HealthStatus healthStatus, 32 | CancellationToken cancellationToken = default) 33 | { 34 | if (output == null) 35 | { 36 | throw new ArgumentNullException(nameof(output)); 37 | } 38 | 39 | var serializer = new HealthStatusSerializer(); 40 | 41 | using (var stringWriter = new StreamWriter(output, _options.Encoding)) 42 | { 43 | using (var textWriter = new HealthStatusTextWriter(stringWriter, _options.Separator, _options.Padding)) 44 | { 45 | serializer.Serialize(textWriter, healthStatus); 46 | } 47 | } 48 | 49 | #if !NETSTANDARD1_6 50 | return AppMetricsHealthTaskHelper.CompletedTask(); 51 | #else 52 | return Task.CompletedTask; 53 | #endif 54 | } 55 | } 56 | } -------------------------------------------------------------------------------- /src/App.Metrics.Health.Formatters.Json/HealthStatusJsonOutputFormatter.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) App Metrics Contributors. All rights reserved. 3 | // 4 | 5 | using System; 6 | using System.IO; 7 | using System.Threading; 8 | using System.Threading.Tasks; 9 | using Newtonsoft.Json; 10 | #if !NETSTANDARD1_6 11 | using App.Metrics.Health.Internal; 12 | #endif 13 | 14 | namespace App.Metrics.Health.Formatters.Json 15 | { 16 | public class HealthStatusJsonOutputFormatter : IHealthOutputFormatter 17 | { 18 | private readonly JsonSerializerSettings _serializerSettings; 19 | 20 | public HealthStatusJsonOutputFormatter() { _serializerSettings = DefaultJsonSerializerSettings.CreateSerializerSettings(); } 21 | 22 | public HealthStatusJsonOutputFormatter(JsonSerializerSettings serializerSettings) 23 | { 24 | _serializerSettings = serializerSettings ?? throw new ArgumentNullException(nameof(serializerSettings)); 25 | } 26 | 27 | public HealthMediaTypeValue MediaType => new HealthMediaTypeValue("application", "vnd.appmetrics.health", "v1", "json"); 28 | 29 | public Task WriteAsync( 30 | Stream output, 31 | HealthStatus healthStatus, 32 | CancellationToken cancellationToken = default) 33 | { 34 | if (output == null) 35 | { 36 | throw new ArgumentNullException(nameof(output)); 37 | } 38 | 39 | var serilizer = JsonSerializer.Create(_serializerSettings); 40 | 41 | using (var streamWriter = new StreamWriter(output)) 42 | { 43 | using (var textWriter = new JsonTextWriter(streamWriter)) 44 | { 45 | serilizer.Serialize(textWriter, healthStatus); 46 | } 47 | } 48 | 49 | #if NETSTANDARD1_6 50 | return Task.CompletedTask; 51 | #else 52 | return AppMetricsHealthTaskHelper.CompletedTask(); 53 | #endif 54 | } 55 | } 56 | } -------------------------------------------------------------------------------- /sandbox/HealthSandbox/HealthSandbox.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | exe 7 | netcoreapp2.1 8 | $(TargetFrameworks);net461 9 | latest 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | Always 38 | PreserveNewest 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /src/App.Metrics.Health.Reporting.Slack/Internal/DefaultJsonSerializerSettings.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) App Metrics Contributors. All rights reserved. 3 | // 4 | 5 | using System; 6 | using Newtonsoft.Json; 7 | using Newtonsoft.Json.Converters; 8 | using Newtonsoft.Json.Serialization; 9 | 10 | namespace App.Metrics.Health.Reporting.Slack.Internal 11 | { 12 | internal static class DefaultJsonSerializerSettings 13 | { 14 | private const int DefaultMaxDepth = 32; 15 | 16 | private static readonly DefaultContractResolver DefaultContractResolver = new DefaultContractResolver 17 | { 18 | NamingStrategy = new SnakeCaseNamingStrategy() 19 | }; 20 | 21 | private static readonly Lazy Lazy = 22 | new Lazy( 23 | () => 24 | { 25 | var settings = new JsonSerializerSettings 26 | { 27 | DateParseHandling = DateParseHandling.DateTimeOffset, 28 | MaxDepth = DefaultMaxDepth, 29 | ContractResolver = DefaultContractResolver, 30 | NullValueHandling = NullValueHandling.Ignore, 31 | MissingMemberHandling = MissingMemberHandling.Ignore, 32 | Formatting = Formatting.Indented, 33 | TypeNameHandling = TypeNameHandling.None 34 | }; 35 | 36 | settings.Converters.Add(new StringEnumConverter()); 37 | 38 | return settings; 39 | }); 40 | 41 | public static JsonSerializerSettings Instance => Lazy.Value; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /test/App.Metrics.Health.Facts/HealthCheckResultTests.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) App Metrics Contributors. All rights reserved. 3 | // 4 | 5 | using System; 6 | using FluentAssertions; 7 | using Xunit; 8 | 9 | namespace App.Metrics.Health.Facts 10 | { 11 | public class HealthCheckResultTests 12 | { 13 | [Fact] 14 | public void Can_create_degraded() 15 | { 16 | var result = HealthCheckResult.Degraded("degrading api"); 17 | 18 | result.Message.Should().Be("degrading api"); 19 | result.Status.Should().Be(HealthCheckStatus.Degraded); 20 | } 21 | 22 | [Fact] 23 | public void Can_create_degraded_with_exception_info() 24 | { 25 | var exception = new InvalidOperationException(); 26 | var exceptionString = $"EXCEPTION: {exception.GetType().Name} - {exception.Message}"; 27 | var result = HealthCheckResult.Degraded(exception); 28 | 29 | result.Message.Should().StartWith(exceptionString); 30 | result.Status.Should().Be(HealthCheckStatus.Degraded); 31 | } 32 | 33 | [Fact] 34 | public void Can_create_degraded_with_formatted_values() 35 | { 36 | var id = Guid.NewGuid(); 37 | var result = HealthCheckResult.Degraded("degrading api {0}", id); 38 | 39 | result.Message.Should().Be($"degrading api {id}"); 40 | result.Status.Should().Be(HealthCheckStatus.Degraded); 41 | } 42 | 43 | [Fact] 44 | public void Can_create_ignored() 45 | { 46 | var result = HealthCheckResult.Ignore(); 47 | 48 | result.Message.Should().Be("ignored check"); 49 | result.Status.Should().Be(HealthCheckStatus.Ignored); 50 | } 51 | 52 | [Fact] 53 | public void Message_defaults_to_degraded() 54 | { 55 | var unused = Guid.NewGuid(); 56 | var result = HealthCheckResult.Degraded(); 57 | 58 | result.Message.Should().Be("DEGRADED"); 59 | result.Status.Should().Be(HealthCheckStatus.Degraded); 60 | } 61 | } 62 | } -------------------------------------------------------------------------------- /src/App.Metrics.Health.Core/Builder/HealthReportingBuilder.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) App Metrics Contributors. All rights reserved. 3 | // 4 | 5 | using System; 6 | 7 | namespace App.Metrics.Health.Builder 8 | { 9 | public class HealthReportingBuilder : IHealthReportingBuilder 10 | { 11 | private readonly Action _reporters; 12 | 13 | public HealthReportingBuilder( 14 | IHealthBuilder healthBuilder, 15 | Action reporters) 16 | { 17 | Builder = healthBuilder ?? throw new ArgumentNullException(nameof(healthBuilder)); 18 | _reporters = reporters ?? throw new ArgumentNullException(nameof(reporters)); 19 | } 20 | 21 | /// 22 | public IHealthBuilder Builder { get; } 23 | 24 | /// 25 | public IHealthBuilder Using(IReportHealthStatus reporter) 26 | { 27 | if (reporter == null) 28 | { 29 | throw new ArgumentNullException(nameof(reporter)); 30 | } 31 | 32 | EnsureRequiredProperties(reporter); 33 | 34 | _reporters(reporter); 35 | 36 | return Builder; 37 | } 38 | 39 | /// 40 | public IHealthBuilder Using() 41 | where TReportHealth : IReportHealthStatus, new() 42 | { 43 | var reporter = new TReportHealth(); 44 | 45 | return Using(reporter); 46 | } 47 | 48 | public IHealthBuilder Using(TimeSpan reportInterval) 49 | where TReportHealth : IReportHealthStatus, new() 50 | { 51 | var reporter = new TReportHealth { ReportInterval = reportInterval }; 52 | 53 | return Using(reporter); 54 | } 55 | 56 | private static void EnsureRequiredProperties(IReportHealthStatus reporter) 57 | { 58 | reporter.ReportInterval = reporter.ReportInterval <= TimeSpan.Zero 59 | ? HealthConstants.Reporting.DefaultReportInterval 60 | : reporter.ReportInterval; 61 | } 62 | } 63 | } -------------------------------------------------------------------------------- /src/App.Metrics.Health.Abstractions/Internal/HealthReporterCollection.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) App Metrics Contributors. All rights reserved. 3 | // 4 | 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Collections.ObjectModel; 8 | 9 | namespace App.Metrics.Health.Internal 10 | { 11 | public class HealthReporterCollection : Collection 12 | { 13 | public HealthReporterCollection() { } 14 | 15 | public HealthReporterCollection(IList list) 16 | : base(list) 17 | { 18 | } 19 | 20 | public IReportHealthStatus GetType() 21 | where TReporter : IReportHealthStatus 22 | { 23 | return GetType(typeof(TReporter)); 24 | } 25 | 26 | public IReportHealthStatus GetType(Type reporterType) 27 | { 28 | for (var i = Count - 1; i >= 0; i--) 29 | { 30 | var reporter = this[i]; 31 | if (reporter.GetType() == reporterType) 32 | { 33 | return reporter; 34 | } 35 | } 36 | 37 | return default; 38 | } 39 | 40 | public void RemoveType() 41 | where TReporter : IReportHealthStatus 42 | { 43 | RemoveType(typeof(TReporter)); 44 | } 45 | 46 | public void RemoveType(Type reporterType) 47 | { 48 | for (var i = Count - 1; i >= 0; i--) 49 | { 50 | var reporter = this[i]; 51 | if (reporter.GetType() == reporterType) 52 | { 53 | RemoveAt(i); 54 | } 55 | } 56 | } 57 | 58 | public void TryAdd(IReportHealthStatus reporter) 59 | where TReporter : IReportHealthStatus 60 | { 61 | RemoveType(); 62 | Add(reporter); 63 | } 64 | 65 | public void TryAdd(IReportHealthStatus reporter) 66 | { 67 | RemoveType(reporter.GetType()); 68 | Add(reporter); 69 | } 70 | } 71 | } -------------------------------------------------------------------------------- /test/App.Metrics.Health.Facts/Builders/HealthReportingBuilderTests.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) App Metrics Contributors. All rights reserved. 3 | // 4 | 5 | using System; 6 | using App.Metrics.Health.Builder; 7 | using App.Metrics.Health.Facts.TestHelpers; 8 | using App.Metrics.Health.Internal; 9 | using FluentAssertions; 10 | using Xunit; 11 | 12 | namespace App.Metrics.Health.Facts.Builders 13 | { 14 | public class HealthReportingBuilderTests 15 | { 16 | [Fact] 17 | public void Cannot_set_null_reporter() 18 | { 19 | // Arrange 20 | Action action = () => 21 | { 22 | // Act 23 | var unused = new HealthBuilder().Report.Using(reporter: null); 24 | }; 25 | 26 | // Assert 27 | action.Should().Throw(); 28 | } 29 | 30 | [Fact] 31 | public void Can_use_reporter_instance() 32 | { 33 | // Arrange 34 | var reporter = new TestReporter(); 35 | var builder = new HealthBuilder().Report.Using(reporter); 36 | 37 | // Act 38 | var metrics = builder.Build(); 39 | 40 | // Assert 41 | metrics.Reporters.Should().Contain(reportMetrics => reportMetrics is TestReporter); 42 | } 43 | 44 | [Fact] 45 | public void Can_use_reporter_of_type() 46 | { 47 | // Arrange 48 | var builder = new HealthBuilder().Report.Using(); 49 | 50 | // Act 51 | var metrics = builder.Build(); 52 | 53 | // Assert 54 | metrics.Reporters.Should().Contain(reportMetrics => reportMetrics is TestReporter); 55 | } 56 | 57 | [Fact] 58 | public void Can_use_reporter_of_type_and_override_flushinterval() 59 | { 60 | // Arrange 61 | var builder = new HealthBuilder().Report.Using(reportInterval: TimeSpan.FromDays(1)); 62 | 63 | // Act 64 | var metrics = builder.Build(); 65 | var reporter = (metrics.Reporters as HealthReporterCollection)?.GetType(); 66 | 67 | // Assert 68 | reporter?.ReportInterval.Should().Be(TimeSpan.FromDays(1)); 69 | } 70 | } 71 | } -------------------------------------------------------------------------------- /src/App.Metrics.Health.Abstractions/Builder/IHealthReportingBuilder.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) App Metrics Contributors. All rights reserved. 3 | // 4 | 5 | using System; 6 | 7 | // ReSharper disable CheckNamespace 8 | namespace App.Metrics.Health 9 | // ReSharper restore CheckNamespace 10 | { 11 | public interface IHealthReportingBuilder 12 | { 13 | /// 14 | /// Gets the where App Metrics Health is configured. 15 | /// 16 | IHealthBuilder Builder { get; } 17 | 18 | /// 19 | /// Reports health status using the specified . 20 | /// 21 | /// An instance used to report health status. 22 | /// 23 | /// An that can be used to further configure App Metrics Health. 24 | /// 25 | IHealthBuilder Using(IReportHealthStatus reporter); 26 | 27 | /// 28 | /// Reports metrics using the specified . 29 | /// 30 | /// 31 | /// An type used to report health status. 32 | /// 33 | /// 34 | /// An that can be used to further configure App Metrics Health. 35 | /// 36 | IHealthBuilder Using() 37 | where TReportHealth : IReportHealthStatus, new(); 38 | 39 | /// 40 | /// Reports metrics using the specified . 41 | /// 42 | /// The interval used to schedule health status reporting. 43 | /// 44 | /// An type used to report health status. 45 | /// 46 | /// 47 | /// An that can be used to further configure App Metrics Health. 48 | /// 49 | IHealthBuilder Using(TimeSpan reportInterval) 50 | where TReportHealth : IReportHealthStatus, new(); 51 | } 52 | } -------------------------------------------------------------------------------- /src/App.Metrics.Health.Core/Builder/HealthCheckBuilder.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) App Metrics Contributors. All rights reserved. 3 | // 4 | 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Threading; 8 | using System.Threading.Tasks; 9 | 10 | namespace App.Metrics.Health.Builder 11 | { 12 | public class HealthCheckBuilder : IHealthCheckBuilder 13 | { 14 | private readonly Action _healthCheck; 15 | 16 | internal HealthCheckBuilder( 17 | IHealthBuilder healthBuilder, 18 | Action healthCheck) 19 | { 20 | Builder = healthBuilder ?? throw new ArgumentNullException(nameof(healthBuilder)); 21 | _healthCheck = healthCheck ?? throw new ArgumentNullException(nameof(healthCheck)); 22 | } 23 | 24 | /// 25 | public IHealthBuilder Builder { get; } 26 | 27 | /// 28 | public IHealthBuilder AddChecks(IEnumerable checks) 29 | { 30 | Register(checks); 31 | 32 | return Builder; 33 | } 34 | 35 | /// 36 | public IHealthBuilder AddCheck(THealthCheck check) 37 | where THealthCheck : HealthCheck 38 | { 39 | Register(check); 40 | 41 | return Builder; 42 | } 43 | 44 | /// 45 | public IHealthBuilder AddCheck() 46 | where THealthCheck : HealthCheck, new() 47 | { 48 | var check = new THealthCheck(); 49 | 50 | Register(check); 51 | 52 | return Builder; 53 | } 54 | 55 | /// 56 | public IHealthBuilder AddCheck(string name, Func> check) 57 | { 58 | Register(new HealthCheck(name, check)); 59 | 60 | return Builder; 61 | } 62 | 63 | /// 64 | public IHealthBuilder AddCheck(string name, Func> check) 65 | { 66 | Register(new HealthCheck(name, check)); 67 | 68 | return Builder; 69 | } 70 | 71 | internal void Register(IEnumerable healthChecks) 72 | { 73 | foreach (var check in healthChecks) 74 | { 75 | _healthCheck(check); 76 | } 77 | } 78 | 79 | internal void Register(HealthCheck healthCheck) 80 | { 81 | _healthCheck(healthCheck); 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /src/App.Metrics.Health.Core/Builder/HealthOutputFormattingBuilder.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) App Metrics Contributors. All rights reserved. 3 | // 4 | 5 | using System; 6 | using App.Metrics.Health.Formatters; 7 | 8 | // ReSharper disable CheckNamespace 9 | namespace App.Metrics.Health 10 | // ReSharper restore CheckNamespace 11 | { 12 | /// 13 | /// Builder for configuring s used for formatting s when 14 | /// they are reported. 15 | /// 16 | public class HealthOutputFormattingBuilder : IHealthOutputFormattingBuilder 17 | { 18 | private readonly Action _metricsFormatter; 19 | 20 | internal HealthOutputFormattingBuilder( 21 | IHealthBuilder healthBuilder, 22 | Action metricsFormatter) 23 | { 24 | Builder = healthBuilder ?? throw new ArgumentNullException(nameof(healthBuilder)); 25 | _metricsFormatter = metricsFormatter ?? throw new ArgumentNullException(nameof(metricsFormatter)); 26 | } 27 | 28 | /// 29 | public IHealthBuilder Builder { get; } 30 | 31 | /// 32 | public IHealthBuilder Using(IHealthOutputFormatter formatter) 33 | { 34 | if (formatter == null) 35 | { 36 | throw new ArgumentNullException(nameof(formatter)); 37 | } 38 | 39 | _metricsFormatter(true, formatter); 40 | 41 | return Builder; 42 | } 43 | 44 | /// 45 | public IHealthBuilder Using() 46 | where TMetricsOutputFormatter : IHealthOutputFormatter, new() 47 | { 48 | _metricsFormatter(true, new TMetricsOutputFormatter()); 49 | 50 | return Builder; 51 | } 52 | 53 | /// 54 | public IHealthBuilder Using(IHealthOutputFormatter formatter, bool replaceExisting) 55 | { 56 | if (formatter == null) 57 | { 58 | throw new ArgumentNullException(nameof(formatter)); 59 | } 60 | 61 | _metricsFormatter(replaceExisting, formatter); 62 | 63 | return Builder; 64 | } 65 | 66 | /// 67 | public IHealthBuilder Using(bool replaceExisting) 68 | where THealthsOutputFormatter : IHealthOutputFormatter, new() 69 | { 70 | _metricsFormatter(replaceExisting, new THealthsOutputFormatter()); 71 | 72 | return Builder; 73 | } 74 | } 75 | } -------------------------------------------------------------------------------- /test/App.Metrics.Health.Facts/Builders/HealthBuilderTests.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) App Metrics Contributors. All rights reserved. 3 | // 4 | 5 | using System.Linq; 6 | using System.Threading.Tasks; 7 | using App.Metrics.Health.Builder; 8 | using App.Metrics.Health.Formatters.Ascii; 9 | using App.Metrics.Health.Internal.NoOp; 10 | using FluentAssertions; 11 | using Xunit; 12 | 13 | namespace App.Metrics.Health.Facts.Builders 14 | { 15 | public class HealthBuilderTests 16 | { 17 | [Fact] 18 | public void When_no_checks_registered_use_noop_runner() 19 | { 20 | // Arrange 21 | var builder = new HealthBuilder(); 22 | 23 | // Act 24 | var health = builder.OutputHealth.AsPlainText().Build(); 25 | 26 | // Assert 27 | health.HealthCheckRunner.Should().BeOfType(); 28 | } 29 | 30 | [Fact] 31 | public void When_checks_registered_should_not_use_noop_runner() 32 | { 33 | // Arrange 34 | var builder = new HealthBuilder(); 35 | 36 | // Act 37 | var health = builder.HealthChecks.AddCheck("test", () => new ValueTask(HealthCheckResult.Healthy())).Build(); 38 | 39 | // Assert 40 | health.HealthCheckRunner.Should().NotBeOfType(); 41 | } 42 | 43 | [Fact] 44 | public void Options_should_not_be_null_when_configuration_not_used() 45 | { 46 | // Arrange 47 | var builder = new HealthBuilder(); 48 | 49 | // Act 50 | var health = builder.Build(); 51 | 52 | // Assert 53 | health.Options.Should().NotBeNull(); 54 | } 55 | 56 | [Fact] 57 | public void Formatter_should_default_to_text_if_not_configured() 58 | { 59 | // Arrange 60 | var builder = new HealthBuilder(); 61 | 62 | // Act 63 | var health = builder.Build(); 64 | 65 | // Assert 66 | health.OutputHealthFormatters.Should().NotBeNull(); 67 | health.OutputHealthFormatters.Single().Should().BeOfType(); 68 | } 69 | 70 | [Fact] 71 | public void Default_formatter_should_default_to_text_if_not_configured() 72 | { 73 | // Arrange 74 | var builder = new HealthBuilder(); 75 | 76 | // Act 77 | var health = builder.Build(); 78 | 79 | // Assert 80 | health.DefaultOutputHealthFormatter.Should().NotBeNull(); 81 | health.DefaultOutputHealthFormatter.Should().BeOfType(); 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/App.Metrics.Health.Abstractions/Formatters/HealthMediaTypeValue.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) App Metrics Contributors. All rights reserved. 3 | // 4 | 5 | using System; 6 | 7 | namespace App.Metrics.Health.Formatters 8 | { 9 | public struct HealthMediaTypeValue 10 | { 11 | public HealthMediaTypeValue(string type, string subType, string version, string format) 12 | { 13 | if (string.IsNullOrWhiteSpace(type)) 14 | { 15 | throw new ArgumentException(nameof(type)); 16 | } 17 | 18 | if (string.IsNullOrWhiteSpace(subType)) 19 | { 20 | throw new ArgumentException(nameof(subType)); 21 | } 22 | 23 | if (string.IsNullOrWhiteSpace(version)) 24 | { 25 | throw new ArgumentException(nameof(version)); 26 | } 27 | 28 | if (string.IsNullOrWhiteSpace(format)) 29 | { 30 | throw new ArgumentException(nameof(format)); 31 | } 32 | 33 | Type = type; 34 | SubType = subType; 35 | Format = format; 36 | Version = version; 37 | } 38 | 39 | public string ContentType => $"{Type}/{Format}"; 40 | 41 | public string Format { get; } 42 | 43 | public string SubType { get; } 44 | 45 | public string Type { get; } 46 | 47 | public string Version { get; } 48 | 49 | public static bool operator ==(HealthMediaTypeValue left, HealthMediaTypeValue right) { return left.Equals(right); } 50 | 51 | public static bool operator !=(HealthMediaTypeValue left, HealthMediaTypeValue right) { return !left.Equals(right); } 52 | 53 | public override bool Equals(object obj) 54 | { 55 | if (ReferenceEquals(null, obj)) 56 | { 57 | return false; 58 | } 59 | 60 | return obj is HealthMediaTypeValue && Equals((HealthMediaTypeValue)obj); 61 | } 62 | 63 | public override int GetHashCode() 64 | { 65 | unchecked 66 | { 67 | var hashCode = Format.GetHashCode(); 68 | hashCode = (hashCode * 397) ^ Type.GetHashCode(); 69 | hashCode = (hashCode * 397) ^ SubType.GetHashCode(); 70 | hashCode = (hashCode * 397) ^ Version.GetHashCode(); 71 | return hashCode; 72 | } 73 | } 74 | 75 | public override string ToString() { return $"{Type}/{SubType}-{Version}+{Format}"; } 76 | 77 | public bool Equals(HealthMediaTypeValue other) 78 | { 79 | return string.Equals(Format, other.Format) && string.Equals(Type, other.Type) && string.Equals(SubType, other.SubType) && 80 | string.Equals(Version, other.Version); 81 | } 82 | } 83 | } -------------------------------------------------------------------------------- /test/App.Metrics.Health.Facts/Builders/HealthConfigurationBuilderTests.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) App Metrics Contributors. All rights reserved. 3 | // 4 | 5 | using System.Collections.Generic; 6 | using App.Metrics.Health.Builder; 7 | using FluentAssertions; 8 | using Xunit; 9 | 10 | namespace App.Metrics.Health.Facts.Builders 11 | { 12 | public class HealthConfigurationBuilderTests 13 | { 14 | [Fact] 15 | public void Can_override_option_instance_values_with_key_value_pair_options() 16 | { 17 | // Arrange 18 | var options = new HealthOptions 19 | { 20 | Enabled = true, 21 | }; 22 | 23 | var keyValuePairs = new Dictionary 24 | { 25 | { "HealthOptions:Enabled", "false" } 26 | }; 27 | 28 | // Act 29 | var health = new HealthBuilder().Configuration.Configure(options, keyValuePairs).Build(); 30 | 31 | // Assert 32 | health.Options.Enabled.Should().BeFalse(); 33 | } 34 | 35 | [Fact] 36 | public void Can_set_options_via_key_value_pairs() 37 | { 38 | // Arrange 39 | var keyValuePairs = new Dictionary 40 | { 41 | { "HealthOptions:Enabled", "false" }, 42 | { "HealthOptions:ReportingEnabled", "false" }, 43 | { "HealthOptions:ApplicationName", "test_app" } 44 | }; 45 | 46 | // Act 47 | var health = new HealthBuilder().Configuration.Configure(keyValuePairs).Build(); 48 | 49 | // Assert 50 | health.Options.Enabled.Should().BeFalse(); 51 | health.Options.ReportingEnabled.Should().BeFalse(); 52 | health.Options.ApplicationName.Should().Be("test_app"); 53 | } 54 | 55 | [Fact] 56 | public void Can_set_options_via_setup_action() 57 | { 58 | // Arrange 59 | void SetupAction(HealthOptions options) 60 | { 61 | options.Enabled = false; 62 | } 63 | 64 | // Act 65 | var health = new HealthBuilder().Configuration.Configure(SetupAction).Build(); 66 | 67 | // Assert 68 | health.Options.Enabled.Should().BeFalse(); 69 | } 70 | 71 | [Fact] 72 | public void Can_set_options_with_instance() 73 | { 74 | // Arrange 75 | var options = new HealthOptions { Enabled = true }; 76 | 77 | // Act 78 | var health = new HealthBuilder().Configuration.Configure(options).Build(); 79 | 80 | // Assert 81 | health.Options.Enabled.Should().BeTrue(); 82 | } 83 | } 84 | } -------------------------------------------------------------------------------- /src/App.Metrics.Health.Abstractions/Formatters/HealthFormatterCollection.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) App Metrics Contributors. All rights reserved. 3 | // 4 | 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Collections.ObjectModel; 8 | 9 | namespace App.Metrics.Health.Formatters 10 | { 11 | public class HealthFormatterCollection : Collection 12 | { 13 | public HealthFormatterCollection() { } 14 | 15 | public HealthFormatterCollection(IList list) 16 | : base(list) 17 | { 18 | } 19 | 20 | public IHealthOutputFormatter GetType() 21 | where T : IHealthOutputFormatter 22 | { 23 | return GetType(typeof(T)); 24 | } 25 | 26 | public IHealthOutputFormatter GetType(Type formatterType) 27 | { 28 | for (var i = Count - 1; i >= 0; i--) 29 | { 30 | var formatter = this[i]; 31 | if (formatter.GetType() == formatterType) 32 | { 33 | return formatter; 34 | } 35 | } 36 | 37 | return default; 38 | } 39 | 40 | public IHealthOutputFormatter GetType(HealthMediaTypeValue mediaTypeValue) 41 | { 42 | for (var i = Count - 1; i >= 0; i--) 43 | { 44 | var formatter = this[i]; 45 | if (formatter.MediaType == mediaTypeValue) 46 | { 47 | return formatter; 48 | } 49 | } 50 | 51 | return default; 52 | } 53 | 54 | public void RemoveType() 55 | where T : IHealthOutputFormatter 56 | { 57 | RemoveType(typeof(T)); 58 | } 59 | 60 | public void RemoveType(Type formatterType) 61 | { 62 | for (var i = Count - 1; i >= 0; i--) 63 | { 64 | var formatter = this[i]; 65 | if (formatter.GetType() == formatterType) 66 | { 67 | RemoveAt(i); 68 | } 69 | } 70 | } 71 | 72 | public void RemoveType(HealthMediaTypeValue mediaTypeValue) 73 | { 74 | for (var i = Count - 1; i >= 0; i--) 75 | { 76 | var formatter = this[i]; 77 | if (formatter.MediaType == mediaTypeValue) 78 | { 79 | RemoveAt(i); 80 | } 81 | } 82 | } 83 | 84 | public void TryAdd(IHealthOutputFormatter formatter) 85 | where TFormatter : IHealthOutputFormatter 86 | { 87 | RemoveType(); 88 | Add(formatter); 89 | } 90 | 91 | public void TryAdd(IHealthOutputFormatter formatter) 92 | { 93 | RemoveType(formatter.GetType()); 94 | Add(formatter); 95 | } 96 | } 97 | } -------------------------------------------------------------------------------- /src/App.Metrics.Health.Core/Internal/Extensions/AppMetricsHealthLoggerExtensions.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) App Metrics Contributors. All rights reserved. 3 | // 4 | 5 | using System; 6 | using System.Diagnostics; 7 | using System.Diagnostics.CodeAnalysis; 8 | using System.Linq; 9 | 10 | // ReSharper disable CheckNamespace 11 | namespace App.Metrics.Health.Logging 12 | // ReSharper restore CheckNamespace 13 | { 14 | [ExcludeFromCodeCoverage] 15 | internal static class AppMetricsHealthLoggerExtensions 16 | { 17 | internal static void HealthCheckGetStatusExecuted( 18 | this ILog logger, 19 | HealthStatus healthStatus, 20 | long startTimestamp) 21 | { 22 | if (!logger.IsTraceEnabled()) 23 | { 24 | return; 25 | } 26 | 27 | if (startTimestamp == 0) 28 | { 29 | return; 30 | } 31 | 32 | var currentTimestamp = Stopwatch.GetTimestamp(); 33 | var elapsed = new TimeSpan((long)(TimestampToTicks * (currentTimestamp - startTimestamp))); 34 | 35 | if (healthStatus.Results.Any()) 36 | { 37 | if (healthStatus.Status.IsHealthy()) 38 | { 39 | logger.Debug( 40 | "Executed HealthStatus, in {ElapsedMilliseconds}ms, IsHealthy: True. {ChecksPassed} health check results passed.", 41 | elapsed.TotalMilliseconds, 42 | healthStatus.Results.Count()); 43 | return; 44 | } 45 | 46 | var checksFailed = healthStatus.Results.Count(x => x.Check.Status.IsUnhealthy()); 47 | var checksDegraded = healthStatus.Results.Count(x => x.Check.Status.IsDegraded()); 48 | var checksPassed = healthStatus.Results.Count(x => x.Check.Status.IsHealthy()); 49 | var failedChecks = healthStatus.Results.Where(h => h.Check.Status.IsUnhealthy()).Select(h => h.Name); 50 | var degradedChecks = healthStatus.Results.Where(h => h.Check.Status.IsDegraded()).Select(h => h.Name); 51 | 52 | logger.Debug( 53 | "Executed HealthStatus, in {ElapsedMilliseconds}ms, IsHealthy: False. {ChecksPassed} health check results passed. {ChecksFailed} health check results failed. Failed Checks: {FailedChecks}. {ChecksDegraded} health check results degredated. Degraded Checks: {DegredatedChecks}", 54 | elapsed.TotalMilliseconds, 55 | checksFailed, 56 | checksDegraded, 57 | checksPassed, 58 | degradedChecks, 59 | failedChecks); 60 | 61 | return; 62 | } 63 | 64 | logger.Debug("Executed HealthStatus, 0 health check results."); 65 | } 66 | 67 | internal static void HealthCheckGetStatusExecuting(this ILog logger) 68 | { 69 | logger.Trace("Executing HealthCheck Get Status"); 70 | } 71 | 72 | private static readonly double TimestampToTicks = TimeSpan.TicksPerSecond / (double)Stopwatch.Frequency; 73 | } 74 | } -------------------------------------------------------------------------------- /src/App.Metrics.Health.Core/Internal/KeyValuePairHealthOptions.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) App Metrics Contributors. All rights reserved. 3 | // 4 | 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Linq; 8 | 9 | namespace App.Metrics.Health.Internal 10 | { 11 | internal class KeyValuePairHealthOptions 12 | { 13 | internal static readonly string EnabledDirective = $"{nameof(HealthOptions)}:{nameof(HealthOptions.Enabled)}"; 14 | internal static readonly string ApplicationNameDirective = $"{nameof(HealthOptions)}:{nameof(HealthOptions.ApplicationName)}"; 15 | internal static readonly string ReportingEnabledDirective = $"{nameof(HealthOptions)}:{nameof(HealthOptions.ReportingEnabled)}"; 16 | 17 | private readonly HealthOptions _options; 18 | 19 | private readonly Dictionary _optionValues; 20 | 21 | public KeyValuePairHealthOptions(HealthOptions options, IEnumerable> optionValues) 22 | { 23 | if (optionValues == null) 24 | { 25 | throw new ArgumentNullException(nameof(optionValues)); 26 | } 27 | 28 | _options = options; 29 | _optionValues = optionValues.ToDictionary(o => o.Key, o => o.Value); 30 | } 31 | 32 | public KeyValuePairHealthOptions(IEnumerable> optionValues) 33 | { 34 | if (optionValues == null) 35 | { 36 | throw new ArgumentNullException(nameof(optionValues)); 37 | } 38 | 39 | _optionValues = optionValues.ToDictionary(o => o.Key, o => o.Value); 40 | } 41 | 42 | public HealthOptions AsOptions() 43 | { 44 | var options = _options ?? new HealthOptions(); 45 | 46 | foreach (var key in _optionValues.Keys) 47 | { 48 | if (string.Compare(key, EnabledDirective, StringComparison.CurrentCultureIgnoreCase) == 0) 49 | { 50 | if (!bool.TryParse(_optionValues[key], out var metricsEnabled)) 51 | { 52 | throw new InvalidCastException($"Attempted to bind {key} to {EnabledDirective} but it's not a boolean"); 53 | } 54 | 55 | options.Enabled = metricsEnabled; 56 | } 57 | else if (string.Compare(key, ApplicationNameDirective, StringComparison.CurrentCultureIgnoreCase) == 0) 58 | { 59 | options.ApplicationName = _optionValues[key]; 60 | } 61 | else if (string.Compare(key, ReportingEnabledDirective, StringComparison.CurrentCultureIgnoreCase) == 0) 62 | { 63 | if (!bool.TryParse(_optionValues[key], out var reportingEnabled)) 64 | { 65 | throw new InvalidCastException($"Attempted to bind {key} to {ReportingEnabledDirective} but it's not a boolean"); 66 | } 67 | 68 | options.ReportingEnabled = reportingEnabled; 69 | } 70 | } 71 | 72 | return options; 73 | } 74 | } 75 | } -------------------------------------------------------------------------------- /src/App.Metrics.Health.Formatters.Json/Converters/HealthStatusConverter.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) App Metrics Contributors. All rights reserved. 3 | // 4 | 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Linq; 8 | using Newtonsoft.Json; 9 | 10 | namespace App.Metrics.Health.Formatters.Json.Converters 11 | { 12 | public class HealthStatusConverter : JsonConverter 13 | { 14 | public override bool CanConvert(Type objectType) { return typeof(HealthStatus) == objectType; } 15 | 16 | public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) 17 | { 18 | var source = serializer.Deserialize(reader); 19 | var healthy = source.Healthy.Keys.Select(k => new HealthCheck.Result(k, HealthCheckResult.Healthy(source.Healthy[k]))); 20 | var unhealthy = source.Unhealthy.Keys.Select(k => new HealthCheck.Result(k, HealthCheckResult.Unhealthy(source.Unhealthy[k]))); 21 | var degraded = source.Degraded.Keys.Select(k => new HealthCheck.Result(k, HealthCheckResult.Degraded(source.Degraded[k]))); 22 | var ignored = source.Ignored.Keys.Select(k => new HealthCheck.Result(k, HealthCheckResult.Ignore(source.Ignored[k]))); 23 | var target = new HealthStatus(healthy.Concat(unhealthy).Concat(degraded).Concat(ignored)); 24 | return target; 25 | } 26 | 27 | public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) 28 | { 29 | var source = (HealthStatus)value; 30 | 31 | var target = new HealthStatusData 32 | { 33 | Status = HealthConstants.HealthStatusDisplay[source.Status] 34 | }; 35 | 36 | var healthy = source.Results.Where(r => r.Check.Status.IsHealthy()) 37 | .Select(c => new KeyValuePair(c.Name, CheckMessage(c))) 38 | .ToDictionary(pair => pair.Key, pair => pair.Value); 39 | 40 | var unhealthy = source.Results.Where(r => r.Check.Status.IsUnhealthy()) 41 | .Select(c => new KeyValuePair(c.Name, CheckMessage(c))) 42 | .ToDictionary(pair => pair.Key, pair => pair.Value); 43 | 44 | var degraded = source.Results.Where(r => r.Check.Status.IsDegraded()) 45 | .Select(c => new KeyValuePair(c.Name, CheckMessage(c))) 46 | .ToDictionary(pair => pair.Key, pair => pair.Value); 47 | 48 | var ignored = source.Results.Where(r => r.Check.Status.IsIgnored()) 49 | .Select(c => new KeyValuePair(c.Name, CheckMessage(c))) 50 | .ToDictionary(pair => pair.Key, pair => pair.Value); 51 | 52 | target.Healthy = healthy.Any() ? healthy : null; 53 | target.Unhealthy = unhealthy.Any() ? unhealthy : null; 54 | target.Degraded = degraded.Any() ? degraded : null; 55 | target.Ignored = ignored.Any() ? ignored : null; 56 | 57 | serializer.Serialize(writer, target); 58 | } 59 | 60 | private static string CheckMessage(HealthCheck.Result c) 61 | { 62 | return c.IsFromCache ? $"[Cached] {c.Check.Message}" : c.Check.Message; 63 | } 64 | } 65 | } -------------------------------------------------------------------------------- /test/App.Metrics.Health.Facts/HealthCheckRunnerTests.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) App Metrics Contributors. All rights reserved. 3 | // 4 | 5 | using System.Linq; 6 | using System.Threading.Tasks; 7 | using App.Metrics.Health.Internal; 8 | using FluentAssertions; 9 | using Xunit; 10 | 11 | namespace App.Metrics.Health.Facts 12 | { 13 | public class HealthCheckRunnerTests 14 | { 15 | [Fact] 16 | public async Task Status_is_degraded_if_one_check_is_degraded() 17 | { 18 | // Arrange 19 | var checks = new[] 20 | { 21 | new HealthCheck("ok", () => new ValueTask(HealthCheckResult.Healthy())), 22 | new HealthCheck("degraded", () => new ValueTask(HealthCheckResult.Degraded())) 23 | }; 24 | var runner = new DefaultHealthCheckRunner(checks); 25 | 26 | // Act 27 | var status = await runner.ReadAsync(); 28 | 29 | // Assert 30 | status.Status.Should().Be(HealthCheckStatus.Degraded); 31 | status.Results.Count().Should().Be(2); 32 | } 33 | 34 | [Fact] 35 | public async Task Status_is_failed_if_one_check_fails() 36 | { 37 | // Arrange 38 | var checks = new[] 39 | { 40 | new HealthCheck("ok", () => new ValueTask(HealthCheckResult.Healthy())), 41 | new HealthCheck("bad", () => new ValueTask(HealthCheckResult.Unhealthy())) 42 | }; 43 | var runner = new DefaultHealthCheckRunner(checks); 44 | 45 | // Act 46 | var status = await runner.ReadAsync(); 47 | 48 | // Assert 49 | status.Status.Should().Be(HealthCheckStatus.Unhealthy); 50 | status.Results.Count().Should().Be(2); 51 | } 52 | 53 | [Fact] 54 | public async Task Status_is_healthy_if_all_checks_are_healthy() 55 | { 56 | // Arrange 57 | var checks = new[] 58 | { 59 | new HealthCheck("ok", () => new ValueTask(HealthCheckResult.Healthy())), 60 | new HealthCheck("another", () => new ValueTask(HealthCheckResult.Healthy())) 61 | }; 62 | var runner = new DefaultHealthCheckRunner(checks); 63 | 64 | // Act 65 | var status = await runner.ReadAsync(); 66 | 67 | // Assert 68 | status.Status.Should().Be(HealthCheckStatus.Healthy); 69 | status.Results.Count().Should().Be(2); 70 | } 71 | 72 | [Fact] 73 | public async Task Status_is_unhealthy_if_any_one_check_fails_even_when_degraded() 74 | { 75 | // Arrange 76 | var checks = new[] 77 | { 78 | new HealthCheck("ok", () => new ValueTask(HealthCheckResult.Healthy())), 79 | new HealthCheck("bad", () => new ValueTask(HealthCheckResult.Unhealthy())), 80 | new HealthCheck("degraded", () => new ValueTask(HealthCheckResult.Degraded())) 81 | }; 82 | var runner = new DefaultHealthCheckRunner(checks); 83 | 84 | // Act 85 | var status = await runner.ReadAsync(); 86 | 87 | // Assert 88 | status.Status.Should().Be(HealthCheckStatus.Unhealthy); 89 | status.Results.Count().Should().Be(3); 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/App.Metrics.Health.Checks.Network/PingHealthCheckBuilderExtensions.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) App Metrics Contributors. All rights reserved. 3 | // 4 | 5 | using System; 6 | using System.Net.NetworkInformation; 7 | using System.Threading.Tasks; 8 | 9 | // ReSharper disable CheckNamespace 10 | namespace App.Metrics.Health 11 | // ReSharper restore CheckNamespace 12 | { 13 | public static class PingHealthCheckBuilderExtensions 14 | { 15 | public static IHealthBuilder AddPingCheck( 16 | this IHealthCheckBuilder healthCheckBuilder, 17 | string name, 18 | string host, 19 | TimeSpan timeout, 20 | bool degradedOnError = false) 21 | { 22 | healthCheckBuilder.AddCheck( 23 | name, 24 | async () => await ExecutePingCheckAsync(host, timeout, degradedOnError)); 25 | 26 | return healthCheckBuilder.Builder; 27 | } 28 | 29 | public static IHealthBuilder AddPingCheck( 30 | this IHealthCheckBuilder healthCheckBuilder, 31 | string name, 32 | string host, 33 | TimeSpan timeout, 34 | TimeSpan cacheDuration, 35 | bool degradedOnError = false) 36 | { 37 | healthCheckBuilder.AddCachedCheck( 38 | name, 39 | async () => await ExecutePingCheckAsync(host, timeout, degradedOnError), 40 | cacheDuration); 41 | 42 | return healthCheckBuilder.Builder; 43 | } 44 | 45 | public static IHealthBuilder AddPingCheck( 46 | this IHealthCheckBuilder healthCheckBuilder, 47 | string name, 48 | string host, 49 | TimeSpan timeout, 50 | HealthCheck.QuiteTime quiteTime, 51 | bool degradedOnError = false) 52 | { 53 | healthCheckBuilder.AddQuiteTimeCheck( 54 | name, 55 | async () => await ExecutePingCheckAsync(host, timeout, degradedOnError), 56 | quiteTime); 57 | 58 | return healthCheckBuilder.Builder; 59 | } 60 | 61 | private static async Task ExecutePingCheckAsync(string host, TimeSpan timeout, bool degradedOnError) 62 | { 63 | try 64 | { 65 | var ping = new Ping(); 66 | var result = await ping.SendPingAsync(host, (int)timeout.TotalMilliseconds).ConfigureAwait(false); 67 | 68 | return result.Status == IPStatus.Success 69 | ? HealthCheckResult.Healthy($"OK. {host}") 70 | : HealthCheckResultOnError($"FAILED. {host} ping result was {result.Status}", degradedOnError); 71 | } 72 | catch (Exception ex) 73 | { 74 | return degradedOnError 75 | ? HealthCheckResult.Degraded(ex) 76 | : HealthCheckResult.Unhealthy(ex); 77 | } 78 | } 79 | 80 | /// 81 | /// Create a failure (degraded or unhealthy) status response. 82 | /// 83 | /// Status message. 84 | /// 85 | /// If true, create a degraded status response. 86 | /// Otherwise create an unhealthy status response. (default: false) 87 | /// 88 | /// Failure status response. 89 | private static HealthCheckResult HealthCheckResultOnError(string message, bool degradedOnError) 90 | { 91 | return degradedOnError 92 | ? HealthCheckResult.Degraded(message) 93 | : HealthCheckResult.Unhealthy(message); 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/App.Metrics.Health.Abstractions/HealthCheckCached.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) App Metrics Contributors. All rights reserved. 3 | // 4 | 5 | using System; 6 | using System.Threading; 7 | using System.Threading.Tasks; 8 | using App.Metrics.Concurrency; 9 | 10 | namespace App.Metrics.Health 11 | { 12 | /// 13 | /// Cached Health Check - Allows caching of Health Check results given a duration to cache 14 | /// 15 | public partial class HealthCheck 16 | { 17 | private readonly TimeSpan _cacheDuration = TimeSpan.Zero; 18 | private Result _cachedResult; 19 | private AtomicLong _reCheckAt = new AtomicLong(0); 20 | 21 | /// 22 | /// Initializes a new instance of the class. 23 | /// 24 | /// A descriptive name for the health check. 25 | /// A function returning either a healthy or un-healthy result. 26 | /// The duration of which to cache the health check result. 27 | public HealthCheck( 28 | string name, 29 | Func> check, 30 | TimeSpan cacheDuration) 31 | { 32 | EnsureValidCacheDuration(cacheDuration); 33 | 34 | Name = name; 35 | 36 | ValueTask CheckWithToken(CancellationToken token) => check(); 37 | 38 | _check = CheckWithToken; 39 | _cacheDuration = cacheDuration; 40 | } 41 | 42 | /// 43 | /// Initializes a new instance of the class. 44 | /// 45 | /// A descriptive name for the health check. 46 | /// A function returning either a healthy or un-healthy result. 47 | /// The duration of which to cache the health check result. 48 | public HealthCheck( 49 | string name, 50 | Func> check, 51 | TimeSpan cacheDuration) 52 | { 53 | EnsureValidCacheDuration(cacheDuration); 54 | 55 | Name = name; 56 | 57 | ValueTask CheckWithToken(CancellationToken token) => check(token); 58 | 59 | _check = CheckWithToken; 60 | _cacheDuration = cacheDuration; 61 | } 62 | 63 | protected HealthCheck(string name, TimeSpan cacheDuration) 64 | { 65 | EnsureValidCacheDuration(cacheDuration); 66 | 67 | Name = name; 68 | _cacheDuration = cacheDuration; 69 | _check = token => new ValueTask(HealthCheckResult.Healthy()); 70 | } 71 | 72 | private static void EnsureValidCacheDuration(TimeSpan cacheDuration) 73 | { 74 | if (cacheDuration <= TimeSpan.Zero) 75 | { 76 | throw new ArgumentException("Must be greater than zero", nameof(cacheDuration)); 77 | } 78 | } 79 | 80 | private async Task ExecuteWithCachingAsync(CancellationToken cancellationToken) 81 | { 82 | if (_reCheckAt.GetValue() >= DateTime.UtcNow.Ticks) 83 | { 84 | return _cachedResult; 85 | } 86 | 87 | var checkResult = await CheckAsync(cancellationToken); 88 | _cachedResult = new Result(Name, checkResult, true); 89 | 90 | _reCheckAt.SetValue(DateTime.UtcNow.Ticks + _cacheDuration.Ticks); 91 | 92 | return new Result(Name, checkResult); 93 | } 94 | 95 | private bool HasCacheDuration() { return _cacheDuration > TimeSpan.Zero; } 96 | } 97 | } -------------------------------------------------------------------------------- /test/App.Metrics.Health.Formatters.Json.Facts/Helpers/TestHelperExtensions.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) App Metrics Contributors. All rights reserved. 3 | // 4 | 5 | using System.Collections.Generic; 6 | using System.IO; 7 | using System.Reflection; 8 | using Newtonsoft.Json; 9 | using Newtonsoft.Json.Linq; 10 | 11 | namespace App.Metrics.Health.Formatters.Json.Facts.Helpers 12 | { 13 | public enum HealthStatusSamples 14 | { 15 | Valid, 16 | NullHealthy, 17 | NullUnhealthy 18 | } 19 | 20 | public static class TestHelperExtensions 21 | { 22 | private static readonly Dictionary HealthStatusFileSampleMapping = new Dictionary 23 | { 24 | { 25 | HealthStatusSamples.Valid, 26 | "healthstatus" 27 | }, 28 | { 29 | HealthStatusSamples.NullHealthy, 30 | "healthstatus_null_healthy" 31 | }, 32 | { 33 | HealthStatusSamples.NullUnhealthy, 34 | "healthstatus_null_unhealthy" 35 | } 36 | }; 37 | 38 | public static JToken ParseAsJson(this string json) 39 | { 40 | return JToken.Parse(json, new JsonLoadSettings { LineInfoHandling = LineInfoHandling.Ignore, CommentHandling = CommentHandling.Ignore }); 41 | } 42 | 43 | public static JToken SampleJson(this HealthStatusSamples sample) { return sample.ExtractHealthStatusSampleFromResourceFile(); } 44 | 45 | private static JToken ExtractHealthStatusSampleFromResourceFile(this HealthStatusSamples sample) 46 | { 47 | return ExtractJsonFromEmbeddedResource(HealthStatusFileSampleMapping[sample]); 48 | } 49 | 50 | private static JToken ExtractJsonFromEmbeddedResource(string key) 51 | { 52 | var assemblyName = new AssemblyName("App.Metrics.Health.Formatters.Json.Facts"); 53 | using ( 54 | var fileStream = 55 | Assembly.Load(assemblyName).GetManifestResourceStream($"App.Metrics.Health.Formatters.Json.Facts.JsonFiles.{key}.json")) 56 | { 57 | if (fileStream == null) 58 | { 59 | return null; 60 | } 61 | 62 | using (var textReader = new StreamReader(fileStream)) 63 | { 64 | using (var jsonReader = new JsonTextReader(textReader)) 65 | { 66 | return JToken.ReadFrom(jsonReader); 67 | } 68 | } 69 | } 70 | } 71 | } 72 | } -------------------------------------------------------------------------------- /src/App.Metrics.Health.Reporting.Slack/Internal/JsonContent.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) App Metrics Contributors. All rights reserved. 3 | // 4 | 5 | using System.Net.Http; 6 | using System.Text; 7 | using Newtonsoft.Json; 8 | 9 | namespace App.Metrics.Health.Reporting.Slack.Internal 10 | { 11 | /// 12 | /// Provides HTTP content based on a json string. 13 | /// 14 | internal class JsonContent : StringContent 15 | { 16 | /// 17 | /// Initializes a new instance of the class. 18 | /// 19 | /// The content used to initialize the . 20 | public JsonContent(object content) 21 | : base(JsonConvert.SerializeObject(content, DefaultJsonSerializerSettings.Instance), Encoding.UTF8, "application/json") 22 | { 23 | } 24 | 25 | /// 26 | /// Initializes a new instance of the class. 27 | /// 28 | /// 29 | /// The content used to initialize the . 30 | /// 31 | /// The encoding to use for the content. 32 | public JsonContent(object content, Encoding encoding) 33 | : base(JsonConvert.SerializeObject(content, DefaultJsonSerializerSettings.Instance), encoding, "application/json") 34 | { 35 | } 36 | 37 | /// 38 | /// Initializes a new instance of the class. 39 | /// 40 | /// The content. 41 | /// The encoding. 42 | /// Type of the media. 43 | /// The JSON serializer setting to use 44 | public JsonContent(object content, Encoding encoding, string mediaType, JsonSerializerSettings setting) 45 | : base(JsonConvert.SerializeObject(content, setting), encoding, mediaType) 46 | { 47 | } 48 | 49 | /// 50 | /// Initializes a new instance of the class. 51 | /// 52 | /// The content used to initialize the . 53 | /// The JSON serializer setting to use 54 | public JsonContent(object content, JsonSerializerSettings setting) 55 | : base(JsonConvert.SerializeObject(content, setting), Encoding.UTF8, "application/json") 56 | { 57 | } 58 | 59 | /// 60 | /// Initializes a new instance of the class. 61 | /// 62 | /// 63 | /// The content used to initialize the . 64 | /// 65 | /// The encoding to use for the content. 66 | /// The JSON serializer setting to use 67 | public JsonContent(object content, Encoding encoding, JsonSerializerSettings setting) 68 | : base(JsonConvert.SerializeObject(content, setting), encoding, "application/json") 69 | { 70 | } 71 | 72 | /// 73 | /// Initializes a new instance of the class. 74 | /// 75 | /// The content. 76 | /// The encoding. 77 | /// Type of the media. 78 | public JsonContent(object content, Encoding encoding, string mediaType) 79 | : base(JsonConvert.SerializeObject(content, DefaultJsonSerializerSettings.Instance), encoding, mediaType) 80 | { 81 | } 82 | } 83 | } -------------------------------------------------------------------------------- /src/App.Metrics.Health.Reporting.Metrics/HealthResultsAsMetricsReporter.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) App Metrics Contributors. All rights reserved. 3 | // 4 | 5 | using System; 6 | using System.Threading; 7 | using System.Threading.Tasks; 8 | using App.Metrics.Health.Logging; 9 | #if !NETSTANDARD1_6 10 | using App.Metrics.Health.Internal; 11 | #endif 12 | using App.Metrics.Health.Reporting.Metrics.Internal; 13 | 14 | namespace App.Metrics.Health.Reporting.Metrics 15 | { 16 | public class HealthResultsAsMetricsReporter : IReportHealthStatus 17 | { 18 | private static readonly ILog Logger = LogProvider.For(); 19 | private readonly IMetrics _metrics; 20 | private readonly HealthAsMetricsOptions _healthAsMetricsOptions; 21 | 22 | public HealthResultsAsMetricsReporter(IMetrics metrics) 23 | : this(metrics, new HealthAsMetricsOptions()) 24 | { 25 | } 26 | 27 | public HealthResultsAsMetricsReporter(IMetrics metrics, HealthAsMetricsOptions healthAsMetricsOptions) 28 | { 29 | _healthAsMetricsOptions = healthAsMetricsOptions ?? throw new ArgumentNullException(nameof(healthAsMetricsOptions)); 30 | _metrics = metrics ?? throw new ArgumentNullException(nameof(metrics)); 31 | 32 | ReportInterval = healthAsMetricsOptions.ReportInterval > TimeSpan.Zero 33 | ? healthAsMetricsOptions.ReportInterval 34 | : HealthConstants.Reporting.DefaultReportInterval; 35 | 36 | Logger.Trace($"Using Metrics Reporter {this}. ReportInterval: {ReportInterval}"); 37 | } 38 | 39 | /// 40 | public TimeSpan ReportInterval { get; set; } 41 | 42 | /// 43 | public Task ReportAsync(HealthOptions options, HealthStatus status, CancellationToken cancellationToken = default) 44 | { 45 | if (!_healthAsMetricsOptions.Enabled || !options.Enabled) 46 | { 47 | Logger.Trace($"Health Status Reporter `{this}` disabled, not reporting."); 48 | 49 | #if NETSTANDARD1_6 50 | return Task.CompletedTask; 51 | #else 52 | return AppMetricsHealthTaskHelper.CompletedTask(); 53 | #endif 54 | } 55 | 56 | Logger.Trace($"Health Status Reporter `{this}` reporting health status."); 57 | 58 | foreach (var healthResult in status.Results) 59 | { 60 | var tags = new MetricTags(HealthReportingConstants.TagKeys.HealthCheckName, healthResult.Name); 61 | 62 | if (healthResult.Check.Status == HealthCheckStatus.Degraded) 63 | { 64 | _metrics.Measure.Gauge.SetValue(ApplicationHealthMetricRegistry.Checks, tags, HealthConstants.HealthScore.degraded); 65 | } 66 | else if (healthResult.Check.Status == HealthCheckStatus.Unhealthy) 67 | { 68 | _metrics.Measure.Gauge.SetValue(ApplicationHealthMetricRegistry.Checks, tags, HealthConstants.HealthScore.unhealthy); 69 | } 70 | else if (healthResult.Check.Status == HealthCheckStatus.Healthy) 71 | { 72 | _metrics.Measure.Gauge.SetValue(ApplicationHealthMetricRegistry.Checks, tags, HealthConstants.HealthScore.healthy); 73 | } 74 | } 75 | 76 | var overallHealthStatus = HealthConstants.HealthScore.healthy; 77 | 78 | if (status.Status == HealthCheckStatus.Unhealthy) 79 | { 80 | overallHealthStatus = HealthConstants.HealthScore.unhealthy; 81 | } 82 | else if (status.Status == HealthCheckStatus.Degraded) 83 | { 84 | overallHealthStatus = HealthConstants.HealthScore.degraded; 85 | } 86 | 87 | _metrics.Measure.Gauge.SetValue(ApplicationHealthMetricRegistry.HealthGauge, overallHealthStatus); 88 | 89 | Logger.Trace($"Health Status Reporter `{this}` successfully reported health status."); 90 | 91 | #if NETSTANDARD1_6 92 | return Task.CompletedTask; 93 | #else 94 | return AppMetricsHealthTaskHelper.CompletedTask(); 95 | #endif 96 | } 97 | } 98 | } -------------------------------------------------------------------------------- /src/App.Metrics.Health.Formatters.Ascii/HealthStatusTextWriter.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) App Metrics Contributors. All rights reserved. 3 | // 4 | 5 | using System; 6 | using System.Collections.Generic; 7 | using System.IO; 8 | using System.Linq; 9 | using App.Metrics.Health.Formatters.Ascii.Internal; 10 | using App.Metrics.Health.Serialization; 11 | 12 | namespace App.Metrics.Health.Formatters.Ascii 13 | { 14 | public class HealthStatusTextWriter : IHealthStatusWriter 15 | { 16 | private readonly int _padding; 17 | private readonly string _separator; 18 | private readonly TextWriter _textWriter; 19 | 20 | public HealthStatusTextWriter( 21 | TextWriter textWriter, 22 | string separator = HealthStatusFormatterConstants.OutputFormatting.Separator, 23 | int padding = HealthStatusFormatterConstants.OutputFormatting.Padding) 24 | { 25 | _textWriter = textWriter ?? throw new ArgumentNullException(nameof(textWriter)); 26 | 27 | _padding = padding; 28 | _separator = separator; 29 | } 30 | 31 | /// 32 | public void Dispose() 33 | { 34 | Dispose(true); 35 | GC.SuppressFinalize(this); 36 | } 37 | 38 | public void Write(HealthStatus healthStatus) 39 | { 40 | var status = GetOverallStatus(healthStatus.Results); 41 | 42 | _textWriter.Write($"# OVERALL STATUS: {status}"); 43 | _textWriter.Write("\n--------------------------------------------------------------\n"); 44 | 45 | foreach (var result in healthStatus.Results.OrderBy(r => (int)r.Check.Status)) 46 | { 47 | WriteCheckResult(result); 48 | } 49 | } 50 | 51 | /// 52 | /// Releases unmanaged and - optionally - managed resources. 53 | /// 54 | /// 55 | /// true to release both managed and unmanaged resources; false to release only 56 | /// unmanaged resources. 57 | /// 58 | protected virtual void Dispose(bool disposing) 59 | { 60 | if (disposing) 61 | { 62 | #if NET452 63 | _textWriter?.Close(); 64 | #endif 65 | _textWriter?.Dispose(); 66 | } 67 | } 68 | 69 | private string FormatReadable(string label, string value) 70 | { 71 | var pad = string.Empty; 72 | 73 | if (label.Length + 2 + _separator.Length < _padding) 74 | { 75 | pad = new string(' ', _padding - label.Length - 1 - _separator.Length); 76 | } 77 | 78 | return $"{pad}{label} {_separator} {value}"; 79 | } 80 | 81 | private string GetOverallStatus(IEnumerable results) 82 | { 83 | var status = HealthConstants.DegradedStatusDisplay; 84 | 85 | var enumerable = results as HealthCheck.Result[] ?? results.ToArray(); 86 | var failed = enumerable.Any(c => c.Check.Status == HealthCheckStatus.Unhealthy); 87 | var degraded = enumerable.Any(c => c.Check.Status == HealthCheckStatus.Degraded); 88 | 89 | if (!degraded && !failed) 90 | { 91 | status = HealthConstants.HealthyStatusDisplay; 92 | } 93 | 94 | if (failed) 95 | { 96 | status = HealthConstants.UnhealthyStatusDisplay; 97 | } 98 | 99 | return status; 100 | } 101 | 102 | private void WriteCheckResult(HealthCheck.Result checkResult) 103 | { 104 | _textWriter.Write("# CHECK: "); 105 | _textWriter.Write(checkResult.Name); 106 | _textWriter.Write('\n'); 107 | _textWriter.Write('\n'); 108 | _textWriter.Write(FormatReadable("MESSAGE", checkResult.IsFromCache ? $"[Cached] {checkResult.Check.Message}" : checkResult.Check.Message)); 109 | _textWriter.Write('\n'); 110 | _textWriter.Write(FormatReadable("STATUS", HealthConstants.HealthStatusDisplay[checkResult.Check.Status])); 111 | _textWriter.Write("\n--------------------------------------------------------------"); 112 | _textWriter.Write('\n'); 113 | } 114 | } 115 | } -------------------------------------------------------------------------------- /test/App.Metrics.Health.Facts/Builders/HealthCheckBuilderTests.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) App Metrics Contributors. All rights reserved. 3 | // 4 | 5 | using System; 6 | using System.Linq; 7 | using System.Threading.Tasks; 8 | using App.Metrics.Health.Builder; 9 | using App.Metrics.Health.Facts.TestHelpers; 10 | using FluentAssertions; 11 | using Xunit; 12 | 13 | namespace App.Metrics.Health.Facts.Builders 14 | { 15 | public class HealthCheckBuilderTests 16 | { 17 | private static readonly ValueTask HealthyResult = new ValueTask(HealthCheckResult.Healthy()); 18 | 19 | [Fact] 20 | public void Can_register_health_check_with_cancellation_token() 21 | { 22 | // Arrange 23 | const string check = "check with token"; 24 | var builder = new HealthBuilder(); 25 | 26 | // Act 27 | var health = builder.HealthChecks.AddCheck(check, token => new ValueTask(HealthCheckResult.Degraded())).Build(); 28 | 29 | // Assert 30 | health.Checks.Count().Should().Be(1); 31 | health.Checks.First(c => c.Name == check).Should().NotBeNull(); 32 | } 33 | 34 | [Fact] 35 | public void Can_register_health_check_without_cancellation_token() 36 | { 37 | // Arrange 38 | const string check = "check"; 39 | var builder = new HealthBuilder(); 40 | 41 | // Act 42 | var health = builder.HealthChecks.AddCheck(check, () => new ValueTask(HealthCheckResult.Degraded())).Build(); 43 | 44 | // Assert 45 | health.Checks.Count().Should().Be(1); 46 | health.Checks.First(c => c.Name == check).Should().NotBeNull(); 47 | } 48 | 49 | [Fact] 50 | public void Can_register_instance_health_checks() 51 | { 52 | // Arrange 53 | var builder = new HealthBuilder(); 54 | var check = new DatabaseHealthCheck(new Database()); 55 | 56 | // Act 57 | var health = builder.HealthChecks.AddCheck(check).Build(); 58 | 59 | // Assert 60 | health.Checks.Count().Should().Be(1); 61 | health.Checks.Single().Name.Should().Be("DatabaseCheck"); 62 | } 63 | 64 | [Fact] 65 | public void Can_register_type_health_checks() 66 | { 67 | // Arrange 68 | var builder = new HealthBuilder(); 69 | 70 | // Act 71 | var health = builder.HealthChecks.AddCheck().Build(); 72 | 73 | // Assert 74 | health.Checks.Count().Should().Be(1); 75 | health.Checks.Single().Name.Should().Be("SampleHealthCheck"); 76 | } 77 | 78 | [Fact] 79 | public void Can_register_mulitple_health_checks() 80 | { 81 | // Arrange 82 | var builder = new HealthBuilder(); 83 | var checks = new[] 84 | { 85 | new HealthCheck("first", () => HealthyResult), 86 | new HealthCheck("second", () => HealthyResult) 87 | }; 88 | 89 | // Act 90 | var health = builder.HealthChecks.AddChecks(checks).Build(); 91 | 92 | // Assert 93 | health.Checks.Count().Should().Be(2); 94 | } 95 | 96 | [Fact] 97 | public void Cannot_set_null_health_check() 98 | { 99 | // Arrange 100 | Action action = () => 101 | { 102 | // Act 103 | var unused = new HealthBuilder().HealthChecks.AddCheck(null, () => HealthyResult); 104 | }; 105 | 106 | // Assert 107 | action.Should().Throw(); 108 | } 109 | 110 | [Fact] 111 | public void Throws_on_duplicate_registration() 112 | { 113 | // Arrange 114 | var builder = new HealthBuilder().HealthChecks.AddCheck("test", () => HealthyResult); 115 | 116 | Action action = () => 117 | { 118 | // Act 119 | builder.HealthChecks.AddCheck("test", () => HealthyResult); 120 | var unused = builder.Build(); 121 | }; 122 | 123 | // Assert 124 | action.Should().Throw(); 125 | } 126 | } 127 | } -------------------------------------------------------------------------------- /src/App.Metrics.Health.Core/Builder/HealthConfigurationBuilder.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) App Metrics Contributors. All rights reserved. 3 | // 4 | 5 | using System; 6 | using System.Collections.Generic; 7 | using App.Metrics.Health.Internal; 8 | using App.Metrics.Health.Internal.Extensions; 9 | 10 | // ReSharper disable CheckNamespace 11 | namespace App.Metrics.Health 12 | // ReSharper restore CheckNamespace 13 | { 14 | /// 15 | /// Builder for configuring the . 16 | /// 17 | public class HealthConfigurationBuilder : IHealthConfigurationBuilder 18 | { 19 | private readonly Action _setupAction; 20 | private HealthOptions _options; 21 | 22 | internal HealthConfigurationBuilder( 23 | IHealthBuilder healthBuilder, 24 | HealthOptions currentOptions, 25 | Action setupAction) 26 | { 27 | Builder = healthBuilder ?? throw new ArgumentNullException(nameof(healthBuilder)); 28 | _setupAction = setupAction ?? throw new ArgumentNullException(nameof(setupAction)); 29 | _options = currentOptions ?? new HealthOptions(); 30 | } 31 | 32 | /// 33 | public IHealthBuilder Builder { get; } 34 | 35 | /// 36 | public IHealthBuilder Configure(HealthOptions options) 37 | { 38 | if (options == null) 39 | { 40 | throw new ArgumentNullException(nameof(options)); 41 | } 42 | 43 | _setupAction(options); 44 | 45 | _options = options; 46 | 47 | return Builder; 48 | } 49 | 50 | /// 51 | public IHealthBuilder Configure(IEnumerable> optionValues) 52 | { 53 | if (optionValues == null) 54 | { 55 | throw new ArgumentNullException(nameof(optionValues)); 56 | } 57 | 58 | var mergedOptions = new KeyValuePairHealthOptions(_options, optionValues).AsOptions(); 59 | 60 | _setupAction(mergedOptions); 61 | 62 | _options = mergedOptions; 63 | 64 | return Builder; 65 | } 66 | 67 | /// 68 | public IHealthBuilder Configure(HealthOptions options, IEnumerable> optionValues) 69 | { 70 | if (options == null) 71 | { 72 | throw new ArgumentNullException(nameof(options)); 73 | } 74 | 75 | if (optionValues == null) 76 | { 77 | throw new ArgumentNullException(nameof(optionValues)); 78 | } 79 | 80 | _setupAction(new KeyValuePairHealthOptions(options, optionValues).AsOptions()); 81 | 82 | _options = options; 83 | 84 | return Builder; 85 | } 86 | 87 | /// 88 | public IHealthBuilder Configure(Action setupAction) 89 | { 90 | if (setupAction == null) 91 | { 92 | throw new ArgumentNullException(nameof(setupAction)); 93 | } 94 | 95 | setupAction(_options); 96 | 97 | _setupAction(_options); 98 | 99 | return Builder; 100 | } 101 | 102 | /// 103 | public IHealthBuilder Extend(IEnumerable> optionValues) 104 | { 105 | if (optionValues == null) 106 | { 107 | throw new ArgumentNullException(nameof(optionValues)); 108 | } 109 | 110 | var mergedOptions = new KeyValuePairHealthOptions(_options, optionValues).AsOptions(); 111 | 112 | _setupAction(mergedOptions); 113 | 114 | _options = mergedOptions; 115 | 116 | return Builder; 117 | } 118 | 119 | /// 120 | public IHealthBuilder Extend(HealthOptions options) 121 | { 122 | if (options == null) 123 | { 124 | throw new ArgumentNullException(nameof(options)); 125 | } 126 | 127 | var optionsValuesToExtend = options.ToKeyValue(); 128 | var extendedOptions = new KeyValuePairHealthOptions(_options, optionsValuesToExtend).AsOptions(); 129 | 130 | _setupAction(extendedOptions); 131 | 132 | _options = extendedOptions; 133 | 134 | return Builder; 135 | } 136 | } 137 | } -------------------------------------------------------------------------------- /src/App.Metrics.Health.Abstractions/Builder/IHealthConfigurationBuilder.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) App Metrics Contributors. All rights reserved. 3 | // 4 | 5 | using System; 6 | using System.Collections.Generic; 7 | 8 | // ReSharper disable CheckNamespace 9 | namespace App.Metrics.Health 10 | // ReSharper restore CheckNamespace 11 | { 12 | public interface IHealthConfigurationBuilder 13 | { 14 | /// 15 | /// Gets the where App Metrics Helath is configured. 16 | /// 17 | IHealthBuilder Builder { get; } 18 | 19 | /// 20 | /// Uses the specifed instance for App Metrics Health core configuration. 21 | /// 22 | /// An instance used to configure core App Metrics Health options. 23 | /// 24 | /// An that can be used to further configure App Metrics Health. 25 | /// 26 | IHealthBuilder Configure(HealthOptions options); 27 | 28 | /// 29 | /// 30 | /// Uses the specifed key value pairs to configure an instance for App Metrics Health core 31 | /// configuration. 32 | /// 33 | /// 34 | /// Keys match the s property names. 35 | /// 36 | /// 37 | /// Key value pairs for configuring App Metrics Health 38 | /// 39 | /// An that can be used to further configure App Metrics Health. 40 | /// 41 | IHealthBuilder Configure(IEnumerable> optionValues); 42 | 43 | /// 44 | /// 45 | /// Uses the specifed key value pairs to configure an instance for App Metrics Health core 46 | /// configuration. 47 | /// 48 | /// 49 | /// Keys match the s property names. Any make key will override the 50 | /// value configured. 51 | /// 52 | /// 53 | /// An instance used to configure core App Metrics Health options. 54 | /// Key value pairs for configuring App Metrics 55 | /// 56 | /// An that can be used to further configure App Metrics Health. 57 | /// 58 | IHealthBuilder Configure(HealthOptions options, IEnumerable> optionValues); 59 | 60 | /// 61 | /// 62 | /// Uses the specifed key value pairs to configure an instance for App Metrics Health core 63 | /// configuration. 64 | /// 65 | /// 66 | /// Keys match the s property names. Any make key will override the 67 | /// value configured. 68 | /// 69 | /// 70 | /// An setup action used to configure core App Metrics Health options. 71 | /// 72 | /// An that can be used to further configure App Metrics Health. 73 | /// 74 | IHealthBuilder Configure(Action setupAction); 75 | 76 | /// 77 | /// Merges the specifed instance with any previously configured options. 78 | /// 79 | /// An instance used to configure core App Metrics Health options. 80 | /// 81 | /// An that can be used to further configure App Metrics Health. 82 | /// 83 | IHealthBuilder Extend(HealthOptions options); 84 | 85 | /// 86 | /// Merges the specifed instance with any previously configured options. 87 | /// 88 | /// An used to configure core App Metrics Health options. 89 | /// 90 | /// An that can be used to further configure App Metrics Health. 91 | /// 92 | IHealthBuilder Extend(IEnumerable> optionValues); 93 | } 94 | } -------------------------------------------------------------------------------- /src/App.Metrics.Health.Abstractions/Builder/IHealthOutputFormattingBuilder.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) App Metrics Contributors. All rights reserved. 3 | // 4 | 5 | using App.Metrics.Health.Formatters; 6 | 7 | // ReSharper disable CheckNamespace 8 | namespace App.Metrics.Health 9 | // ReSharper restore CheckNamespace 10 | { 11 | public interface IHealthOutputFormattingBuilder 12 | { 13 | /// 14 | /// Gets the where App Metrics Health is configured. 15 | /// 16 | IHealthBuilder Builder { get; } 17 | 18 | /// 19 | /// 20 | /// Uses the specifed as one of the available formatters when reporting 21 | /// metric values. 22 | /// 23 | /// 24 | /// Multiple formatters can be used, in which case the 25 | /// will be set to the first configured formatter. 26 | /// 27 | /// 28 | /// An instance used to format metric values when reporting. 29 | /// 30 | /// An that can be used to further configure App Metrics. 31 | /// 32 | IHealthBuilder Using(IHealthOutputFormatter formatter); 33 | 34 | /// 35 | /// 36 | /// Uses the specifed as one of the available formatters when reporting 37 | /// metric values. 38 | /// 39 | /// 40 | /// Multiple formatters can be used, in which case the 41 | /// will be set to the first configured formatter. 42 | /// 43 | /// 44 | /// 45 | /// An type used to format metric values 46 | /// when reporting. 47 | /// 48 | /// 49 | /// An that can be used to further configure App Metrics. 50 | /// 51 | IHealthBuilder Using() 52 | where THealthOutputFormatter : IHealthOutputFormatter, new(); 53 | 54 | /// 55 | /// 56 | /// Uses the specifed as one of the available formatters when reporting 57 | /// metric values. 58 | /// 59 | /// 60 | /// Multiple formatters can be used, in which case the 61 | /// will be set to the first configured formatter. 62 | /// 63 | /// 64 | /// An instance used to format metric values when reporting. 65 | /// 66 | /// If [true] replaces matching formatter type with the formatter instance, otherwise the 67 | /// existing formatter instance of matching type. 68 | /// 69 | /// 70 | /// An that can be used to further configure App Metrics. 71 | /// 72 | IHealthBuilder Using(IHealthOutputFormatter formatter, bool replaceExisting); 73 | 74 | /// 75 | /// 76 | /// Uses the specifed as one of the available formatters when reporting 77 | /// metric values. 78 | /// 79 | /// 80 | /// Multiple formatters can be used, in which case the 81 | /// will be set to the first configured formatter. 82 | /// 83 | /// 84 | /// 85 | /// An type used to format metric values 86 | /// when reporting. 87 | /// 88 | /// 89 | /// If [true] replaces matching formatter type with the formatter instance, otherwise the 90 | /// existing formatter instance of matching type. 91 | /// 92 | /// 93 | /// An that can be used to further configure App Metrics. 94 | /// 95 | IHealthBuilder Using(bool replaceExisting) 96 | where THealthOutputFormatter : IHealthOutputFormatter, new(); 97 | } 98 | } -------------------------------------------------------------------------------- /src/App.Metrics.Health.Core/Builder/HealthBuilder.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) App Metrics Contributors. All rights reserved. 3 | // 4 | 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Linq; 8 | using System.Reflection; 9 | using App.Metrics.Health.Formatters; 10 | using App.Metrics.Health.Formatters.Ascii; 11 | using App.Metrics.Health.Internal; 12 | using App.Metrics.Health.Internal.NoOp; 13 | using App.Metrics.Health.Logging; 14 | 15 | namespace App.Metrics.Health.Builder 16 | { 17 | public class HealthBuilder : IHealthBuilder 18 | { 19 | private static readonly ILog Logger = LogProvider.For(); 20 | private readonly Dictionary _checks = new Dictionary(StringComparer.OrdinalIgnoreCase); 21 | private readonly HealthFormatterCollection _healthFormatterCollection = new HealthFormatterCollection(); 22 | private readonly HealthReporterCollection _healthStatusReporters = new HealthReporterCollection(); 23 | private IHealthOutputFormatter _defaultMetricsHealthFormatter; 24 | private HealthOptions _options; 25 | 26 | /// 27 | public bool CanReport() { return _options.Enabled && _options.ReportingEnabled && _healthStatusReporters.Any(); } 28 | 29 | /// 30 | public IHealthConfigurationBuilder Configuration 31 | { 32 | get 33 | { 34 | return new HealthConfigurationBuilder( 35 | this, 36 | _options, 37 | options => { _options = options; }); 38 | } 39 | } 40 | 41 | public IHealthCheckBuilder HealthChecks 42 | { 43 | get 44 | { 45 | return new HealthCheckBuilder( 46 | this, 47 | healthCheck => 48 | { 49 | try 50 | { 51 | _checks.Add(healthCheck.Name, healthCheck); 52 | } 53 | catch (ArgumentException ex) 54 | { 55 | Logger.Error(ex, $"Attempted to add health checks with duplicates names: {healthCheck.Name}"); 56 | throw; 57 | } 58 | }); 59 | } 60 | } 61 | 62 | /// 63 | public IHealthOutputFormattingBuilder OutputHealth => new HealthOutputFormattingBuilder( 64 | this, 65 | (replaceExisting, formatter) => 66 | { 67 | if (_defaultMetricsHealthFormatter == null) 68 | { 69 | _defaultMetricsHealthFormatter = formatter; 70 | } 71 | 72 | if (replaceExisting) 73 | { 74 | _healthFormatterCollection.TryAdd(formatter); 75 | } 76 | else 77 | { 78 | if (_healthFormatterCollection.GetType(formatter.GetType()) == null) 79 | { 80 | _healthFormatterCollection.Add(formatter); 81 | } 82 | } 83 | }); 84 | 85 | /// 86 | public IHealthReportingBuilder Report => new HealthReportingBuilder( 87 | this, 88 | reporter => { _healthStatusReporters.TryAdd(reporter); }); 89 | 90 | public IHealthRoot Build() 91 | { 92 | if (_options == null) 93 | { 94 | _options = new HealthOptions(); 95 | } 96 | 97 | if (_healthFormatterCollection.Count == 0) 98 | { 99 | _healthFormatterCollection.Add(new HealthStatusTextOutputFormatter()); 100 | } 101 | 102 | IRunHealthChecks healthCheckRunner; 103 | 104 | var health = new DefaultHealth(_checks.Values); 105 | var defaultMetricsOutputFormatter = _defaultMetricsHealthFormatter ?? _healthFormatterCollection.FirstOrDefault(); 106 | 107 | if (_options.Enabled && health.Checks.Any()) 108 | { 109 | healthCheckRunner = new DefaultHealthCheckRunner(health.Checks); 110 | } 111 | else 112 | { 113 | healthCheckRunner = new NoOpHealthCheckRunner(); 114 | } 115 | 116 | if (string.IsNullOrWhiteSpace(_options.ApplicationName)) 117 | { 118 | var entryAssembly = Assembly.GetEntryAssembly(); 119 | 120 | _options.ApplicationName = entryAssembly?.GetName()?.Name?.Trim(); 121 | } 122 | 123 | return new HealthRoot( 124 | health, 125 | _options, 126 | _healthFormatterCollection, 127 | defaultMetricsOutputFormatter, 128 | healthCheckRunner, 129 | _healthStatusReporters); 130 | } 131 | } 132 | } -------------------------------------------------------------------------------- /benchmarks/App.Metrics.Health.Benchmarks/Support/BenchmarkTestExecutor.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) App Metrics Contributors. All rights reserved. 3 | // 4 | 5 | using System; 6 | using System.Linq; 7 | using BenchmarkDotNet.Columns; 8 | using BenchmarkDotNet.Configs; 9 | using BenchmarkDotNet.Loggers; 10 | using BenchmarkDotNet.Reports; 11 | using BenchmarkDotNet.Running; 12 | using Xunit; 13 | using Xunit.Abstractions; 14 | 15 | namespace App.Metrics.Health.Benchmarks.Support 16 | { 17 | // ReSharper disable UnusedMember.Global 18 | public class BenchmarkTestExecutor 19 | // ReSharper restore UnusedMember.Global 20 | { 21 | private readonly ITestOutputHelper _output; 22 | 23 | public BenchmarkTestExecutor() { } 24 | 25 | protected BenchmarkTestExecutor(ITestOutputHelper output) { _output = output; } 26 | 27 | /// 28 | /// Runs Benchmarks with the most simple config (SingleRunFastConfig) 29 | /// combined with any benchmark config applied to TBenchmark (via an attribute) 30 | /// By default will verify if every benchmark was successfully executed 31 | /// 32 | /// type that defines Benchmarks 33 | /// Optional custom config to be used instead of the default 34 | /// Optional: disable validation (default = true/enabled) 35 | /// The summary from the benchmark run 36 | // ReSharper disable UnusedMember.Global 37 | internal Summary CanExecute(IConfig config = null, bool fullValidation = true) 38 | // ReSharper restore UnusedMember.Global 39 | { 40 | return CanExecute(typeof(TBenchmark), config, fullValidation); 41 | } 42 | 43 | /// 44 | /// Runs Benchmarks with the most simple config (SingleRunFastConfig) 45 | /// combined with any benchmark config applied to Type (via an attribute) 46 | /// By default will verify if every benchmark was successfully executed 47 | /// 48 | /// type that defines Benchmarks 49 | /// Optional custom config to be used instead of the default 50 | /// Optional: disable validation (default = true/enabled) 51 | /// The summary from the benchmark run 52 | private Summary CanExecute(Type type, IConfig config = null, bool fullValidation = true) 53 | { 54 | // Add logging, so the Benchmark execution is in the TestRunner output (makes Debugging easier) 55 | if (config == null) 56 | { 57 | config = CreateSimpleConfig(); 58 | } 59 | 60 | if (!config.GetLoggers().OfType().Any()) 61 | { 62 | config = config.With(_output != null ? new OutputLogger(_output) : ConsoleLogger.Default); 63 | } 64 | 65 | if (!config.GetColumnProviders().Any()) 66 | { 67 | config = config.With(DefaultColumnProviders.Instance); 68 | } 69 | 70 | // Make sure we ALWAYS combine the Config (default or passed in) with any Config applied to the Type/Class 71 | var summary = BenchmarkRunner.Run(type, BenchmarkConverter.GetFullConfig(type, config)); 72 | 73 | if (!fullValidation) 74 | { 75 | return summary; 76 | } 77 | 78 | Assert.False(summary.HasCriticalValidationErrors, "The \"Summary\" should have NOT \"HasCriticalValidationErrors\""); 79 | 80 | Assert.True(summary.Reports.Any(), "The \"Summary\" should contain at least one \"BenchmarkReport\" in the \"Reports\" collection"); 81 | 82 | Assert.True( 83 | summary.Reports.All(r => r.BuildResult.IsBuildSuccess), 84 | "The following benchmarks are failed to build: " + string.Join(", ", summary.Reports.Where(r => !r.BuildResult.IsBuildSuccess).Select(r => r.BenchmarkCase.DisplayInfo))); 85 | 86 | Assert.True( 87 | summary.Reports.All(r => r.ExecuteResults.Any(er => er.FoundExecutable && er.Data.Any())), 88 | "All reports should have at least one \"ExecuteResult\" with \"FoundExecutable\" = true and at least one \"Data\" item"); 89 | 90 | Assert.True( 91 | summary.Reports.All(report => report.AllMeasurements.Any()), 92 | "All reports should have at least one \"Measurement\" in the \"AllMeasurements\" collection"); 93 | 94 | return summary; 95 | } 96 | 97 | private IConfig CreateSimpleConfig(OutputLogger logger = null) 98 | { 99 | return new SingleRunFastConfig() 100 | .With(logger ?? (_output != null ? new OutputLogger(_output) : ConsoleLogger.Default)) 101 | .With(DefaultColumnProviders.Instance); 102 | } 103 | } 104 | } --------------------------------------------------------------------------------