Skip to content

Commit

Permalink
Tweak dead-lettering (#64)
Browse files Browse the repository at this point in the history
* Tweak dead-lettering

* Fix tests.
  • Loading branch information
chullybun authored Apr 26, 2023
1 parent 9f00bde commit ae8a3bc
Show file tree
Hide file tree
Showing 9 changed files with 27 additions and 18 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

Represents the **NuGet** versions.

## v2.9.1
- *Fixed:* The dead-lettering within `ServiceBusSubscriberInvoker` will write the exception stack trace, etc. to a new message property named `SubscriberException` to ensure this content is consistently persisted, with related error description being the exception message only.

## v2.9.0
- *Enhancement:* Added `PagingAttribute` and `PagingOperationFilter` to enable swagger output of `PagingArgs` parameters for an operation.

Expand Down
2 changes: 1 addition & 1 deletion Common.targets
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<Project>
<PropertyGroup>
<Version>2.9.0</Version>
<Version>2.9.1</Version>
<LangVersion>preview</LangVersion>
<Authors>Avanade</Authors>
<Company>Avanade</Company>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ public Task ReceiveAsync(ServiceBusReceivedMessage message, ServiceBusMessageAct
// Execute subscriber receive with the event.
var success = await Orchestrator.ReceiveAsync(this, subscriber!, @event, cancellationToken).ConfigureAwait(false);
if (success)
Logger.LogInformation("{Type} executed {Subscriber} successfully - Service Bus message '{Message}'.", GetType().Name, subscriber!.GetType().Name, message.MessageId);
Logger.LogDebug("{Type} executed {Subscriber} successfully - Service Bus message '{Message}'.", GetType().Name, subscriber!.GetType().Name, message.MessageId);
}, (message, messageActions), cancellationToken);
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/CoreEx.Azure/ServiceBus/ServiceBusSubscriber.cs
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ public Task ReceiveAsync(ServiceBusReceivedMessage message, ServiceBusMessageAct
// Invoke the actual function logic.
await function(@event!).ConfigureAwait(false);

Logger.LogInformation("{Type} executed successfully - Service Bus message '{Message}'.", GetType().Name, message.MessageId);
Logger.LogDebug("{Type} executed successfully - Service Bus message '{Message}'.", GetType().Name, message.MessageId);
}, (message, messageActions), cancellationToken);
}

