TASK 55 slice 1: the startup log is size-capped

The creator (2026-10-09): 'we need some sort of log-rotation so that we don't
inadvertently fill up a user's drive.' startup.log was uncapped - 23MB and growing
every few seconds, because FfmpegEncoder logs every non-progress ffmpeg stderr line.

The policy lives in a new Helpers/LogRotation.cs as a pure function over a path
(RollIfNeeded(logPath, maxBytes, retainedFiles)), so AppLog's 156 call sites keep
their surface and the rule is testable on its own. At 5MB the live file rolls to
startup.1.log and one rolled copy is kept - the FILE SET is bounded, not just the
live file, so a long stream cannot grow the log without limit. A failed rotation
degrades to the old behaviour instead of taking the app down.

Pattern reference (cited): Serilog's file sink pairs fileSizeLimitBytes (the cap)
with retainedFileCountLimit (how many rolled files to keep) -
https://github.com/serilog/serilog-sinks-file

Guard: ytLive.Tests/AppLogRotationTests.cs - the ONE test drives the real
AppLog.Write against a temp dir (two internal seams: PathOverride, MaxBytes) and
asserts the roll, the fresh live file, and that after a SECOND roll the directory
still holds exactly startup.log + startup.1.log.

Also fixed a stale catalog row: TASK 12 (master limiter) shipped 2026-08-15 and was
still marked 'Queued'.
This commit is contained in:
2026-10-22 06:51:04 -07:00
parent 826cc17e47
commit b1d6034662
9 changed files with 201 additions and 14 deletions
+22
View File
@@ -2298,3 +2298,25 @@ so the honest report was "4 hits, all false positives", not "4 leaks".
**Never print a matched value — but still say WHAT it is.** The report format that satisfies both:
commit + path + line + rule + verdict. "README.md:79 | assigned-credential | placeholder" is
actionable; the same report without the verdict forces the reader to go look at the secret.
## 2026-10-22 — CAP A LOGGER'S GROWTH WITHOUT TOUCHING ITS CALL SITES (RECIPE)
`AppLog` has **156 call sites** and one job; its log had no size cap (23 MB, growing every few
seconds — ffmpeg logs every stderr line). The instinct is to add a cap parameter to the logger
and change the callers. Don't.
**The recipe:**
1. **Put the POLICY in a separate pure function over the path**, not in the logger:
`LogRotation.RollIfNeeded(logPath, maxBytes, retainedFiles)`. The logger calls it before each
append; its public surface (`AppLog.Write`) never changes, so no caller is touched.
2. **Bound the FILE SET, not just the live file.** Rolling the live file alone (`startup.log` →
`startup.1.log`) still grows forever if the rolled copy is never discarded. Shift the set:
`name.1.log` → `name.2.log` → … → `name.N.log`, then the live file → `name.1.log`. Keep the
rolled count explicit (`retainedFiles`) and assert the directory's file list in the test —
that assertion is the one that proves the growth is bounded.
3. **Test the logger, not just the helper** — otherwise the helper can be perfect and unwired.
Two `internal` seams on the logger (`PathOverride`, `MaxBytes`) + the project's existing
`InternalsVisibleTo("ytLive.Tests")` let the test drive the real `AppLog.Write` against a
temp directory. Never let the test write to the real `%APPDATA%` log.
4. **Never throw from the rotation**: a failed rotation must degrade to the old behaviour
(the log keeps growing), never take the app down. Same rule as the logger itself.