Skip to content

feat(observability): structured logger + authmux diag (X3)#32

Merged
NagyVikt merged 1 commit into
mainfrom
agent/x3-observability
May 18, 2026
Merged

feat(observability): structured logger + authmux diag (X3)#32
NagyVikt merged 1 commit into
mainfrom
agent/x3-observability

Conversation

@NagyVikt
Copy link
Copy Markdown
Collaborator

Theme X3 — Observability v1: structured logs + authmux diag.

Reference: docs/future/17-ROADMAP.md (Next horizon, Theme X3).

Summary

  • src/infra/log/logger.ts — zero-dep structured logger. info/warn/error/debug. Default = human single-line on stderr; AUTHMUX_LOG=json switches to JSON-lines. AUTHMUX_LOG_LEVEL filters. logger.child({correlationId}) returns a child logger that injects the id into every event. newCorrelationId() returns a hex crypto.randomBytes(8) string. In-memory ring buffer keeps the last 200 JSON events for authmux diag.
  • Daemon evaluation cycles in auto-switch/policy.ts now emit paired daemon.cycle.start / daemon.cycle.end events sharing one correlation id. End event includes action (noop/switched/error), ms, and either reason or from/to.
  • src/commands/diag.ts — new authmux diag command. Writes authmux-diag-<ts>.tgz to cwd containing summary.txt, bundle.json, and log-tail.jsonl. Bundle includes authmux version, node, platform, lazily-resolved paths, accounts dir listing (filenames + sizes + mtimes ONLY — never contents), allowlisted env, and the last 200 ring-buffer log lines.

Exit criteria

  • src/infra/log/logger.ts exports a logger with info/warn/error/debug methods, no transitive deps.
  • Daemon evaluation cycles emit one structured event per cycle with a correlation id (start + end paired).
  • authmux diag writes authmux-diag-<ts>.tgz to cwd containing version, env table (filtered), ~/.codex/accounts/ listing (no contents), the last 200 log lines.
  • Documented redaction list: never include auth.json or snapshot bytes.

Redaction allowlist

literal:
  NODE_ENV, NODE_VERSION, PATH, HOME, SHELL, TERM, OS, PLATFORM

prefixes:
  CODEX_AUTH_*, AUTHMUX_*

forbidden suffixes (filtered even when prefix matches):
  _TOKEN, _KEY, _PASSWORD, _SECRET, _COOKIE
  (plus bare TOKEN/KEY/PASSWORD/SECRET/COOKIE)

special:
  HOME -> "<set, len=N>" (literal path value never emitted)
  PATH -> truncated at 256 chars

Snapshot bytes, auth.json contents, and registry/sessions contents are
never read by diag. The accounts dir listing comes from fsp.readdir +
fsp.stat only.

Verification

npm run build  -> clean
npm test       -> tests 135, pass 135, fail 0

AUTHMUX_LOG=json node -e "logger.info('test',{k:'v'}); logger.warn('warn',{})"
  {"level":"info","ts":"...","msg":"test","k":"v"}
  {"level":"warn","ts":"...","msg":"warn"}

node dist/index.js diag
  wrote diag bundle: /tmp/authmux-diag-<ts>.tgz (1975 bytes)

OPENAI_API_KEY=sk-leak MY_TOKEN=secret node dist/index.js diag --print-env | grep -E 'sk-leak|MY_TOKEN'
  -> OK redacted

Tarball layout:

authmux-diag-<ts>/
  summary.txt
  bundle.json
  log-tail.jsonl

Test plan

  • npm run build clean
  • npm test — 135/135
  • Logger JSON mode produces one event per line on stderr
  • authmux diag writes a .tgz to cwd
  • authmux diag --print-env does not leak OPENAI_API_KEY or MY_TOKEN
  • New diag-redaction.test.ts asserts the leak guard
  • New logger.test.ts asserts ring buffer cap of 200 and correlation IDs

Theme X3 (Observability v1) from docs/future/17-ROADMAP.md.

- src/infra/log/logger.ts: zero-dep structured logger with
  debug/info/warn/error levels. Default human single-line on stderr;
  AUTHMUX_LOG=json switches to JSON-lines. AUTHMUX_LOG_LEVEL filters.
  child({correlationId}) injects an ID into every event;
  newCorrelationId() returns a 16-hex string. An in-memory ring buffer
  keeps the last 200 JSON events for diag.
- Daemon evaluation cycles emit paired daemon.cycle.start /
  daemon.cycle.end events with a shared correlation id, action, and
  duration (no auth bytes).
- src/commands/diag.ts: new "authmux diag" command writing
  authmux-diag-<ts>.tgz to cwd. Bundle contains version, node, platform,
  paths, accounts dir listing (filenames + sizes + mtimes ONLY — never
  contents), allowlisted env, and the last 200 log lines. Env table is
  allowlist-only (CODEX_AUTH_*, AUTHMUX_*, plus PATH/HOME/SHELL/TERM/
  NODE_ENV/...); credential-shaped suffixes (_TOKEN/_KEY/_PASSWORD/
  _SECRET/_COOKIE) are filtered even if the prefix matches. HOME's
  literal value never appears — only its length stand-in.
- src/tests/logger.test.ts and src/tests/diag-redaction.test.ts:
  ring buffer, correlation IDs, JSON line shape, and the leak-guard
  proving OPENAI_API_KEY / MY_TOKEN / DB_PASSWORD / *_SECRET / *_KEY
  never reach the output while CODEX_AUTH_DEBUG / AUTHMUX_LOG do.

Redaction list (never in bundle): auth.json, snapshot file contents,
any env name matching the FORBIDDEN_SUFFIXES list, raw HOME value.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@NagyVikt NagyVikt merged commit 11abf3a into main May 18, 2026
12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant