Skip to content

#43 Log swallowed TryGetAsync exceptions - #52

Merged
matthewdevenny merged 2 commits into
mainfrom
matt/43-trygetasync
Jul 6, 2026
Merged

matthewdevenny merged 2 commits into
mainfrom
matt/43-trygetasync

Conversation

@matthewdevenny

@matthewdevenny matthewdevenny commented Jul 1, 2026 •

Copy link
Copy Markdown
Contributor

Closes #43.

Problem

NatsCache.TryGetAsync(string, IBufferWriter<byte>, …) caught all exceptions and returned false with no logging. A NATS connectivity error was indistinguishable from a normal cache miss and left no diagnostic trail.

What changed

TryGetAsync now logs a swallowed read failure, and the read path is reworked so each failure is logged exactly once.

  • Log at Warning. A swallowed read failure means the cache genuinely failed (e.g. NATS down), not just missed — Debug would be invisible in production. Reuses the existing Exception event id (101). Still returns false, preserving the IBufferDistributedCache contract.
  • Cancellation propagates. OperationCanceledException from a cancelled caller token is no longer swallowed into a false miss (and is not logged) — cooperative cancellation isn't a cache failure.
  • No redundant logging. Exception logging moved out of the shared helpers (the lazy KV-store factory and GetAndRefreshAsync) up to the operation boundaries, so each failure is logged once:
    • Propagating ops (Get/Set/Refresh/Remove) → Error, then rethrow.
    • TryGetAsync swallow → Warning.
  • Supporting mechanical changes: SetAsync resolves the store inside its logged try; the private remove helper is renamed RemoveCoreAsync to resolve an overload ambiguity when routing sync Remove through the logging public RemoveAsync.

Behavior notes

  • Remove failures are now consistently logged at Error (a DeleteAsync failure was previously logged inconsistently or not at all).
  • TryGetAsync now throws on caller cancellation instead of returning false — the idiomatic .NET behavior, and a deliberate change from the original "swallow everything" semantics.

Tests

New test/IntegrationTests/Cache/TryGetAsyncTests.cs, using an in-memory RecordingLogger<NatsCache> added to TestUtils (no new package dependency, so lock files are untouched):

  1. TryGetAsync on a nonexistent bucket → returns false, writes nothing, logs exactly one Warning.
  2. GetAsync on a nonexistent bucket → throws, logs exactly one Error (guards against the helpers double-logging).
  3. TryGetAsync with a cancelled token → throws OperationCanceledException, no exception logged.

Verification

  • Build clean with TreatWarningsAsErrors=true.
  • Unit tests 62/62 (net8.0 + net10.0); integration tests 61/61.
  • dotnet format --verify-no-changes passes.

Acceptance criteria

  • ✅ A thrown error in the buffer read path produces a log entry.
  • ✅ Return behavior is otherwise unchanged (still returns false on failure; cancellation now propagates by design).

@matthewdevenny
matthewdevenny requested a review from Copilot July 1, 2026 22:24
@matthewdevenny matthewdevenny changed the title #43 don't swallow TryGetAsync exceptions #43 Log swallowed TryGetAsync exceptions Jul 1, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds explicit logging for exceptions that are swallowed by NatsCache.TryGetAsync (to honor the IBufferDistributedCache “return false on failure” behavior) and introduces test utilities + an integration test to assert the behavior.

Changes:

  • Add a dedicated debug log method (LogSwallowedException) and call it from TryGetAsync’s exception handler.
  • Introduce an in-memory RecordingLogger<T> for capturing log entries in tests.
  • Add an integration test that forces a read-path failure and asserts TryGetAsync returns false and logs the swallowed exception.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

File Description
test/TestUtils/Services/Logging/RecordingLogger.cs Adds an in-memory test logger to capture ILogger calls for assertions.
test/IntegrationTests/Cache/TryGetAsyncTests.cs Adds an integration test asserting swallowed exceptions are logged and TryGetAsync returns false.
src/NatsDistributedCache/NatsCache.Log.cs Adds a new debug logging helper for swallowed exceptions.
src/NatsDistributedCache/NatsCache.cs Updates TryGetAsync to log swallowed exceptions.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/NatsDistributedCache/NatsCache.cs

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated no new comments.

matthewdevenny and others added 2 commits July 1, 2026 20:14
Signed-off-by: Matthew DeVenny <matt@codecargo.com>
Refine the TryGetAsync logging so a swallowed read failure is surfaced
without being logged twice, and handle cancellation intentionally.

- Log the swallowed exception at Warning (was Debug) so a real read
  failure such as NATS connectivity stays visible in production and is
  distinguishable from a normal cache miss.
- Propagate OperationCanceledException from a cancelled caller token
  instead of swallowing it into a false miss (and do not log it).
- Move exception logging out of the shared helpers (the lazy KV-store
  factory and GetAndRefreshAsync) up to the operation boundaries so each
  failure is logged exactly once: Error when it propagates to the caller
  (Get/Set/Refresh/Remove), Warning when TryGetAsync swallows it. This
  removes the previous double-log for connectivity errors.
- Rename the private remove helper to RemoveCoreAsync to resolve an
  overload ambiguity created by routing sync Remove through the logging
  public RemoveAsync.
- Expand the integration tests: swallow logs once at Warning, a
  propagating read logs once at Error, and cancellation propagates
  without being logged as an exception.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Matthew DeVenny <matt@codecargo.com>
@matthewdevenny
matthewdevenny marked this pull request as ready for review July 2, 2026 15:51
@matthewdevenny
matthewdevenny requested a review from mtmk July 2, 2026 15:51

@mtmk mtmk left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@matthewdevenny
matthewdevenny merged commit 6710ae5 into main Jul 6, 2026
2 checks passed
@matthewdevenny
matthewdevenny deleted the matt/43-trygetasync branch July 6, 2026 15:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Don't silently swallow errors in TryGetAsync

3 participants