Expand Down
8 changes: 5 additions & 3 deletions src/CoreEx.Azure/ServiceBus/ServiceBusSubscriberInvoker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ namespace CoreEx.Azure.ServiceBus
/// </summary>
public class ServiceBusSubscriberInvoker : InvokerBase<EventSubscriberBase, (ServiceBusReceivedMessage Message, ServiceBusMessageActions MessageActions)>
{
private const string SubscriberExceptionPropertyName = "SubscriberException";

/// <inheritdoc/>
protected async override Task<TResult> OnInvokeAsync<TResult>(EventSubscriberBase invoker, Func<CancellationToken, Task<TResult>> func, (ServiceBusReceivedMessage Message, ServiceBusMessageActions MessageActions) args, CancellationToken cancellationToken)
{
Expand Down Expand Up @@ -94,14 +96,14 @@ protected async override Task<TResult> OnInvokeAsync<TResult>(EventSubscriberBas
public static async Task DeadLetterExceptionAsync(EventSubscriberBase invoker, ServiceBusReceivedMessage message, ServiceBusMessageActions messageActions, string errorReason, Exception exception, CancellationToken cancellationToken)
{
invoker.Logger.LogDebug("Dead Lettering - Service Bus message '{Message}'. [{Reason}] {Error}", message.MessageId, errorReason, exception.Message);
await messageActions.DeadLetterMessageAsync(message, errorReason, ToDeadLetterReason(exception.ToString()), cancellationToken).ConfigureAwait(false);
await messageActions.DeadLetterMessageAsync(message, new Dictionary<string, object?> { { SubscriberExceptionPropertyName, FormatText(exception.ToString()) } }, errorReason, FormatText(exception.Message), cancellationToken).ConfigureAwait(false);
invoker.Logger.LogError(exception, "Dead Lettered - Service Bus message '{Message}'. [{Reason}] {Error}", message.MessageId, errorReason, exception.Message);
}

/// <summary>
/// Shortens the reason text to 4096 characters, which is the maximum allowed length for a dead letter reason.
/// Shortens the text to 2048 characters; should be enough to given context - otherwise, full context should have be written to the log.
/// </summary>
private static string? ToDeadLetterReason(string? reason) => reason?[..Math.Min(reason.Length, 4096)];
private static string? FormatText(string? text) => text?[..Math.Min(text.Length, 2048)];

/// <summary>
/// Update the <see cref="ILogger.BeginScope{TState}(TState)"/> <paramref name="state"/> from the <paramref name="message"/>.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using Microsoft.Azure.WebJobs.ServiceBus;
using Moq;
using NUnit.Framework;
using System.Collections.Generic;
using UnitTestEx.NUnit;

namespace CoreEx.Test.Framework.FluentValidation
Expand All @@ -23,7 +24,7 @@ public void ReceiveAsync_ValidationException_Validation()
.Run(s => s.RunAsync(message, actionsMock.Object))
.AssertSuccess();

actionsMock.Verify(m => m.DeadLetterMessageAsync(message, ErrorType.ValidationError.ToString(), It.IsAny<string>(), default), Times.Once);
actionsMock.Verify(m => m.DeadLetterMessageAsync(message, It.IsAny<Dictionary<string, object?>>(), ErrorType.ValidationError.ToString(), It.IsAny<string>(), default), Times.Once);
actionsMock.VerifyNoOtherCalls();
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using Moq;
using NUnit.Framework;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using UnitTestEx.NUnit;

Expand All @@ -26,7 +27,7 @@ public void ReceiveAsync_ValidationException()
.Run(s => s.ReceiveAsync<Product>(message, actionsMock.Object, ed => throw new InvalidOperationException("Should not get here.")))
.AssertSuccess();

actionsMock.Verify(m => m.DeadLetterMessageAsync(message, ErrorType.ValidationError.ToString(), It.IsAny<string>(), default), Times.Once);
actionsMock.Verify(m => m.DeadLetterMessageAsync(message, It.IsAny<Dictionary<string, object?>>(), ErrorType.ValidationError.ToString(), It.IsAny<string>(), default), Times.Once);
actionsMock.VerifyNoOtherCalls();
}

Expand Down Expand Up @@ -55,7 +56,7 @@ public void ReceiveAsync_UnhandledException()
.Run(s => s.ReceiveAsync<Product>(message, actionsMock.Object, ed => throw new DivideByZeroException()))
.AssertSuccess();

actionsMock.Verify(m => m.DeadLetterMessageAsync(message, ErrorType.UnhandledError.ToString(), It.IsAny<string>(), default), Times.Once);
actionsMock.Verify(m => m.DeadLetterMessageAsync(message, It.IsAny<Dictionary<string, object?>>(), ErrorType.UnhandledError.ToString(), It.IsAny<string>(), default), Times.Once);
actionsMock.VerifyNoOtherCalls();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
using Moq;
using NUnit.Framework;
using System;
using System.Collections.Generic;
using UnitTestEx.Abstractions;
using UnitTestEx.NUnit;
using Az = Azure.Messaging.ServiceBus;
Expand All @@ -33,7 +34,7 @@ public void NotSubscribed()
.Run(f => f.RunAsync(message, actionsMock.Object))
.AssertSuccess();

actionsMock.Verify(m => m.DeadLetterMessageAsync(message, EventSubscriberExceptionSource.OrchestratorNotSubscribed.ToString(), It.IsNotNull<string>(), default), Times.Once);
actionsMock.Verify(m => m.DeadLetterMessageAsync(message, It.IsAny<Dictionary<string, object?>>(), EventSubscriberExceptionSource.OrchestratorNotSubscribed.ToString(), It.IsNotNull<string>(), default), Times.Once);
actionsMock.VerifyNoOtherCalls();
}

Expand Down Expand Up @@ -64,7 +65,7 @@ public void Product_ValueIsRequired()
.Run(f => f.RunAsync(message, actionsMock.Object))
.AssertSuccess();

actionsMock.Verify(m => m.DeadLetterMessageAsync(message, ErrorType.ValidationError.ToString(), It.IsNotNull<string>(), default), Times.Once);
actionsMock.Verify(m => m.DeadLetterMessageAsync(message, It.IsAny<Dictionary<string, object?>>(), ErrorType.ValidationError.ToString(), It.IsNotNull<string>(), default), Times.Once);
actionsMock.VerifyNoOtherCalls();
}

Expand All @@ -79,7 +80,7 @@ public void Product_ValueIsInvalid()
.Run(f => f.RunAsync(message, actionsMock.Object))
.AssertSuccess();

