Title
feat(audit): always-on persistent structured log file
Output: what changes
Today — all log output goes to stderr only. When the terminal closes, it's gone.
After this issue — every crane invocation also writes a JSON Lines log file:
$ crane export -n my-app -e ./export
... (normal console output) ...
$ cat .crane-audit.log
{"ts":"2026-04-18T14:32:01.123Z","level":"info","cmd":"export","msg":"Starting export","namespace":"my-app"}
{"ts":"2026-04-18T14:32:01.456Z","level":"info","cmd":"export","msg":"Discovered 127 API resource types"}
{"ts":"2026-04-18T14:32:02.789Z","level":"warn","cmd":"export","msg":"Cannot list resource","kind":"ConfigMap","error":"Forbidden"}
{"ts":"2026-04-18T14:32:12.345Z","level":"info","cmd":"export","msg":"Export complete","exported":47,"failed":2,"duration":"11.2s"}
The file captures all log levels (including Debug) regardless of console verbosity. Support engineers get full detail without needing --debug.
Description
Context
Console logs are ephemeral. Support cases require log replay. The audit log is a logrus Hook attached in GetLogger() that writes every log entry as a JSON Lines file. It is always active — --audit-log exists only to override the default path.
User outcome
- Every run produces a persistent, grep-able, jq-parseable log file.
- No existing command code needs changes — the hook captures automatically.
Scope
- Implement logrus file hook in
internal/audit/audit_logger.go.
- Attach hook in
GetLogger() (always-on).
- Add
--audit-log flag for path override (default: .crane-audit.log).
Acceptance criteria
Title
feat(audit): always-on persistent structured log fileOutput: what changes
Today — all log output goes to stderr only. When the terminal closes, it's gone.
After this issue — every
craneinvocation also writes a JSON Lines log file:The file captures all log levels (including Debug) regardless of console verbosity. Support engineers get full detail without needing
--debug.Description
Context
Console logs are ephemeral. Support cases require log replay. The audit log is a logrus
Hookattached inGetLogger()that writes every log entry as a JSON Lines file. It is always active —--audit-logexists only to override the default path.User outcome
Scope
internal/audit/audit_logger.go.GetLogger()(always-on).--audit-logflag for path override (default:.crane-audit.log).Acceptance criteria
crane export/transform/applyrun creates.crane-audit.log(or custom path via--audit-log).go test ./...passes.