`FlushWorker.step` checked `getattr(response, "exception") != None` without a
default. On a network error the uploader returns a `Fake500` that carries an
`exception` attribute, but a genuine server HTTP 500 is a real
`requests.Response`, which has no such attribute. So on a real 500 the
`getattr` raised `AttributeError`, which propagated out of `step()`/`run()` and
killed the background flush thread — every subsequent log was then queued but
never sent for the life of the process.
Pass a default (`getattr(response, "exception", None) is not None`) so a real
Response skips the branch instead of crashing, while the network-error Fake500
path still logs as before.
Adds two regression tests: a real `requests.Response` 500 must not crash the
worker (fails on the old code with the exact AttributeError from logtail#25), and the
Fake500 network-error path must still log. Note: the existing tests use
`MagicMock(status_code=500)`, whose auto-vivified `.exception` masks the bug, so
the new test uses a real Response.
Fixes logtail#25.
Summary
Fixes #25.
FlushWorker.step decides whether to log an upload failure with:
getattr(response, "exception") is called without a default. The two things step can receive are asymmetric:
So on a real 500, status_code == 500 is True, then getattr(response, "exception") raises AttributeError. That propagates out of step() → run(), so the background flush thread dies. Every subsequent log is enqueued but never sent for the rest of the process — silent log loss. This matches the reporter's "happens occasionally" (real 500s are intermittent).
Fix
A real Response now yields None and skips the branch; the network-error Fake500 path still logs its exception as before. (Also switched != None → is not None to match idiom.)
Tests
Two regression tests in tests/test_flusher.py:
⚠️ Worth noting: the existing tests use mock.MagicMock(status_code=500), and MagicMock auto-vivifies .exception, which is why they never caught this — the new test deliberately uses a real Response.
Full tests/test_flusher.py suite passes (11 tests).