actionsMock.Verify(m => m.DeadLetterMessageAsync(message, ErrorType.ValidationError.ToString(), It.IsNotNull<string>(), default), Times.Once);
actionsMock.Verify(m => m.DeadLetterMessageAsync(message, It.IsAny<Dictionary<string, object?>>(), ErrorType.ValidationError.ToString(), It.IsNotNull<string>(), default), Times.Once);
actionsMock.VerifyNoOtherCalls();
}

Expand Down
13 changes: 7 additions & 6 deletions tests/CoreEx.Test/TestFunction/ServiceBusTriggerTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using Microsoft.Azure.WebJobs.ServiceBus;
using Moq;
using NUnit.Framework;
using System.Collections.Generic;
using System.Net;
using System.Net.Http;
using UnitTestEx.NUnit;
Expand All @@ -28,7 +29,7 @@ public void NullMessage()
.Run(f => f.RunAsync(message, actionsMock.Object))
.AssertSuccess();

actionsMock.Verify(m => m.DeadLetterMessageAsync(message, ErrorType.ValidationError.ToString(), It.IsNotNull<string>(), default), Times.Once);
actionsMock.Verify(m => m.DeadLetterMessageAsync(message, It.IsAny<Dictionary<string, object?>>(), ErrorType.ValidationError.ToString(), It.IsNotNull<string>(), default), Times.Once);
actionsMock.VerifyNoOtherCalls();
}

Expand All @@ -43,7 +44,7 @@ public void InvalidMessage()
.Run(f => f.RunAsync(message, actionsMock.Object))
.AssertSuccess();

actionsMock.Verify(m => m.DeadLetterMessageAsync(message, EventSubscriberExceptionSource.EventDataDeserialization.ToString(), It.IsNotNull<string>(), default), Times.Once);
actionsMock.Verify(m => m.DeadLetterMessageAsync(message, It.IsAny<Dictionary<string, object?>>(), EventSubscriberExceptionSource.EventDataDeserialization.ToString(), It.IsNotNull<string>(), default), Times.Once);
actionsMock.VerifyNoOtherCalls();
}

Expand All @@ -61,7 +62,7 @@ public void InvalidMessage_Newtonsoft()
.Run(f => f.RunAsync(message, actionsMock.Object))
.AssertSuccess();

actionsMock.Verify(m => m.DeadLetterMessageAsync(message, EventSubscriberExceptionSource.EventDataDeserialization.ToString(), It.IsNotNull<string>(), default), Times.Once);
actionsMock.Verify(m => m.DeadLetterMessageAsync(message, It.IsAny<Dictionary<string, object?>>(), EventSubscriberExceptionSource.EventDataDeserialization.ToString(), It.IsNotNull<string>(), default), Times.Once);
actionsMock.VerifyNoOtherCalls();
}

Expand All @@ -76,7 +77,7 @@ public void InvalidValue()
.Run(f => f.RunAsync(message, actionsMock.Object))
.AssertSuccess();

actionsMock.Verify(m => m.DeadLetterMessageAsync(message, ErrorType.ValidationError.ToString(), It.IsNotNull<string>(), default), Times.Once);
actionsMock.Verify(m => m.DeadLetterMessageAsync(message, It.IsAny<Dictionary<string, object?>>(), ErrorType.ValidationError.ToString(), It.IsNotNull<string>(), default), Times.Once);
actionsMock.VerifyNoOtherCalls();
}

Expand All @@ -94,7 +95,7 @@ public void InvalidValue_Newtonsoft()
.Run(f => f.RunAsync(message, actionsMock.Object))
.AssertSuccess();

actionsMock.Verify(m => m.DeadLetterMessageAsync(message, ErrorType.ValidationError.ToString(), It.IsNotNull<string>(), default), Times.Once);
actionsMock.Verify(m => m.DeadLetterMessageAsync(message, It.IsAny<Dictionary<string, object?>>(), ErrorType.ValidationError.ToString(), It.IsNotNull<string>(), default), Times.Once);
actionsMock.VerifyNoOtherCalls();
}

Expand Down Expand Up @@ -135,7 +136,7 @@ public void UnhandledError()
.AssertSuccess();

mc.Verify();
actionsMock.Verify(m => m.DeadLetterMessageAsync(message, ErrorType.UnhandledError.ToString(), It.IsNotNull<string>(), default), Times.Once);
actionsMock.Verify(m => m.DeadLetterMessageAsync(message, It.IsAny<Dictionary<string, object?>>(), ErrorType.UnhandledError.ToString(), It.IsNotNull<string>(), default), Times.Once);
actionsMock.VerifyNoOtherCalls();
}

Expand Down

0 comments on commit ae8a3bc

Please sign in to comment.