|
1 | 1 | --- |
2 | | -summary: Resolve `_MemoryLoggerFactoryConfig.log_stream` at bootstrap instead of at import, so structlog output follows a stdout the process rebinds after importing `lite_bootstrap` (the root-logger handler already does). |
| 2 | +summary: `_MemoryLoggerFactoryConfig.log_stream` now resolves `sys.stdout` through a `default_factory`, so the memory logger binds the stream at bootstrap instead of at import and structlog output follows a stdout the process rebinds — matching the root-logger handler installed at the same moment. |
3 | 3 | --- |
4 | 4 |
|
5 | 5 | # Change: Bind the memory logger's stream at bootstrap, not at import |
@@ -54,21 +54,34 @@ between import and bootstrap. |
54 | 54 |
|
55 | 55 | - `lite_bootstrap/instruments/logging_factory.py` — `log_stream` gains a `default_factory`. |
56 | 56 | - `tests/instruments/test_logging_instrument.py` — test added. |
| 57 | +- `architecture/instruments.md` — records the bootstrap-time binding. |
| 58 | +- `planning/releases/1.4.0.md` — bug-fix entry (1.4.0 is written but not yet tagged). |
57 | 59 |
|
58 | 60 | ## Verification |
59 | 61 |
|
60 | | -- [ ] Failing test first: bootstrap a `FreeBootstrapper` inside |
| 62 | +- [x] Failing test first: bootstrap a `LoggingInstrument` inside |
61 | 63 | `contextlib.redirect_stdout(io.StringIO())`, log one line, assert it lands |
62 | | - in the buffer. Command: `just test -k "log_stream"`. Expected failure: the |
63 | | - buffer is empty because the line went to the import-time stdout. |
64 | | -- [ ] Apply the change. |
65 | | -- [ ] Test passes — `just test -k "log_stream"`. |
66 | | -- [ ] `just test` — full suite green, coverage still 100%. |
67 | | -- [ ] `just lint` — clean. |
| 64 | + in the buffer. Command: `just test -k "binds_log_stream"`. Observed failure: |
| 65 | + the buffer was empty while pytest's own captured stdout held the line. |
| 66 | +- [x] Apply the change. |
| 67 | +- [x] Test passes — `just test -k "binds_log_stream"`. |
| 68 | +- [x] `just test` — 230 passed, coverage 100%. |
| 69 | +- [x] `just lint-ci` — clean. |
| 70 | + |
| 71 | +The test drives `LoggingInstrument` directly rather than `FreeBootstrapper`: it |
| 72 | +lives in `tests/instruments/test_logging_instrument.py` next to the other |
| 73 | +factory tests, and the instrument is what owns the stream. |
68 | 74 |
|
69 | 75 | ## Notes |
70 | 76 |
|
71 | 77 | Found while fixing the Litestar access-log body leak |
72 | 78 | (`planning/changes/2026-08-10.01-litestar-middleware-logging.md`), which is |
73 | 79 | where the test-capture consequence is documented. Deliberately left out of that |
74 | 80 | change to keep a security fix unencumbered. |
| 81 | + |
| 82 | +That consequence is now obsolete: with the stream resolved at bootstrap, |
| 83 | +`capsys` does observe structlog output in a test that bootstraps inside the test |
| 84 | +body (verified). `2026-08-10.01`'s Testing section describes what was true when |
| 85 | +it shipped and is left as written — the access-logging tests keep recording |
| 86 | +through a handler on the `litestar` logger, which is more precise than reading |
| 87 | +captured stdout and does not depend on pytest's capture mode. |
0 commit comments