chore: wire ILogger into MessageProducer (closes #222) - #260
Conversation
MessageProducer swallowed two exception paths behind placeholder TODOs: the per-message write failure and the background-loop guard. The project already depends on Microsoft.Extensions.Logging.Abstractions and injects ILogger elsewhere (FirmwareUpdateService), leaving MessageProducer as the last component on the silent-swallow pattern. - Inject an optional ILogger<MessageProducer<T>> (defaults to NullLogger), so existing single-arg consumers compile and behave unchanged. - Replace the per-write TODO with LogWarning(ex, ...) and the loop-guard TODO with LogError(ex, ...). - Emit LogInformation on a clean background-loop exit and LogError on an abnormal one; the last-resort handler guards its own logging call so a faulting logger can never crash the background thread. - Tests: assert a warning is logged when a write throws, that the no-logger path still silently continues, and that a clean stop logs an information-level lifecycle event. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
PR Summary by QodoWire ILogger into MessageProducer to surface background write failures Description
Diagram
High-Level Assessment
Files changed (2)
|
Code Review by Qodo
Context used 1.
|
Qodo review of #260 flagged a state-consistency bug: the prior commit guarded only the outer last-resort handler, so a logger that throws from LogWarning/LogError inside the inner handlers would still unwind ProcessMessages and return with _isRunning left true. That leaves a dead background thread while the producer reports IsRunning=true, so Send() keeps enqueueing messages that never drain and StopSafely() blocks until timeout. - Route every logger call in the loop through a SafeLog helper that swallows logger exceptions, so a faulting logger can no longer unwind the loop. - Clear _isRunning in the last-resort catch as defense-in-depth so the producer never advertises a running state with a dead thread. - Add a regression test: with an always-throwing logger and always-failing writes, the loop keeps draining, StopSafely() completes within the timeout, and the producer is not left running. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Response to Qodo review🐞 Bug #1 — "Faulting logger kills loop" — ✅ Agreed and fixed (commit 02e3486) Good catch — this goes one level deeper than the outer-handler hardening already in the PR. The original last-resort Implemented the suggested two-part fix:
Added a regression test ( Validation: build clean (0 warnings under No other findings to address — Qodo reported 1 bug, 0 rule violations, 0 requirement gaps, and endorsed the optional- |
Summary
Closes #222.
MessageProducer<T>swallowed two distinct exception paths behind placeholder// TODO: Add proper logging system in future stepcomments — the per-message write failure and the background-thread loop guard. That "future step" has effectively happened: the project already referencesMicrosoft.Extensions.Logging.Abstractionsand injectsILogger<FirmwareUpdateService>.MessageProducerwas the last major component still silently swallowing.Silent swallowing here is a debugging black hole: if writes start failing mid-stream, the only signal a desktop client sees is "samples stopped flowing." This surfaces those exceptions through
ILogger<MessageProducer<T>>without changing the public API for existing callers.Changes
ILogger<MessageProducer<T>>? logger = null, defaulting toNullLogger<MessageProducer<T>>.Instance. Nullable + default keeps the three existing single-arg call sites (DaqifiDevice,SerialDeviceFinder) source-compatible and behaviourally unchanged.LogWarning(ex, …)(keeps draining the queue); background-loop guard →LogError(ex, …)(keeps the loop running).LogInformationon a clean loop exit,LogErroron an abnormal one. The last-resort handler wraps its own logging call so a faulting logger can never escalate into an unhandled background-thread exception (which would crash the host process).Tests
MessageProducer_WhenWriteThrows_ShouldLogWarning— a throwing stream causes aWarningcarrying the originalIOException.MessageProducer_WithNoLogger_WhenWriteThrows_ShouldNotThrowToCaller— the default (no-logger) path still silently continues, proving unchanged behaviour for existing consumers.MessageProducer_WhenStoppedNormally_ShouldLogCleanExit— a normal stop emits the clean-exitInformationlog.No Moq in this project, so the tests use a small hand-written
CaptureLogger+ throwing stream, matching the existingErrorThrowingStreampattern.Out of scope
Plumbing
ILoggerthrough every other class (e.g.DaqifiDevice,SerialDeviceFinder) — this PR is specifically about closing theMessageProducerTODOs, per the issue.Validation
dotnet build— clean (0 warnings / 0 errors; the project builds withTreatWarningsAsErrors).dotnet test(net10.0) — 1167 passed, 2 skipped, 0 failed.Acceptance criteria
MessageProducerconstructor accepts an optionalILogger<MessageProducer<T>>.🤖 Generated with Claude Code