Always subscribe to Dead-lettered messages when using an Azure Service Bus

I typically come across solutions that incorporate a service bus where monitoring of the dead-letter queues are simply ignored. Upon questioning the reason, the normal excuse for not monitoring the DLQ (dead-letter queue) is quote “I don’t require it because I have added exception handling therefore no messages will end up on the DLQ”. From experience there will be scenarios where the exception logic did not capture an edge case scenario and the message will end up onto the DLQ without anyone knowing about. 

A simple solution is to have a single process to monitor all the DQL messages and raise an alert when one occurs. Below is one of my building blocks which a typically incorporate when there is a service bus involved in a solution.

image

To use the DLQ building block to centrally capture any DLQ message, simply set the “ForwardDeadLetteredMessagesTo” property on each of the messaging queues to a common DLQ handler queue as shown below.

image

Now when a message gets dead lettered, it will end up in this common queue which is monitored by an Azure Function. Note the current NuGet version v3.0.4 of the ServiceBus DLL has the DeadLetterSource property and is not currently available in the Logic App Service Bus connector. The function writes the DLQ meta data, any custom properties and the message payload to a blobstore file. By using an Azure Storage Account V2 for the blobstore, a new blob creation event will be fired to any interested subscribers which in this case is a Logic App.

Azure Function Code

Below is the code for the Azure Function. Here I am using the IBinder interface to allow me to set the folder path and file name imperatively. The connection strings (ServiceBusConn, StorageAccountConn) are defined in the Application settings of the  Azure App Service.

Code Snippet
  1. using Microsoft.Azure.ServiceBus;
  2. using Microsoft.Azure.WebJobs;
  3. using Microsoft.Extensions.Logging;
  4. using Newtonsoft.Json;
  5. using System;
  6. using System.Collections.Generic;
  7. using System.IO;
  8. using System.Threading.Tasks;
  9.  
  10. namespace ServiceBusDLQMonitoring
  11. {
  12.     public static class Function1
  13.     {
  14.         [FunctionName("Function1")]
  15.  
  16.         public static async Task RunAsync([ServiceBusTrigger("dlq-processor", Connection = "ServiceBusConn")] Message dlqQueue,
  17.             Binder blobBinder,          
  18.             ILogger log)
  19.         {
  20.             log.LogInformation($"C# ServiceBus queue trigger function processed message: {dlqQueue.MessageId}");
  21.  
  22.             //set the filename and blob path
  23.             var blobFile = Guid.NewGuid().ToString() + ".json";
  24.             var path = string.Concat("dlq/",DateTime.UtcNow.ToString("yyyy/MM/dd"),"/" ,dlqQueue.SystemProperties.DeadLetterSource,"/", blobFile);
  25.  
  26.             var dlqMsq = new DLQMessage
  27.             {
  28.                 DeadletterSource = dlqQueue.SystemProperties.DeadLetterSource,
  29.                 MessageId = dlqQueue.MessageId,
  30.                 SequenceNumber = dlqQueue.SystemProperties.SequenceNumber,
  31.                 SessionId = dlqQueue.SessionId,
  32.                 UserProperties = dlqQueue.UserProperties,
  33.                 DeadLetterReason = dlqQueue.UserProperties["DeadLetterReason"].ToString(),
  34.                 EnqueuedDttmUTC = dlqQueue.SystemProperties.EnqueuedTimeUtc,
  35.                 ContentType = dlqQueue.ContentType,
  36.                 DeliveryCount = dlqQueue.SystemProperties.DeliveryCount,
  37.                 Label = dlqQueue.Label,
  38.                 MsgBase64Encoded = Convert.ToBase64String(dlqQueue.Body, Base64FormattingOptions.None)
  39.             };
  40.  
  41.             var blobAttributes = new Attribute[]
  42.             {
  43.                 new BlobAttribute(path),
  44.                 new StorageAccountAttribute("StorageAccountConn")                
  45.             };
  46.  
  47.             using (var writer = await blobBinder.BindAsync<TextWriter>(blobAttributes))
  48.             {              
  49.               writer.Write(JsonConvert.SerializeObject(dlqMsq,Formatting.Indented));
  50.             }
  51.         }
  52.     }
  53.  
  54.     public class DLQMessage
  55.     {       
  56.         public string DeadletterSource { get; set; }
  57.         public long SequenceNumber { get; set; }
  58.         public string MessageId { get; set; }
  59.         public string SessionId { get; set; }
  60.         public string Label { get; set; }
  61.         public string DeadLetterReason { get; set; }
  62.         public DateTime EnqueuedDttmUTC { get; set; }
  63.         public int DeliveryCount { get; set; }        
  64.         public string ContentType { get; set; }
  65.         public IDictionary<string,object> UserProperties { get; set; }
  66.         public string MsgBase64Encoded { get; set; }
  67.     }
  68. }

 

Logic App Implementation

A Logic App is used to the retrieve the message from the blob store when it is triggered by the EventGrid  HTTP webhook. The basic workflow is shown below and can be expanded to suit your own requirements.

image

The expression for the ‘Get blob content using path’ action is @{replace(triggerBody()[0][‘data’][‘url’],’https://dqlmessages.blob.core.windows.net/’,”)}. Here I am just replacing the Domain name with an empty string as I only want the resource location.

The Parse JSON action has the following schema. This makes it easier to reference the properties downstream.

{

    "properties": {

        "ContentType": {

            "type": [

                "string",

                "null"

            ]

        },

        "DeadLetterReason": {

            "type": "string"

        },

        "DeadletterSource": {

            "type": "string"

        },

        "DeliveryCount": {

            "type": "integer"

        },

        "EnqueuedDttmUTC": {

            "type": "string"

        },

        "Label": {

            "type": [

                "string",

                "null"

            ]

        },

        "MessageId": {

            "type": "string"

        },

        "MsgBase64Encoded": {

            "type": [

                "string",

                "null"

            ]

        },

        "SequenceNumber": {

            "type": "integer"

        },

        "SessionId": {

            "type": [

                "string",

                "null"

            ]

        },

        "UserProperties": {

            "type": "any"

        }

    },

    "type": "object"

}

 

The last action ‘Set variable MsgBody’  has the value set to: “@{base64ToString(body(‘Parse_JSON’)?[‘MsgBase64Encoded’])}”

Blob creation event configuration

Next is to setup a subscription to the Blob creation event. Click on Events under the Storage account for the DLQ messages as shown below.

image

Then click on the +Event Subscription to add a new subscription.

image

Setup the subscription Basic details with a suitable subscription name and the blob storage account resource name. Uncheck the ‘Subscribe to all event types’ and select Blob Created event. Set the endpoint details to Web Hook and the URL of the Logic App Http trigger endpoint address.

image

Under Filters, enable subject filtering. Add the following prefix ‘/blobServices/default/containers/’ to the name of the DLQ container (in this example its called ‘dlq’) and add it to the ‘Subject Begins With’ textbox.  Then in the ‘Subject Ends With’, set it to the filename extension ‘.json’. Now click the Create button at the bottom of the page to create the subscription.

image

Sample Logic App output

Once everything is wired up and if there are any messages that have been placed onto the DLQ, you should see some logic app runs. Below is an example of the outputs from the last two actions.

image

All you need to do now is extend the Logic App to deliver the DLQ alert to somewhere.

Enjoy…

3 Replies to “Always subscribe to Dead-lettered messages when using an Azure Service Bus”

  1. Really good pattern. We are looking into a similar scenario and will be looking to implementation a similar setup to create tickets upon dead letter messages.

Leave a Reply

Your email address will not be published. Required fields are marked *