Send diagnostics to stderr and stop dropping log attributes (closes #82)
All checks were successful
check / check (pull_request) Successful in 2m34s
All checks were successful
check / check (pull_request) Successful in 2m34s
Two defects in internal/log, fixed together because both live in the handler construction path. Logger on stdout (#82). Initialize built both handlers over os.Stdout. Every --json subcommand writes its document to that same stream, and WARN/ERROR are never suppressed by any flag, so a log record could land inside a JSON document and break the parse. This was not theoretical: a config file with group- or world-readable permissions triggers a WARN during startup, which was enough to make `snapshot list --json | jq` fail. Diagnostics now go to stderr, and the TTY/JSON format choice follows stderr's TTY-ness rather than stdout's -- testing the wrong stream would colorize records on a redirected stderr whenever stdout happened to be a terminal, and emit JSON to a terminal in the reverse case. This is user-visible: --verbose and --debug output moves to stderr as well, so `vaultik snapshot list -v > out.txt` no longer captures the diagnostics. README.md documents the split under a new "stdout and stderr" section. --quiet and --cron semantics are untouched: level selection is unchanged, and warnings and errors are still emitted in both modes. It also retires the local workaround in internal/vaultik/snapshot_list.go. warnWhileListing had been hand-rolling structured-log formatting to reach a writer that was not stdout, and the jsonOutput parameter threaded through the remote-listing helpers existed only to choose between the two writers; both are gone, and those warnings go through log.Warn in every mode. The collect-then-emit machinery around listingWarning stays, on its remaining merit rather than its original one: slog handlers are safe for concurrent use, but emitting from the manifest-fetch workers would order warnings by network timing, where collecting and emitting in key order after group.Wait makes two runs over the same damaged store produce the same diagnostics in the same order. TTYHandler dropped attributes (#97). WithAttrs and WithGroup discarded their arguments and returned the receiver, while their doc comments claimed the opposite. Because the handler is chosen by TTY-ness, this failed only on a terminal and worked correctly in CI -- so it broke exactly when someone was debugging interactively. Both now return a new handler: the receiver is never written to, since slog permits a handler to be shared and derived from concurrently, and the derived handler copies its slices rather than reslicing so two concurrent derivations cannot overwrite each other's attributes. The mutex became a pointer so handlers sharing a stream keep sharing one lock. Attributes persist across every subsequent record, and grouping is implemented as dotted key prefixes, which is the only honest rendering for a format with nowhere to nest. Tests: an attribute attached through the exported log.With reaches TTYHandler output; attributes persist across records; groups qualify keys; deriving does not leak between siblings or back to the parent; sixteen goroutines derive from and write through one handler under -race; and the TTY and JSON handlers are fed identical derivation chains and compared attribute set by attribute set, which is the test that would have caught the original defect and the one that stops the two paths drifting again. All of these fail against the unfixed handler. The existing snapshot-list tests that asserted these warnings on an injected writer now capture the process's real stderr, which is where they go; the assertions are otherwise unchanged.
This commit is contained in:
34
README.md
34
README.md
@@ -113,11 +113,32 @@ vaultik version
|
||||
### global flags
|
||||
|
||||
* `--config <path>`: Path to config file (default: `$VAULTIK_CONFIG`, then platform config dir, then `/etc/vaultik/config.yml`)
|
||||
* `--verbose`, `-v`: Enable verbose output
|
||||
* `--debug`: Enable debug output
|
||||
* `--verbose`, `-v`: Enable verbose output (on stderr — see below)
|
||||
* `--debug`: Enable debug output (on stderr — see below)
|
||||
* `--quiet`, `-q`: Suppress non-error output (also suppresses startup banner)
|
||||
* `--skip-errors`: Continue past per-file errors instead of aborting (applies to `snapshot create` and `restore`)
|
||||
|
||||
### stdout and stderr
|
||||
|
||||
Log output — everything from `--verbose` and `--debug`, and every
|
||||
warning and error the logger emits — goes to **stderr**. stdout carries
|
||||
the output you asked for: tables, and the documents produced by `--json`.
|
||||
|
||||
This means `vaultik snapshot list --verbose > out.txt` captures the
|
||||
listing and leaves the diagnostics on your terminal. To capture both,
|
||||
redirect stderr as well (`> out.txt 2> log.txt`, or `> out.txt 2>&1` to
|
||||
interleave them).
|
||||
|
||||
The split is what makes `--json` usable from a script. Warnings and
|
||||
errors are never suppressed — not by `--quiet`, not by `--cron` — so a
|
||||
logger on stdout would eventually land a log line inside a JSON
|
||||
document and break the parse. A config file with group- or
|
||||
world-readable permissions is enough to trigger it.
|
||||
|
||||
Format follows the stream: when stderr is a terminal the records are
|
||||
colorized one-liners, and when it is redirected or piped they are
|
||||
JSON, one object per line.
|
||||
|
||||
### environment variables
|
||||
|
||||
* `VAULTIK_AGE_SECRET_KEY`: Age private key for decryption (required for `snapshot restore` and `snapshot verify --deep`)
|
||||
@@ -208,8 +229,9 @@ local index alone, and still exits zero.
|
||||
(whether the snapshot is in the local index), `remote_key` (the full
|
||||
64-character storage key), and `remote_present` (whether it was seen
|
||||
on the destination store, or `null` if the destination could not be
|
||||
listed). The warning about an unlistable destination goes to stderr
|
||||
so stdout stays a single parseable document.
|
||||
listed). Warnings about an unlistable destination, unreadable
|
||||
manifests, and a truncated listing all go to stderr through the
|
||||
logger, so stdout stays a single parseable document.
|
||||
|
||||
**`snapshot verify`**: Verify snapshot integrity.
|
||||
* Default (shallow): checks that all blobs referenced in the manifest exist in storage
|
||||
@@ -504,6 +526,10 @@ All user-facing output goes through helpers in `internal/ui` and conforms
|
||||
to a uniform style. Color is enabled when stdout is a TTY and the
|
||||
`NO_COLOR` environment variable is unset (https://no-color.org/).
|
||||
|
||||
`internal/ui` writes to stdout; it is the output the user asked for.
|
||||
Structured log records are a different thing and go through
|
||||
`internal/log`, which writes to stderr (see "stdout and stderr" above).
|
||||
|
||||
Message classes:
|
||||
|
||||
| Class | Marker | Alignment | Use for |
|
||||
|
||||
Reference in New Issue
Block a user