feat: one zip a user can actually produce — the diagnostics bundle - #2354
Merged
Merged
Conversation
Producing evidence for a bug took four manually-coordinated steps, and the last two were: leave the app, open %LOCALAPPDATA%\SysManager\logs\ in Explorer, and work out which of up to fourteen daily rolling files covers the moment the bug happened. The person most likely to hit a bug cannot do either. So their bug was not badly reported — it was unreportable (#1650). About now has "Save diagnostics bundle": one zip holding the system report, the environment block the tab already builds for the clipboard, and the three newest rolling logs, written wherever the user points the dialog. Every input already existed as a working service; this is composition. The privacy half is the part worth reading. The report inside is a SHARABLE variant with each adapter's MAC dropped and the IPv4 host part masked. That is not caution: this file is built specifically to be attached to a public issue, a MAC address is a permanent hardware identifier that survives a Windows reinstall, and nobody can withdraw one once it is in a GitHub thread. Neither it nor the exact host address answers any question a bug report asks. The full report is deliberately untouched — a file you keep is a different thing from one you hand to a stranger — and #2352, filed while building this, carries whether the existing exports should redact by default too. Redacted from the DATA, not by pattern-matching the rendered report. A generic IPv4 regex over the finished text would also match a four-part version string: 1.111.0.0 has four valid octets. It would have quietly rewritten the version line while claiming to protect an address. The logs need no such treatment — the sink already strips the Windows user name from every line, message, property and exception text alike, before it reaches disk — but the README inside says so, along with everything else in the zip, that nothing was uploaded, and that the logs record which tabs were opened and which cleanups were run. A bundle whose contents you have to take on trust is one a careful user will not send, and the careful users file the good bugs. Two decisions that look arbitrary and are not: Logs are picked by FILENAME, not by LastWriteTime. The names sort chronologically, and a backup tool or a sync client touching a file rewrites its timestamp — which would silently select three old logs and omit the one holding the failure. The unit test writes its fixtures oldest-last, so an implementation ordering by write time returns exactly the wrong three and fails. An oversized log is cut from its FRONT. A log reads in order, so the failure being reported is at the tail; truncating the other way round compiles, passes a size check, and reliably discards the only part anyone needs. The note that replaces the partial first line says how much went. Also: FileShare.ReadWrite on the log read, because the sink holds the current day's file open. Without it every bundle would have omitted today's log — the one most likely to contain the failure — and no size check would have noticed. 16 unit tests and 2 integration tests. The split is deliberate: WriteAsync reads WMI twice through the report service, so the packaging goes through an internal PackAsync that takes an already-gathered report, keeping the unit suite free of system dependencies while the redaction decision stays on the one path a caller can reach. The integration pair is the one that makes the bundle safe rather than intended to be safe: it asserts against this machine's real adapters, AND asserts the FULL report still carries them, so "nothing found" cannot pass for "something was removed". Neither failure message prints a value — they reach a public CI log, so a naive Assert.DoesNotContain would have published the exact identifier it exists to protect on the day it broke. Red ritual, eight mutations, each rebuilt before running and restored from saved bytes: the bundle packaging the full report; the MAC surviving redaction; masking becoming a no-op; logs ordered by write time; an oversized log keeping its head; the log opened without a write share; the README dropping its disclosure; and the command losing its only binding, which the reachability guard catches. The ritual also found a hole in one of my own tests. The truncation test put its only early marker on line one — which the implementation drops anyway, to remove the partial line a byte-offset read starts on — so a mutation that read the whole file and dropped just that line passed. A second marker further in, and an assertion on the returned SIZE, are what closed it. Two existing guards caught real problems in the new code and both were right: DiagnosticsBundleService wrote the zip in place (now AtomicFile — a torn zip the user attaches makes the round trip longer, not shorter), and its constructor documented its parameters without a summary. Unit 5815 passed, 0 failed. All four projects 0 warnings. dotnet format clean. No network code in the bundle service, asserted by grep as well as by reading. SECURITY.md moved to 1.111.x. Closes #1650 Refs #2352
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Producing evidence for a bug took four manually-coordinated steps, and the last two were: leave the app, open
%LOCALAPPDATA%\SysManager\logs\in Explorer, and work out which of up to fourteen daily rolling files covers the moment the bug happened.The person most likely to hit a bug cannot do either. So their bug was not badly reported — it was unreportable.
What changed
About has a "Save diagnostics bundle" button. One zip holding the system report, the environment block the tab already builds for the clipboard, and the three newest rolling logs, written wherever the user points the dialog. Every input already existed as a working service; this is composition, not new capability.
The privacy half, which is the part worth reading
The report inside is a sharable variant with each adapter's MAC dropped and the IPv4 host part masked. That is not caution. This file is built specifically to be attached to a public issue; a MAC address is a permanent hardware identifier that survives a Windows reinstall; and nobody can withdraw one once it is in a GitHub thread. Neither it nor the exact host address answers any question a bug report asks.
The full report is deliberately untouched — a file you keep is a different thing from one you hand to a stranger. #2352, filed while building this, carries the question of whether the existing exports should redact by default too.
Redacted from the data, not by pattern-matching the rendered report. A generic IPv4 regex over the finished text would also match a four-part version string:
1.111.0.0has four valid octets. It would have quietly rewritten the version line while claiming to protect an address.The logs need no such treatment — the sink already strips the Windows user name from every line, message, property and exception text alike, before it reaches disk — but the
README.txtinside says so, along with every file in the zip, that nothing was uploaded, and that the logs record which tabs were opened and which cleanups were run. A bundle whose contents you have to take on trust is one a careful user will not send, and the careful users file the good bugs.Two decisions that look arbitrary and are not
Logs are picked by filename, not by
LastWriteTime. The names sort chronologically, and a backup tool or a sync client touching a file rewrites its timestamp — which would silently select three old logs and omit the one holding the failure. The unit test writes its fixtures oldest-last, so an implementation ordering by write time returns exactly the wrong three and fails.An oversized log is cut from its front. A log reads in order, so the failure being reported is at the tail; truncating the other way round compiles, passes a size check, and reliably discards the only part anyone needs. The note replacing the partial first line says how much went.
Also
FileShare.ReadWriteon the log read, because the sink holds the current day's file open. Without it every bundle would have omitted today's log — the one most likely to contain the failure — and no size check would have noticed.Verification
16 unit tests, 2 integration tests. The split is deliberate:
WriteAsyncreads WMI twice through the report service, so the packaging goes through an internalPackAsyncthat takes an already-gathered report. That keeps the unit suite free of system dependencies (0.25s for all 16) while the redaction decision stays on the one path a caller can reach.The integration pair is what makes the bundle safe rather than intended to be safe. It asserts against this machine's real adapters, and asserts the full report still carries them — so "nothing found" cannot pass for "something was removed". Neither failure message prints a value: they reach a public CI log, so a naive
Assert.DoesNotContain(mac, report)would have published the exact identifier it exists to protect, on the day it broke.Red ritual — eight mutations, each rebuilt before running, each restored from saved bytes:
EveryViewModelCommand_IsReachableFromTheUiThe ritual found a hole in one of my own tests. The truncation test put its only early marker on line one — which the implementation drops anyway, to remove the partial line a byte-offset read starts on — so a mutation that read the whole file and dropped just that line passed. A second marker further in, plus an assertion on the returned size, are what closed it.
Two existing guards caught real problems in the new code, and both were right.
DiagnosticsBundleServicewrote the zip in place — nowAtomicFile, because a torn zip the user attaches makes the round trip longer rather than shorter — and its constructor documented its parameters without a<summary>.dotnet format --verify-no-changes: clean on app, unit and integration.Docs
README's About section describes the bundle, what it omits and why. ARCHITECTURE documents both the new service and
GenerateSharableReportAsync, including why the redaction happens on the data.SECURITY.mdmoved to1.111.x.Closes #1650
Refs #2352