fix(#4196): forward dead-letter reason and description to Azure Service Bus native fields#4201
fix(#4196): forward dead-letter reason and description to Azure Service Bus native fields#4201DevJonny wants to merge 2 commits into
Conversation
…4196) AzureServiceBusConsumer.RejectAsync computed the rejection reason and description, logged them, then settled via the no-reason DeadLetterAsync overload — leaving Azure Service Bus's native DeadLetterReason and DeadLetterErrorDescription fields blank, so the reason existed only in logs and dead-letter queue triage had to pivot to log search. Add a DeadLetterAsync(lockToken, reason, description) overload to the public IServiceBusReceiverWrapper, implemented via the existing message shiv against the SDK's native DeadLetterMessageAsync overload (values truncated to the 4096-char ASB limit), and forward the reason RejectAsync already computes. The sync Reject path inherits the fix via BrighterAsyncContext.Run. Covered by developer tests for both the Proactor (async) and Reactor (sync) consumers; release note added. Co-Authored-By: Claude (claude-opus-4-8) <noreply@anthropic.com>
Code Review — PR #4201: forward dead-letter reason/description to ASB native fieldsThanks for this — it's a tight, well-scoped fix. The diagnosis (reason/description computed in Correctness ✅
Test coverage 🔶Per the repo's strict TDD stance, two introduced behaviors are unverified:
The two happy-path tests are clear and mirror each other well across Proactor/Reactor. Style nit
Minor edge case
Docs/notes ✅Release note is clear and accurately scoped; XML docs on both the interface and impl explain the 4096 cap. Overall: a solid bug fix that does exactly what #4196 asked. Main suggestion is adding a truncation test (and ideally a null-reason test) to satisfy the repo's TDD guardrails before merge. |
There was a problem hiding this comment.
Pull request overview
This PR fixes an Azure Service Bus DLQ observability gap by forwarding the already-computed rejection reason and description from AzureServiceBusConsumer.RejectAsync into Azure Service Bus’s native DeadLetterReason / DeadLetterErrorDescription fields (instead of only logging them and calling the no-reason dead-letter overload).
Changes:
- Add a new
DeadLetterAsync(lockToken, reason, description)overload toIServiceBusReceiverWrapperand implement it inServiceBusReceiverWrapperusing the native SDK dead-letter overload with 4096-char truncation. - Update
AzureServiceBusConsumer.RejectAsyncto call the new overload, preserving operator-visible DLQ fields. - Add reactor (sync) and proactor (async) tests verifying that reason/description are forwarded.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| tests/Paramore.Brighter.AzureServiceBus.Tests/MessagingGateway/Reactor/AzureServiceBusConsumerTests.cs | Adds sync-path test asserting dead-letter reason/description are forwarded. |
| tests/Paramore.Brighter.AzureServiceBus.Tests/MessagingGateway/Proactor/AzureServiceBusConsumerTestsAsync.cs | Adds async-path test asserting dead-letter reason/description are forwarded. |
| tests/Paramore.Brighter.AzureServiceBus.Tests/Fakes/FakeServiceBusReceiverWrapper.cs | Extends fake receiver wrapper to capture dead-letter reason/description for assertions. |
| src/Paramore.Brighter.MessagingGateway.AzureServiceBus/AzureServiceBusWrappers/ServiceBusReceiverWrapper.cs | Implements new dead-letter overload and truncates fields to ASB’s 4096-char limit. |
| src/Paramore.Brighter.MessagingGateway.AzureServiceBus/AzureServiceBusWrappers/IServiceBusReceiverWrapper.cs | Adds the new public dead-letter overload to propagate native DLQ fields. |
| src/Paramore.Brighter.MessagingGateway.AzureServiceBus/AzureServiceBusConsumer.cs | Switches RejectAsync to call the reason/description dead-letter overload. |
| release_notes.md | Documents the fix and the new interface overload. |
|
@DevJonny Can you peer at this feedback for me: "Test coverage 🔶 Per the repo's strict TDD stance, two introduced behaviors are unverified: The two happy-path tests are clear and mirror each other well across Proactor/Reactor." |
…field-length constant Add Proactor and Reactor tests asserting that rejecting a message with no reason forwards the default "DeliveryError"/"unknown" values to the dead-letter queue, closing the gap left by the populated-reason happy-path tests. Rename MAX_DEAD_LETTER_FIELD_LENGTH to MaxDeadLetterFieldLength to match the project's PascalCase constant convention. Co-Authored-By: Claude (claude-opus-4-8) <noreply@anthropic.com>
|
Thanks for the nudge @iancooper — facepalm, I'd already forgotten about these. 🤦 Done in the latest commit:
On truncation — leaving it untested deliberately. Also leaving the single-arg |
There was a problem hiding this comment.
Our agent can fix these. Install it.
Gates Passed
4 Quality Gates Passed
Quality Gate Profile: Clean Code Collective
Install CodeScene MCP: safeguard and uplift AI-generated code. Catch issues early with our IDE extension and CLI tool.
Code Review — fix(#4196): forward dead-letter reason/description to ASB native fieldsNicely scoped bug fix. The change does exactly what the issue asks: Strengths
Suggestions (non-blocking)
Verification
Overall: correct, minimal, well-tested at the consumer level. The only real gap is direct coverage of the truncation clamp. LGTM pending your call on that. |
Fixes #4196.
When a handler rejects a message consumed from Azure Service Bus, AzureServiceBusConsumer.RejectAsync computed the rejection reason and description, logged them, then settled via the no-reason DeadLetterAsync overload — leaving the broker's native DeadLetterReason / DeadLetterErrorDescription fields blank. Operators triaging the DLQ saw empty columns and had to pivot to logs and correlate by sequence number.
git blame shows the reason/description were introduced in #3918 (Universal DLQ) and wired only into the log line; the no-reason settle predates it and wasn't updated. As confirmed on #4196, this is an oversight, and forwarding to the native overload is the intended fix (consistent with ADR 0045's "defer to native").
Change
New DeadLetterAsync(lockToken, reason, description) overload on the public IServiceBusReceiverWrapper (per maintainer steer on ASB: rejected messages dead-lettered with blank reason/description #4196 — interface addition accepted; DIM not viable under netstandard2.0).
Implemented via the existing message shiv against the SDK's native DeadLetterMessageAsync, truncating to ASB's 4096-char limit.
RejectAsync forwards the values it already computes; sync Reject inherits via BrighterAsyncContext.Run.
Testing