diff --git a/src/AzureFunctionHealthCheck/AzureFunctionHealthCheck-old/AzureFunctionHealthCheck.csproj b/src/AzureFunctionHealthCheck/AzureFunctionHealthCheck-old/AzureFunctionHealthCheck.csproj
new file mode 100644
index 0000000..a454366
--- /dev/null
+++ b/src/AzureFunctionHealthCheck/AzureFunctionHealthCheck-old/AzureFunctionHealthCheck.csproj
@@ -0,0 +1,13 @@
+
+
+
+ net5.0
+ TaleLearnCode.AzureFunctionHealthCheck
+ TaleLearnCode.AzureFunctionHealthCheck
+
+
+
+
+
+
+
diff --git a/src/AzureFunctionHealthCheck/AzureFunctionHealthCheck-old/DefaultHealthCheckService.cs b/src/AzureFunctionHealthCheck/AzureFunctionHealthCheck-old/DefaultHealthCheckService.cs
new file mode 100644
index 0000000..df3d646
--- /dev/null
+++ b/src/AzureFunctionHealthCheck/AzureFunctionHealthCheck-old/DefaultHealthCheckService.cs
@@ -0,0 +1,121 @@
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Diagnostics.HealthChecks;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace TaleLearnCode.AzureFunctionHealthCheck
+{
+
+ public class DefaultHealthCheckService : HealthCheckService
+ {
+
+ private readonly IServiceScopeFactory _serviceScopeFactory;
+ private readonly IOptions _options;
+ private readonly ILogger _logger;
+
+ public DefaultHealthCheckService(
+ IServiceScopeFactory serviceScopeFactory,
+ IOptions options,
+ ILogger logger)
+ {
+ _serviceScopeFactory = serviceScopeFactory ?? throw new ArgumentNullException(nameof(serviceScopeFactory));
+ _options = options ?? throw new ArgumentNullException(nameof(options));
+ _logger = logger ?? throw new ArgumentNullException(nameof(options));
+ ValidateRegistrations(_options.Value.Registrations);
+ }
+
+ ///
+ /// Performs the health check.
+ ///
+ /// The predicate.
+ /// The cancellation token.
+ ///
+ public override async Task CheckHealthAsync(
+ Func predicate,
+ CancellationToken cancellationToken = default)
+ {
+
+ ICollection registrations = _options.Value.Registrations;
+
+ using IServiceScope scope = _serviceScopeFactory.CreateScope();
+
+ HealthCheckContext healthCheckContext = new();
+ Dictionary entries = new(StringComparer.OrdinalIgnoreCase);
+
+ ValueStopwatch totalTime = ValueStopwatch.StartNew();
+ Log.HealthCheckProcessingBegin(_logger);
+
+ foreach (HealthCheckRegistration registration in registrations)
+ {
+ if (predicate != null && !predicate(registration))
+ continue;
+
+ cancellationToken.ThrowIfCancellationRequested();
+
+ IHealthCheck healthCheck = registration.Factory(scope.ServiceProvider);
+
+ ValueStopwatch stopwatch = ValueStopwatch.StartNew();
+ healthCheckContext.Registration = registration;
+
+ Log.HealthCheckBegin(_logger, registration);
+
+ HealthReportEntry entry;
+ try
+ {
+ HealthCheckResult result = await healthCheck.CheckHealthAsync(healthCheckContext, cancellationToken);
+ TimeSpan duration = stopwatch.Elapsed;
+
+ entry = new HealthReportEntry(
+ status: result.Status,
+ description: result.Description,
+ duration: duration,
+ exception: result.Exception,
+ data: result.Data);
+
+ Log.HealthCheckEnd(_logger, registration, entry, duration);
+ Log.HealthCheckData(_logger, registration, entry);
+ }
+ catch (Exception ex) when (ex as OperationCanceledException == null)
+ {
+ var duration = stopwatch.Elapsed;
+ entry = new HealthReportEntry(
+ status: HealthStatus.Unhealthy,
+ description: ex.Message,
+ duration: duration,
+ exception: ex,
+ data: null);
+
+ Log.HealthCheckError(_logger, registration, ex, duration);
+ }
+ entries[registration.Name] = entry;
+ }
+
+ var totalElapsedTime = totalTime.Elapsed;
+ var report = new HealthReport(entries, totalElapsedTime);
+ Log.HealthCheckProcessingEnd(_logger, report.Status, totalElapsedTime);
+ return report;
+
+ }
+
+ private static void ValidateRegistrations(IEnumerable registrations)
+ {
+
+ List duplicateNames = registrations
+ .GroupBy(c => c.Name, StringComparer.OrdinalIgnoreCase)
+ .Where(g => g.Count() > 1)
+ .Select(g => g.Key)
+ .ToList();
+
+ if (duplicateNames.Any())
+ throw new ArgumentException($"Duplicate health checks were registered with the name(s): {string.Join(", ", duplicateNames)}", nameof(registrations));
+
+ }
+
+ }
+
+}
\ No newline at end of file
diff --git a/src/AzureFunctionHealthCheck/AzureFunctionHealthCheck-old/EventIds.cs b/src/AzureFunctionHealthCheck/AzureFunctionHealthCheck-old/EventIds.cs
new file mode 100644
index 0000000..8d38517
--- /dev/null
+++ b/src/AzureFunctionHealthCheck/AzureFunctionHealthCheck-old/EventIds.cs
@@ -0,0 +1,14 @@
+using Microsoft.Extensions.Logging;
+
+namespace TaleLearnCode.AzureFunctionHealthCheck
+{
+ internal static class EventIds
+ {
+ public static readonly EventId HealthCheckProcessingBegin = new EventId(100, "HealthCheckProcessingBegin");
+ public static readonly EventId HealthCheckProcessingEnd = new EventId(101, "HealthCheckProcessingEnd");
+ public static readonly EventId HealthCheckBegin = new EventId(102, "HealthCheckBegin");
+ public static readonly EventId HealthCheckEnd = new EventId(103, "HealthCheckEnd");
+ public static readonly EventId HealthCheckError = new EventId(104, "HealthCheckError");
+ public static readonly EventId HealthCheckData = new EventId(105, "HealthCheckData");
+ }
+}
diff --git a/src/AzureFunctionHealthCheck/AzureFunctionHealthCheck-old/HealthCheckDataLogValue.cs b/src/AzureFunctionHealthCheck/AzureFunctionHealthCheck-old/HealthCheckDataLogValue.cs
new file mode 100644
index 0000000..dfd60fd
--- /dev/null
+++ b/src/AzureFunctionHealthCheck/AzureFunctionHealthCheck-old/HealthCheckDataLogValue.cs
@@ -0,0 +1,107 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace TaleLearnCode.AzureFunctionHealthCheck
+{
+
+ ///
+ /// Represents value of a health check data log.
+ ///
+ ///
+ internal class HealthCheckDataLogValue : IReadOnlyList>
+ {
+ private readonly string _name;
+ private readonly List> _values;
+ private string _formatted;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The name.
+ /// The values.
+ public HealthCheckDataLogValue(string name, IReadOnlyDictionary values)
+ {
+ _name = name;
+ _values = values.ToList();
+ _values.Add(new KeyValuePair("HealthCheckName", name));
+ }
+
+ ///
+ /// Gets the at the specified index.
+ ///
+ ///
+ /// The .
+ ///
+ /// The index.
+ ///
+ /// index
+ public KeyValuePair this[int index]
+ {
+ get
+ {
+ if (index < 0 || index >= Count)
+ throw new IndexOutOfRangeException(nameof(index));
+ return _values[index];
+ }
+ }
+
+ public int Count => _values.Count;
+
+ ///
+ /// Returns an enumerator that iterates through the collection.
+ ///
+ ///
+ /// An enumerator that can be used to iterate through the collection.
+ ///
+ public IEnumerator> GetEnumerator()
+ {
+ return _values.GetEnumerator();
+ }
+
+ ///
+ /// Returns an enumerator that iterates through a collection.
+ ///
+ ///
+ /// An object that can be used to iterate through the collection.
+ ///
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return _values.GetEnumerator();
+ }
+
+ ///
+ /// Converts the to a string.
+ ///
+ ///
+ /// A that represents this instance.
+ ///
+ public override string ToString()
+ {
+ if (_formatted == null)
+ {
+ var builder = new StringBuilder();
+ builder.AppendLine($"Health check data for {_name}:");
+
+ var values = _values;
+ for (var i = 0; i < values.Count; i++)
+ {
+ var kvp = values[i];
+ builder.Append(" ");
+ builder.Append(kvp.Key);
+ builder.Append(": ");
+
+ builder.AppendLine(kvp.Value?.ToString());
+ }
+
+ _formatted = builder.ToString();
+ }
+
+ return _formatted;
+ }
+
+ }
+
+}
\ No newline at end of file
diff --git a/src/AzureFunctionHealthCheck/AzureFunctionHealthCheck-old/HealthCheckServiceFunctionExtension.cs b/src/AzureFunctionHealthCheck/AzureFunctionHealthCheck-old/HealthCheckServiceFunctionExtension.cs
new file mode 100644
index 0000000..1ae47f0
--- /dev/null
+++ b/src/AzureFunctionHealthCheck/AzureFunctionHealthCheck-old/HealthCheckServiceFunctionExtension.cs
@@ -0,0 +1,30 @@
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.DependencyInjection.Extensions;
+using Microsoft.Extensions.Diagnostics.HealthChecks;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace TaleLearnCode.AzureFunctionHealthCheck
+{
+
+ ///
+ /// Provides extension methods for registering .
+ ///
+ public static class HealthCheckServiceFunctionExtension
+ {
+
+ ///
+ /// Adds the to the container, using the provided delegates to register.
+ ///
+ /// The services.
+ public static IHealthChecksBuilder AddFunctionHealthChecks(this IServiceCollection services)
+ {
+ services.TryAddSingleton();
+ return new HealthChecksBuilder(services);
+ }
+
+ }
+}
diff --git a/src/AzureFunctionHealthCheck/AzureFunctionHealthCheck-old/HealthChecksBuilder.cs b/src/AzureFunctionHealthCheck/AzureFunctionHealthCheck-old/HealthChecksBuilder.cs
new file mode 100644
index 0000000..ec9d0a3
--- /dev/null
+++ b/src/AzureFunctionHealthCheck/AzureFunctionHealthCheck-old/HealthChecksBuilder.cs
@@ -0,0 +1,47 @@
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Diagnostics.HealthChecks;
+using System;
+
+namespace TaleLearnCode.AzureFunctionHealthCheck
+{
+
+ ///
+ /// A builder used to register health checks.
+ ///
+ ///
+ public class HealthChecksBuilder : IHealthChecksBuilder
+ {
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The into which instances should be registered.
+ public HealthChecksBuilder(IServiceCollection services)
+ {
+ Services = services;
+ }
+
+ ///
+ /// Gets the into which instances should be registered.
+ ///
+ public IServiceCollection Services { get; }
+
+ ///
+ /// Adds a for a health check.
+ ///
+ /// The .
+ /// An initialized
+ /// registration
+ public IHealthChecksBuilder Add(HealthCheckRegistration registration)
+ {
+ if (registration == default) throw new ArgumentNullException(nameof(registration));
+ Services.Configure(options =>
+ {
+ options.Registrations.Add(registration);
+ });
+ return this;
+ }
+
+ }
+
+}
\ No newline at end of file
diff --git a/src/AzureFunctionHealthCheck/AzureFunctionHealthCheck-old/Log.cs b/src/AzureFunctionHealthCheck/AzureFunctionHealthCheck-old/Log.cs
new file mode 100644
index 0000000..503e6a8
--- /dev/null
+++ b/src/AzureFunctionHealthCheck/AzureFunctionHealthCheck-old/Log.cs
@@ -0,0 +1,105 @@
+using Microsoft.Extensions.Diagnostics.HealthChecks;
+using Microsoft.Extensions.Logging;
+using System;
+
+namespace TaleLearnCode.AzureFunctionHealthCheck
+{
+
+ internal static class Log
+ {
+ private static readonly Action _healthCheckProcessingBegin = LoggerMessage.Define(
+ LogLevel.Debug,
+ EventIds.HealthCheckProcessingBegin,
+ "Running health checks");
+
+ private static readonly Action _healthCheckProcessingEnd = LoggerMessage.Define(
+ LogLevel.Debug,
+ EventIds.HealthCheckProcessingEnd,
+ "Health check processing completed after {ElapsedMilliseconds}ms with combined status {HealthStatus}");
+
+ private static readonly Action _healthCheckBegin = LoggerMessage.Define(
+ LogLevel.Debug,
+ EventIds.HealthCheckBegin,
+ "Running health check {HealthCheckName}");
+
+ // These are separate so they can have different log levels
+ private static readonly string HealthCheckEndText = "Health check {HealthCheckName} completed after {ElapsedMilliseconds}ms with status {HealthStatus} and '{HealthCheckDescription}'";
+
+ private static readonly Action _healthCheckEndHealthy = LoggerMessage.Define(
+ LogLevel.Debug,
+ EventIds.HealthCheckEnd,
+ HealthCheckEndText);
+
+ private static readonly Action _healthCheckEndDegraded = LoggerMessage.Define(
+ LogLevel.Warning,
+ EventIds.HealthCheckEnd,
+ HealthCheckEndText);
+
+ private static readonly Action _healthCheckEndUnhealthy = LoggerMessage.Define(
+ LogLevel.Error,
+ EventIds.HealthCheckEnd,
+ HealthCheckEndText);
+
+ private static readonly Action _healthCheckEndFailed = LoggerMessage.Define(
+ LogLevel.Error,
+ EventIds.HealthCheckEnd,
+ HealthCheckEndText);
+
+ private static readonly Action _healthCheckError = LoggerMessage.Define(
+ LogLevel.Error,
+ EventIds.HealthCheckError,
+ "Health check {HealthCheckName} threw an unhandled exception after {ElapsedMilliseconds}ms");
+
+ public static void HealthCheckProcessingBegin(ILogger logger)
+ {
+ _healthCheckProcessingBegin(logger, null);
+ }
+
+ public static void HealthCheckProcessingEnd(ILogger logger, HealthStatus status, TimeSpan duration)
+ {
+ _healthCheckProcessingEnd(logger, duration.TotalMilliseconds, status, null);
+ }
+
+ public static void HealthCheckBegin(ILogger logger, HealthCheckRegistration registration)
+ {
+ _healthCheckBegin(logger, registration.Name, null);
+ }
+
+ public static void HealthCheckEnd(ILogger logger, HealthCheckRegistration registration, HealthReportEntry entry, TimeSpan duration)
+ {
+ switch (entry.Status)
+ {
+ case HealthStatus.Healthy:
+ _healthCheckEndHealthy(logger, registration.Name, duration.TotalMilliseconds, entry.Status, entry.Description, null);
+ break;
+
+ case HealthStatus.Degraded:
+ _healthCheckEndDegraded(logger, registration.Name, duration.TotalMilliseconds, entry.Status, entry.Description, null);
+ break;
+
+ case HealthStatus.Unhealthy:
+ _healthCheckEndUnhealthy(logger, registration.Name, duration.TotalMilliseconds, entry.Status, entry.Description, null);
+ break;
+ }
+ }
+
+ public static void HealthCheckError(ILogger logger, HealthCheckRegistration registration, Exception exception, TimeSpan duration)
+ {
+ _healthCheckError(logger, registration.Name, duration.TotalMilliseconds, exception);
+ }
+
+ public static void HealthCheckData(ILogger logger, HealthCheckRegistration registration, HealthReportEntry entry)
+ {
+ if (entry.Data.Count > 0 && logger.IsEnabled(LogLevel.Debug))
+ {
+ logger.Log(
+ LogLevel.Debug,
+ EventIds.HealthCheckData,
+ new HealthCheckDataLogValue(registration.Name, entry.Data),
+ null,
+ (state, ex) => state.ToString());
+ }
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/src/AzureFunctionHealthCheck/AzureFunctionHealthCheck-old/ValueStopwatch.cs b/src/AzureFunctionHealthCheck/AzureFunctionHealthCheck-old/ValueStopwatch.cs
new file mode 100644
index 0000000..7d3bac6
--- /dev/null
+++ b/src/AzureFunctionHealthCheck/AzureFunctionHealthCheck-old/ValueStopwatch.cs
@@ -0,0 +1,107 @@
+using System;
+using System.Diagnostics;
+
+namespace TaleLearnCode.AzureFunctionHealthCheck
+{
+
+ public struct ValueStopwatch
+ {
+
+ private static readonly double TimestampToTicks = TimeSpan.TicksPerSecond / (double)Stopwatch.Frequency;
+ private long value;
+
+ ///
+ /// Creates and starts a new stopwatch instance.
+ ///
+ /// A new stopwatch which has been started.
+ public static ValueStopwatch StartNew() => new ValueStopwatch(Stopwatch.GetTimestamp());
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The timestamp.
+ private ValueStopwatch(long timestamp)
+ {
+ value = timestamp;
+ }
+
+ ///
+ /// Gets a value indicating whether this instance is running.
+ ///
+ ///
+ /// true if this instance is running; otherwise, false.
+ ///
+ public bool IsRunning => value > 0;
+
+ ///
+ /// Gets the elapsed ticks.
+ ///
+ ///
+ /// The elapsed ticks.
+ ///
+ public long ElapsedTicks
+ {
+ get
+ {
+ long timestamp = value;
+
+ long delta;
+ if (IsRunning)
+ {
+ long start = timestamp;
+ long end = Stopwatch.GetTimestamp();
+ delta = end - start;
+ }
+ else
+ {
+ delta = -timestamp;
+ }
+ return (long)(delta * TimestampToTicks);
+ }
+
+ }
+
+ public TimeSpan Elapsed => TimeSpan.FromTicks(ElapsedTicks);
+
+ ///
+ /// Gets the raw timestamp value.
+ ///
+ /// A long representing the value of the raw timestamp.
+ public long GetRawTimestamp() => value;
+
+ ///
+ /// Starts this stopwatch.
+ ///
+ public void Start()
+ {
+ long timestamp = value;
+
+ if (IsRunning) return; // Already stated; nothing to do
+
+ long newValue = Stopwatch.GetTimestamp() + timestamp;
+ if (newValue == 0) newValue = 1;
+ value = newValue;
+ }
+
+ ///
+ /// Restarts the stopwatch.
+ ///
+ public void Restart() => value = Stopwatch.GetTimestamp();
+
+ ///
+ /// Stops this stopwatch.
+ ///
+ public void Stop()
+ {
+ long timestamp = value;
+ if (!IsRunning) return;
+
+ long end = Stopwatch.GetTimestamp();
+ long delta = end - timestamp;
+
+ value = -delta;
+ }
+
+ }
+
+}
\ No newline at end of file
diff --git a/src/AzureFunctionHealthCheck/AzureFunctionHealthCheck.sln b/src/AzureFunctionHealthCheck/AzureFunctionHealthCheck.sln
new file mode 100644
index 0000000..4ee8ada
--- /dev/null
+++ b/src/AzureFunctionHealthCheck/AzureFunctionHealthCheck.sln
@@ -0,0 +1,25 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.31213.239
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AzureFunctionHealthCheck", "AzureFunctionHealthCheck\AzureFunctionHealthCheck.csproj", "{EDC5DDE0-101A-4D80-9B65-322C50D8FD7A}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {EDC5DDE0-101A-4D80-9B65-322C50D8FD7A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {EDC5DDE0-101A-4D80-9B65-322C50D8FD7A}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {EDC5DDE0-101A-4D80-9B65-322C50D8FD7A}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {EDC5DDE0-101A-4D80-9B65-322C50D8FD7A}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {B243C6A1-104D-436F-A7C6-3A2371023E45}
+ EndGlobalSection
+EndGlobal
diff --git a/src/AzureFunctionHealthCheck/AzureFunctionHealthCheck/AzureFunctionHealthCheck.csproj b/src/AzureFunctionHealthCheck/AzureFunctionHealthCheck/AzureFunctionHealthCheck.csproj
new file mode 100644
index 0000000..a10c28b
--- /dev/null
+++ b/src/AzureFunctionHealthCheck/AzureFunctionHealthCheck/AzureFunctionHealthCheck.csproj
@@ -0,0 +1,26 @@
+
+
+
+ net5.0
+ TaleLearnCode.AzureFunctionHealthCheck
+ TaleLearnCode.AzureFunctionHealthCheck
+ true
+ 0.0.0-pre
+ TaleLearnCode
+ Green Events & Technology, LLC.
+ Azure Functions HealthCheck
+ Provides the mechanism to allow Azure Functions to use Microsoft HealthCheck diagnostics.
+ Copyright ©2021 by Green Events & Technology, LLC. All rights reserved.
+ MIT
+ https://github.com/TaleLearnCode/AzureFunctionHealthCheck
+ https://github.com/TaleLearnCode/AzureFunctionHealthCheck
+ git
+ Azure Functions HealthCheck
+ Initial beta release
+ en-US
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/AzureFunctionHealthCheck/AzureFunctionHealthCheck/DefaultHealthCheckService.cs b/src/AzureFunctionHealthCheck/AzureFunctionHealthCheck/DefaultHealthCheckService.cs
new file mode 100644
index 0000000..df3d646
--- /dev/null
+++ b/src/AzureFunctionHealthCheck/AzureFunctionHealthCheck/DefaultHealthCheckService.cs
@@ -0,0 +1,121 @@
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Diagnostics.HealthChecks;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace TaleLearnCode.AzureFunctionHealthCheck
+{
+
+ public class DefaultHealthCheckService : HealthCheckService
+ {
+
+ private readonly IServiceScopeFactory _serviceScopeFactory;
+ private readonly IOptions _options;
+ private readonly ILogger _logger;
+
+ public DefaultHealthCheckService(
+ IServiceScopeFactory serviceScopeFactory,
+ IOptions options,
+ ILogger logger)
+ {
+ _serviceScopeFactory = serviceScopeFactory ?? throw new ArgumentNullException(nameof(serviceScopeFactory));
+ _options = options ?? throw new ArgumentNullException(nameof(options));
+ _logger = logger ?? throw new ArgumentNullException(nameof(options));
+ ValidateRegistrations(_options.Value.Registrations);
+ }
+
+ ///
+ /// Performs the health check.
+ ///
+ /// The predicate.
+ /// The cancellation token.
+ ///
+ public override async Task CheckHealthAsync(
+ Func predicate,
+ CancellationToken cancellationToken = default)
+ {
+
+ ICollection registrations = _options.Value.Registrations;
+
+ using IServiceScope scope = _serviceScopeFactory.CreateScope();
+
+ HealthCheckContext healthCheckContext = new();
+ Dictionary entries = new(StringComparer.OrdinalIgnoreCase);
+
+ ValueStopwatch totalTime = ValueStopwatch.StartNew();
+ Log.HealthCheckProcessingBegin(_logger);
+
+ foreach (HealthCheckRegistration registration in registrations)
+ {
+ if (predicate != null && !predicate(registration))
+ continue;
+
+ cancellationToken.ThrowIfCancellationRequested();
+
+ IHealthCheck healthCheck = registration.Factory(scope.ServiceProvider);
+
+ ValueStopwatch stopwatch = ValueStopwatch.StartNew();
+ healthCheckContext.Registration = registration;
+
+ Log.HealthCheckBegin(_logger, registration);
+
+ HealthReportEntry entry;
+ try
+ {
+ HealthCheckResult result = await healthCheck.CheckHealthAsync(healthCheckContext, cancellationToken);
+ TimeSpan duration = stopwatch.Elapsed;
+
+ entry = new HealthReportEntry(
+ status: result.Status,
+ description: result.Description,
+ duration: duration,
+ exception: result.Exception,
+ data: result.Data);
+
+ Log.HealthCheckEnd(_logger, registration, entry, duration);
+ Log.HealthCheckData(_logger, registration, entry);
+ }
+ catch (Exception ex) when (ex as OperationCanceledException == null)
+ {
+ var duration = stopwatch.Elapsed;
+ entry = new HealthReportEntry(
+ status: HealthStatus.Unhealthy,
+ description: ex.Message,
+ duration: duration,
+ exception: ex,
+ data: null);
+
+ Log.HealthCheckError(_logger, registration, ex, duration);
+ }
+ entries[registration.Name] = entry;
+ }
+
+ var totalElapsedTime = totalTime.Elapsed;
+ var report = new HealthReport(entries, totalElapsedTime);
+ Log.HealthCheckProcessingEnd(_logger, report.Status, totalElapsedTime);
+ return report;
+
+ }
+
+ private static void ValidateRegistrations(IEnumerable registrations)
+ {
+
+ List duplicateNames = registrations
+ .GroupBy(c => c.Name, StringComparer.OrdinalIgnoreCase)
+ .Where(g => g.Count() > 1)
+ .Select(g => g.Key)
+ .ToList();
+
+ if (duplicateNames.Any())
+ throw new ArgumentException($"Duplicate health checks were registered with the name(s): {string.Join(", ", duplicateNames)}", nameof(registrations));
+
+ }
+
+ }
+
+}
\ No newline at end of file
diff --git a/src/AzureFunctionHealthCheck/AzureFunctionHealthCheck/EventIds.cs b/src/AzureFunctionHealthCheck/AzureFunctionHealthCheck/EventIds.cs
new file mode 100644
index 0000000..8d38517
--- /dev/null
+++ b/src/AzureFunctionHealthCheck/AzureFunctionHealthCheck/EventIds.cs
@@ -0,0 +1,14 @@
+using Microsoft.Extensions.Logging;
+
+namespace TaleLearnCode.AzureFunctionHealthCheck
+{
+ internal static class EventIds
+ {
+ public static readonly EventId HealthCheckProcessingBegin = new EventId(100, "HealthCheckProcessingBegin");
+ public static readonly EventId HealthCheckProcessingEnd = new EventId(101, "HealthCheckProcessingEnd");
+ public static readonly EventId HealthCheckBegin = new EventId(102, "HealthCheckBegin");
+ public static readonly EventId HealthCheckEnd = new EventId(103, "HealthCheckEnd");
+ public static readonly EventId HealthCheckError = new EventId(104, "HealthCheckError");
+ public static readonly EventId HealthCheckData = new EventId(105, "HealthCheckData");
+ }
+}
diff --git a/src/AzureFunctionHealthCheck/AzureFunctionHealthCheck/HealthCheckDataLogValue.cs b/src/AzureFunctionHealthCheck/AzureFunctionHealthCheck/HealthCheckDataLogValue.cs
new file mode 100644
index 0000000..dfd60fd
--- /dev/null
+++ b/src/AzureFunctionHealthCheck/AzureFunctionHealthCheck/HealthCheckDataLogValue.cs
@@ -0,0 +1,107 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace TaleLearnCode.AzureFunctionHealthCheck
+{
+
+ ///
+ /// Represents value of a health check data log.
+ ///
+ ///
+ internal class HealthCheckDataLogValue : IReadOnlyList>
+ {
+ private readonly string _name;
+ private readonly List> _values;
+ private string _formatted;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The name.
+ /// The values.
+ public HealthCheckDataLogValue(string name, IReadOnlyDictionary values)
+ {
+ _name = name;
+ _values = values.ToList();
+ _values.Add(new KeyValuePair("HealthCheckName", name));
+ }
+
+ ///
+ /// Gets the at the specified index.
+ ///
+ ///
+ /// The .
+ ///
+ /// The index.
+ ///
+ /// index
+ public KeyValuePair this[int index]
+ {
+ get
+ {
+ if (index < 0 || index >= Count)
+ throw new IndexOutOfRangeException(nameof(index));
+ return _values[index];
+ }
+ }
+
+ public int Count => _values.Count;
+
+ ///
+ /// Returns an enumerator that iterates through the collection.
+ ///
+ ///
+ /// An enumerator that can be used to iterate through the collection.
+ ///
+ public IEnumerator> GetEnumerator()
+ {
+ return _values.GetEnumerator();
+ }
+
+ ///
+ /// Returns an enumerator that iterates through a collection.
+ ///
+ ///
+ /// An object that can be used to iterate through the collection.
+ ///
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return _values.GetEnumerator();
+ }
+
+ ///
+ /// Converts the to a string.
+ ///
+ ///
+ /// A that represents this instance.
+ ///
+ public override string ToString()
+ {
+ if (_formatted == null)
+ {
+ var builder = new StringBuilder();
+ builder.AppendLine($"Health check data for {_name}:");
+
+ var values = _values;
+ for (var i = 0; i < values.Count; i++)
+ {
+ var kvp = values[i];
+ builder.Append(" ");
+ builder.Append(kvp.Key);
+ builder.Append(": ");
+
+ builder.AppendLine(kvp.Value?.ToString());
+ }
+
+ _formatted = builder.ToString();
+ }
+
+ return _formatted;
+ }
+
+ }
+
+}
\ No newline at end of file
diff --git a/src/AzureFunctionHealthCheck/AzureFunctionHealthCheck/HealthCheckServiceFunctionExtension.cs b/src/AzureFunctionHealthCheck/AzureFunctionHealthCheck/HealthCheckServiceFunctionExtension.cs
new file mode 100644
index 0000000..1ae47f0
--- /dev/null
+++ b/src/AzureFunctionHealthCheck/AzureFunctionHealthCheck/HealthCheckServiceFunctionExtension.cs
@@ -0,0 +1,30 @@
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.DependencyInjection.Extensions;
+using Microsoft.Extensions.Diagnostics.HealthChecks;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace TaleLearnCode.AzureFunctionHealthCheck
+{
+
+ ///
+ /// Provides extension methods for registering .
+ ///
+ public static class HealthCheckServiceFunctionExtension
+ {
+
+ ///
+ /// Adds the to the container, using the provided delegates to register.
+ ///
+ /// The services.
+ public static IHealthChecksBuilder AddFunctionHealthChecks(this IServiceCollection services)
+ {
+ services.TryAddSingleton();
+ return new HealthChecksBuilder(services);
+ }
+
+ }
+}
diff --git a/src/AzureFunctionHealthCheck/AzureFunctionHealthCheck/HealthChecksBuilder.cs b/src/AzureFunctionHealthCheck/AzureFunctionHealthCheck/HealthChecksBuilder.cs
new file mode 100644
index 0000000..ec9d0a3
--- /dev/null
+++ b/src/AzureFunctionHealthCheck/AzureFunctionHealthCheck/HealthChecksBuilder.cs
@@ -0,0 +1,47 @@
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Diagnostics.HealthChecks;
+using System;
+
+namespace TaleLearnCode.AzureFunctionHealthCheck
+{
+
+ ///
+ /// A builder used to register health checks.
+ ///
+ ///
+ public class HealthChecksBuilder : IHealthChecksBuilder
+ {
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The into which instances should be registered.
+ public HealthChecksBuilder(IServiceCollection services)
+ {
+ Services = services;
+ }
+
+ ///
+ /// Gets the into which instances should be registered.
+ ///
+ public IServiceCollection Services { get; }
+
+ ///
+ /// Adds a for a health check.
+ ///
+ /// The .
+ /// An initialized
+ /// registration
+ public IHealthChecksBuilder Add(HealthCheckRegistration registration)
+ {
+ if (registration == default) throw new ArgumentNullException(nameof(registration));
+ Services.Configure(options =>
+ {
+ options.Registrations.Add(registration);
+ });
+ return this;
+ }
+
+ }
+
+}
\ No newline at end of file
diff --git a/src/AzureFunctionHealthCheck/AzureFunctionHealthCheck/Log.cs b/src/AzureFunctionHealthCheck/AzureFunctionHealthCheck/Log.cs
new file mode 100644
index 0000000..503e6a8
--- /dev/null
+++ b/src/AzureFunctionHealthCheck/AzureFunctionHealthCheck/Log.cs
@@ -0,0 +1,105 @@
+using Microsoft.Extensions.Diagnostics.HealthChecks;
+using Microsoft.Extensions.Logging;
+using System;
+
+namespace TaleLearnCode.AzureFunctionHealthCheck
+{
+
+ internal static class Log
+ {
+ private static readonly Action _healthCheckProcessingBegin = LoggerMessage.Define(
+ LogLevel.Debug,
+ EventIds.HealthCheckProcessingBegin,
+ "Running health checks");
+
+ private static readonly Action _healthCheckProcessingEnd = LoggerMessage.Define(
+ LogLevel.Debug,
+ EventIds.HealthCheckProcessingEnd,
+ "Health check processing completed after {ElapsedMilliseconds}ms with combined status {HealthStatus}");
+
+ private static readonly Action _healthCheckBegin = LoggerMessage.Define(
+ LogLevel.Debug,
+ EventIds.HealthCheckBegin,
+ "Running health check {HealthCheckName}");
+
+ // These are separate so they can have different log levels
+ private static readonly string HealthCheckEndText = "Health check {HealthCheckName} completed after {ElapsedMilliseconds}ms with status {HealthStatus} and '{HealthCheckDescription}'";
+
+ private static readonly Action _healthCheckEndHealthy = LoggerMessage.Define(
+ LogLevel.Debug,
+ EventIds.HealthCheckEnd,
+ HealthCheckEndText);
+
+ private static readonly Action _healthCheckEndDegraded = LoggerMessage.Define(
+ LogLevel.Warning,
+ EventIds.HealthCheckEnd,
+ HealthCheckEndText);
+
+ private static readonly Action _healthCheckEndUnhealthy = LoggerMessage.Define(
+ LogLevel.Error,
+ EventIds.HealthCheckEnd,
+ HealthCheckEndText);
+
+ private static readonly Action _healthCheckEndFailed = LoggerMessage.Define(
+ LogLevel.Error,
+ EventIds.HealthCheckEnd,
+ HealthCheckEndText);
+
+ private static readonly Action _healthCheckError = LoggerMessage.Define(
+ LogLevel.Error,
+ EventIds.HealthCheckError,
+ "Health check {HealthCheckName} threw an unhandled exception after {ElapsedMilliseconds}ms");
+
+ public static void HealthCheckProcessingBegin(ILogger logger)
+ {
+ _healthCheckProcessingBegin(logger, null);
+ }
+
+ public static void HealthCheckProcessingEnd(ILogger logger, HealthStatus status, TimeSpan duration)
+ {
+ _healthCheckProcessingEnd(logger, duration.TotalMilliseconds, status, null);
+ }
+
+ public static void HealthCheckBegin(ILogger logger, HealthCheckRegistration registration)
+ {
+ _healthCheckBegin(logger, registration.Name, null);
+ }
+
+ public static void HealthCheckEnd(ILogger logger, HealthCheckRegistration registration, HealthReportEntry entry, TimeSpan duration)
+ {
+ switch (entry.Status)
+ {
+ case HealthStatus.Healthy:
+ _healthCheckEndHealthy(logger, registration.Name, duration.TotalMilliseconds, entry.Status, entry.Description, null);
+ break;
+
+ case HealthStatus.Degraded:
+ _healthCheckEndDegraded(logger, registration.Name, duration.TotalMilliseconds, entry.Status, entry.Description, null);
+ break;
+
+ case HealthStatus.Unhealthy:
+ _healthCheckEndUnhealthy(logger, registration.Name, duration.TotalMilliseconds, entry.Status, entry.Description, null);
+ break;
+ }
+ }
+
+ public static void HealthCheckError(ILogger logger, HealthCheckRegistration registration, Exception exception, TimeSpan duration)
+ {
+ _healthCheckError(logger, registration.Name, duration.TotalMilliseconds, exception);
+ }
+
+ public static void HealthCheckData(ILogger logger, HealthCheckRegistration registration, HealthReportEntry entry)
+ {
+ if (entry.Data.Count > 0 && logger.IsEnabled(LogLevel.Debug))
+ {
+ logger.Log(
+ LogLevel.Debug,
+ EventIds.HealthCheckData,
+ new HealthCheckDataLogValue(registration.Name, entry.Data),
+ null,
+ (state, ex) => state.ToString());
+ }
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/src/AzureFunctionHealthCheck/AzureFunctionHealthCheck/ValueStopwatch.cs b/src/AzureFunctionHealthCheck/AzureFunctionHealthCheck/ValueStopwatch.cs
new file mode 100644
index 0000000..7d3bac6
--- /dev/null
+++ b/src/AzureFunctionHealthCheck/AzureFunctionHealthCheck/ValueStopwatch.cs
@@ -0,0 +1,107 @@
+using System;
+using System.Diagnostics;
+
+namespace TaleLearnCode.AzureFunctionHealthCheck
+{
+
+ public struct ValueStopwatch
+ {
+
+ private static readonly double TimestampToTicks = TimeSpan.TicksPerSecond / (double)Stopwatch.Frequency;
+ private long value;
+
+ ///
+ /// Creates and starts a new stopwatch instance.
+ ///
+ /// A new stopwatch which has been started.
+ public static ValueStopwatch StartNew() => new ValueStopwatch(Stopwatch.GetTimestamp());
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The timestamp.
+ private ValueStopwatch(long timestamp)
+ {
+ value = timestamp;
+ }
+
+ ///
+ /// Gets a value indicating whether this instance is running.
+ ///
+ ///
+ /// true if this instance is running; otherwise, false.
+ ///
+ public bool IsRunning => value > 0;
+
+ ///
+ /// Gets the elapsed ticks.
+ ///
+ ///
+ /// The elapsed ticks.
+ ///
+ public long ElapsedTicks
+ {
+ get
+ {
+ long timestamp = value;
+
+ long delta;
+ if (IsRunning)
+ {
+ long start = timestamp;
+ long end = Stopwatch.GetTimestamp();
+ delta = end - start;
+ }
+ else
+ {
+ delta = -timestamp;
+ }
+ return (long)(delta * TimestampToTicks);
+ }
+
+ }
+
+ public TimeSpan Elapsed => TimeSpan.FromTicks(ElapsedTicks);
+
+ ///
+ /// Gets the raw timestamp value.
+ ///
+ /// A long representing the value of the raw timestamp.
+ public long GetRawTimestamp() => value;
+
+ ///
+ /// Starts this stopwatch.
+ ///
+ public void Start()
+ {
+ long timestamp = value;
+
+ if (IsRunning) return; // Already stated; nothing to do
+
+ long newValue = Stopwatch.GetTimestamp() + timestamp;
+ if (newValue == 0) newValue = 1;
+ value = newValue;
+ }
+
+ ///
+ /// Restarts the stopwatch.
+ ///
+ public void Restart() => value = Stopwatch.GetTimestamp();
+
+ ///
+ /// Stops this stopwatch.
+ ///
+ public void Stop()
+ {
+ long timestamp = value;
+ if (!IsRunning) return;
+
+ long end = Stopwatch.GetTimestamp();
+ long delta = end - timestamp;
+
+ value = -delta;
+ }
+
+ }
+
+}
\ No newline at end of file