aws / aws/aws-dotnet-messaging
HandlerInvoker.LogError does not include the exception when handler resolution fails
- Dominant language
- C#
- Stars
- 143
- Forks
- 27
- Avg merge
- 1d 18h
- Merged PRs (30d)
- 4
Description
### Description
When `HandlerInvoker` catches a DI resolution failure, the actual exception (with root cause and stack trace) is **never logged** in the SQS poller path. This makes production debugging extremely difficult — we spent significant time tracking down a constructor exception that was completely invisible in Datadog/CloudWatch.
### The full chain
**Step 1 — `HandlerInvoker.InvokeAsync`**: Catches the DI exception, logs a generic message **without the exception object**, then wraps it in `InvalidMessageHandlerSignatureException`:
```csharp
catch (Exception e)
{
_logger.LogError(
"Unable to resolve a handler for {HandlerType} while handling message ID {MessageEnvelopeId}.",
subscriberMapping.HandlerType, messageEnvelope.Id);
// ^^^ no `e` parameter — exception not in the log
throw new InvalidMessageHandlerSignatureException("...", e);
}
```
**Step 2 — `DefaultMessageManager.InvokeHandler`**: Catches `InvalidMessageHandlerSignatureException` and **re-throws without logging**:
```csharp
catch (InvalidMessageHandlerSignatureException)
{
throw; // no logging
}
```
**Step 3 — `DefaultMessageManager.ProcessMessageAsync`**: Has **no catch block**. Exception propagates.
**Step 4 — `SQSMessagePoller.ProcessInStandardMode`**: **Fire-and-forgets the task**:
```csharp
_ = _messageManager.ProcessMessageAsync(result.Envelope, result.Mapping, token);
```
The exception becomes an **unobserved task exception** that .NET silently ignores. The inner exception (the actual root cause) is lost forever.
### What you see in logs
Only this — for every failed message, forever:
> Unable to resolve a handler for {HandlerType} while handling message ID {MessageEnvelopeId}.
No stack trace, no inner exception, no indication of whether it's a missing DI registration, a constructor throwing, a scoping issue, or anything else.
### Expected behavior
At minimum, the `LogError` call in `HandlerInvoker` should include the exception:
```csharp
_logger.LogError(e,
"Unable to resolve a handler for {HandlerType} while handling message ID {MessageEnvelopeId}.",
subscriberMapping.HandlerType, messageEnvelope.Id);
```
Ideally, the fire-and-forget in `SQSMessagePoller` should also handle exceptions from the discarded task (e.g., `ContinueWith` to log).
### Note on Lambda vs SQS paths
In the **Lambda path** (`DefaultLambdaMessageProcessor`), tasks are collected and awaited, and the catch block logs `Exception ex` — so the inner exception IS visible there. This inconsistency means the same handler failure is debuggable in Lambda but invisible in ECS/SQS poller deployments.
### Impact
- There is no stack trace, no inner exception, no indication of what actually failed
- The error message is identical whether the failure is a missing DI registration, a constructor exception, a scoping issue, or anything else
- Operators have to guess or reproduce locally to find the root cause
- This specifically affects the SQS poller path (the most common deployment pattern for long-running workers)
### Additional context
- PR #169 (fixing #166) added the catch + re-throw logic but missed passing the exception to the logger
- `DefaultMessageManager` inconsistently handles this — generic `Exception` is logged with the exception object, but `InvalidMessageHandlerSignatureException` is specifically re-thrown without logging
### Environment
- `AWS.Messaging` version: 1.2.0
- .NET 10.0
Contributor guide
Assessment
This issue has not been assessed yet.