From c1ad415abbffed005ac3497c48ac2d30b211415d Mon Sep 17 00:00:00 2001 From: Benney Au Date: Tue, 11 Dec 2018 12:39:23 +1100 Subject: [PATCH 1/2] add azure service bus DLQ check --- .editorconfig | 8 +++ HealthAzure.sln | 1 + ...iceBusQueueHealthCheckBuilderExtensions.cs | 58 ++++++++++++++++++- ...iceBusTopicHealthCheckBuilderExtensions.cs | 58 +++++++++++++++++++ .../ServiceBusHealthChecks.cs | 43 ++++++++++++++ 5 files changed, 167 insertions(+), 1 deletion(-) create mode 100644 .editorconfig create mode 100644 src/App.Metrics.Health.Checks.AzureServiceBus/ServiceBusHealthChecks.cs diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..7218dcc --- /dev/null +++ b/.editorconfig @@ -0,0 +1,8 @@ +[*] +end_of_line = crlf + +[*.xml] +indent_style = space + +[*.cs] +dotnet_sort_system_directives_first = true \ No newline at end of file diff --git a/HealthAzure.sln b/HealthAzure.sln index 03eace7..f54e0d5 100644 --- a/HealthAzure.sln +++ b/HealthAzure.sln @@ -10,6 +10,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{2D805782-756 EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{31A4DDB1-952E-4EED-96EF-29C669279A86}" ProjectSection(SolutionItems) = preProject + .editorconfig = .editorconfig .travis.yml = .travis.yml AppMetrics.ruleset = AppMetrics.ruleset appveyor.yml = appveyor.yml diff --git a/src/App.Metrics.Health.Checks.AzureServiceBus/AzureServiceBusQueueHealthCheckBuilderExtensions.cs b/src/App.Metrics.Health.Checks.AzureServiceBus/AzureServiceBusQueueHealthCheckBuilderExtensions.cs index eed41d0..275aad0 100644 --- a/src/App.Metrics.Health.Checks.AzureServiceBus/AzureServiceBusQueueHealthCheckBuilderExtensions.cs +++ b/src/App.Metrics.Health.Checks.AzureServiceBus/AzureServiceBusQueueHealthCheckBuilderExtensions.cs @@ -5,12 +5,14 @@ using System; using System.Text; using System.Threading.Tasks; +using App.Metrics.Health.Checks.AzureServiceBus; using App.Metrics.Health.Logging; using Microsoft.Azure.ServiceBus; +using Microsoft.Azure.ServiceBus.Management; // ReSharper disable CheckNamespace namespace App.Metrics.Health - // ReSharper restore CheckNamespace +// ReSharper restore CheckNamespace { public static class AzureServiceBusQueueHealthCheckBuilderExtensions { @@ -67,6 +69,60 @@ public static IHealthBuilder AddAzureServiceBusQueueConnectivityCheck( return builder.Builder; } + public static IHealthBuilder AddAzureServiceBusQueueDeadLetterQueueCheck( + this IHealthCheckBuilder builder, + string name, + string connectionString, + string queueName, + long deadLetterWarningThreshold = 1, + long? deadLetterErrorThreshold = null) + { + if (deadLetterErrorThreshold.HasValue && (deadLetterWarningThreshold > deadLetterErrorThreshold)) + { + throw new ArgumentException("Error threshold must exceed warning threshold", nameof(deadLetterErrorThreshold)); + } + + var managementClient = new ManagementClient(connectionString); + builder.AddCheck( + name, + ServiceBusHealthChecks.CheckDeadLetterQueueCount(Logger, queueName, name, GetQueueMessageCount, deadLetterWarningThreshold, deadLetterErrorThreshold)); + return builder.Builder; + + async Task GetQueueMessageCount() + { + var info = await managementClient.GetQueueRuntimeInfoAsync(queueName); + return info.MessageCountDetails; + } + } + + public static IHealthBuilder AddAzureServiceBusQueueDeadLetterQueueCheck( + this IHealthCheckBuilder builder, + string name, + string connectionString, + string queueName, + TimeSpan cacheDuration, + long deadLetterWarningThreshold = 1, + long? deadLetterErrorThreshold = null) + { + if (deadLetterErrorThreshold.HasValue && (deadLetterWarningThreshold > deadLetterErrorThreshold)) + { + throw new ArgumentException("Error threshold must exceed warning threshold", nameof(deadLetterErrorThreshold)); + } + + var managementClient = new ManagementClient(connectionString); + builder.AddCachedCheck( + name, + ServiceBusHealthChecks.CheckDeadLetterQueueCount(Logger, queueName, name, GetQueueMessageCount, deadLetterWarningThreshold, deadLetterErrorThreshold), + cacheDuration); + return builder.Builder; + + async Task GetQueueMessageCount() + { + var info = await managementClient.GetQueueRuntimeInfoAsync(queueName); + return info.MessageCountDetails; + } + } + private static Func> CheckServiceBusQueueConnectivity(string name, QueueClient queueClient) { return async () => diff --git a/src/App.Metrics.Health.Checks.AzureServiceBus/AzureServiceBusTopicHealthCheckBuilderExtensions.cs b/src/App.Metrics.Health.Checks.AzureServiceBus/AzureServiceBusTopicHealthCheckBuilderExtensions.cs index a96ab2c..d93ace5 100644 --- a/src/App.Metrics.Health.Checks.AzureServiceBus/AzureServiceBusTopicHealthCheckBuilderExtensions.cs +++ b/src/App.Metrics.Health.Checks.AzureServiceBus/AzureServiceBusTopicHealthCheckBuilderExtensions.cs @@ -5,8 +5,10 @@ using System; using System.Text; using System.Threading.Tasks; +using App.Metrics.Health.Checks.AzureServiceBus; using App.Metrics.Health.Logging; using Microsoft.Azure.ServiceBus; +using Microsoft.Azure.ServiceBus.Management; // ReSharper disable CheckNamespace namespace App.Metrics.Health @@ -67,6 +69,62 @@ public static IHealthBuilder AddAzureServiceBusTopicConnectivityCheck( return builder.Builder; } + public static IHealthBuilder AddAzureServiceBusTopicSubscriptionDeadLetterQueueCheck( + this IHealthCheckBuilder builder, + string name, + string connectionString, + string topicName, + string subscriptionName, + long deadLetterWarningThreshold = 1, + long? deadLetterErrorThreshold = null) + { + if (deadLetterErrorThreshold.HasValue && (deadLetterWarningThreshold > deadLetterErrorThreshold)) + { + throw new ArgumentException("Error threshold must exceed warning threshold", nameof(deadLetterErrorThreshold)); + } + + var managementClient = new ManagementClient(connectionString); + builder.AddCheck( + name, + ServiceBusHealthChecks.CheckDeadLetterQueueCount(Logger, EntityNameHelper.FormatSubscriptionPath(topicName, subscriptionName), name, GetQueueMessageCount, deadLetterWarningThreshold, deadLetterErrorThreshold)); + return builder.Builder; + + async Task GetQueueMessageCount() + { + var info = await managementClient.GetSubscriptionRuntimeInfoAsync(topicName, subscriptionName); + return info.MessageCountDetails; + } + } + + public static IHealthBuilder AddAzureServiceBusTopicSubscriptionDeadLetterQueueCheck( + this IHealthCheckBuilder builder, + string name, + string connectionString, + string topicName, + string subscriptionName, + TimeSpan cacheDuration, + long deadLetterWarningThreshold = 1, + long? deadLetterErrorThreshold = null) + { + if (deadLetterErrorThreshold.HasValue && (deadLetterWarningThreshold > deadLetterErrorThreshold)) + { + throw new ArgumentException("Error threshold must exceed warning threshold", nameof(deadLetterErrorThreshold)); + } + + var managementClient = new ManagementClient(connectionString); + builder.AddCachedCheck( + name, + ServiceBusHealthChecks.CheckDeadLetterQueueCount(Logger, EntityNameHelper.FormatSubscriptionPath(topicName, subscriptionName), name, GetQueueMessageCount, deadLetterWarningThreshold, deadLetterErrorThreshold), + cacheDuration); + return builder.Builder; + + async Task GetQueueMessageCount() + { + var info = await managementClient.GetSubscriptionRuntimeInfoAsync(topicName, subscriptionName); + return info.MessageCountDetails; + } + } + private static Func> CheckServiceBusTopicConnectivity(string name, TopicClient topicClient) { return async () => diff --git a/src/App.Metrics.Health.Checks.AzureServiceBus/ServiceBusHealthChecks.cs b/src/App.Metrics.Health.Checks.AzureServiceBus/ServiceBusHealthChecks.cs new file mode 100644 index 0000000..c7dc01c --- /dev/null +++ b/src/App.Metrics.Health.Checks.AzureServiceBus/ServiceBusHealthChecks.cs @@ -0,0 +1,43 @@ +// +// Copyright (c) App Metrics Contributors. All rights reserved. +// + +using System; +using System.Threading.Tasks; +using App.Metrics.Health.Logging; +using Microsoft.Azure.ServiceBus.Management; + +namespace App.Metrics.Health.Checks.AzureServiceBus +{ + internal static class ServiceBusHealthChecks + { + internal static Func> CheckDeadLetterQueueCount(ILog logger, string entityPath, string name, Func> getMessageCount, long deadLetterWarningThreshold, long? deadLetterErrorThreshold) + { + return async () => + { + var deadLetteredMessages = 0L; + try + { + deadLetteredMessages = (await getMessageCount()).DeadLetterMessageCount; + } + catch (Exception ex) + { + logger.ErrorException($"{name} failed.", ex); + + return HealthCheckResult.Unhealthy(ex); + } + + if (deadLetterErrorThreshold.HasValue && deadLetteredMessages >= deadLetterErrorThreshold) + { + return HealthCheckResult.Unhealthy($"Unhealthy. '{entityPath}' has {deadLetteredMessages} dead letter messages."); + } + else if (deadLetteredMessages >= deadLetterWarningThreshold) + { + return HealthCheckResult.Degraded($"Degraded. '{entityPath}' has {deadLetteredMessages} dead letter messages."); + } + + return HealthCheckResult.Healthy($"OK. {entityPath} has {deadLetteredMessages} dead letter messages."); + }; + } + } +} From 745028686d2bc4dfcb39f251e29365547385da1e Mon Sep 17 00:00:00 2001 From: alhardy Date: Tue, 11 Dec 2018 20:44:49 +1100 Subject: [PATCH 2/2] version bump to 1.1.0 --- version.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.props b/version.props index 9373e86..a48c5d6 100644 --- a/version.props +++ b/version.props @@ -1,7 +1,7 @@ - 1.0.0 + 1.1.0 \ No newline at end of file