Skip to content

Add -v/--verbose wire-logging flag to the CLI#3

Merged
jramos merged 6 commits into
masterfrom
develop
Apr 21, 2026
Merged

Add -v/--verbose wire-logging flag to the CLI#3
jramos merged 6 commits into
masterfrom
develop

Conversation

@jramos
Copy link
Copy Markdown
Owner

@jramos jramos commented Apr 20, 2026

Summary

Adds a -v / --verbose flag to bin/cgminer_api_client that prints the JSON request and raw response to stderr before the formatted result goes to stdout. Removes the "edit the binary to add a puts" debug ritual for operators trying to understand why a cgminer verb isn't behaving.

  • Miner#initialize and MinerPool#initialize get an optional on_wire: kwarg — a Proc with signature (direction, host, port, payload). Default nil is a no-op, so every existing caller is unaffected. Library code itself never writes to stderr (honoring the AGENTS.md rule).
  • The CLI wraps $stderr in a Mutex and provides the callback when -v is set. MinerPool's per-miner thread fan-out can produce responses larger than PIPE_BUF (4 KiB on Linux, 512 B on macOS), so the mutex keeps each request/response pair contiguous. Every line is prefixed with host:port so operators can grep a single miner out of the mixed stream.
  • Parsed via OptionParser#permute!, so -v summary, --verbose summary, and summary -v all work.
  • Passwords in addpool, and values in setconfig, ascset, pgaset are replaced with [REDACTED] in the log output. An integration spec against FakeCgminer's on_request: hook pins the invariant: the wire payload still carries the real secret, so a future refactor can't silently start shipping [REDACTED] to cgminer.
  • DEBUG=1 behavior is unchanged — still controls the one-shot backtrace on top-level errors only.

Test plan

  • bundle exec rake (rspec + rubocop) — 221 examples, 0 failures, 0 offenses
  • Unit specs for the on_wire callback, redaction across all four commands, method_missing path, escape-under-redaction, and the on_wire: nil default
  • Integration spec confirming the real wire bytes still contain the unredacted secret even when the log shows [REDACTED]
  • CLI integration specs covering -v <cmd>, --verbose <cmd>, <cmd> -v (permute), silent stderr without the flag, and per-miner prefix on multi-miner fan-out
  • Manual smoke against a local FakeCgminer (documented commands in the plan)

Commits

  • 8a7fbbe Add on_wire callback to Miner and MinerPool with secret redaction
  • d61616c Add -v/--verbose flag to the CLI
  • bb50148 Document --verbose in README and CHANGELOG

jramos added 6 commits April 20, 2026 16:47
Introduces an optional `on_wire:` kwarg on `Miner#initialize` and
`MinerPool#initialize` that takes a Proc of shape
`(direction, host, port, payload)` where direction is `:request`,
`:response`, or `:response_repaired`. Default `nil` is a no-op, so every
existing caller is unaffected.

`Miner#query` now builds two requests — the real one sent on the wire
and a loggable copy — and the on_wire callback only ever sees the
loggable copy. Parameters at known positional indices are redacted in
the loggable copy: `addpool` password, `setconfig` value, and
`ascset`/`pgaset` values. An integration spec against FakeCgminer's
on_request hook pins the important invariant: the wire payload still
carries the real secret even when the log shows `[REDACTED]`, so a
future refactor cannot silently start shipping the redacted form to
cgminer.

Library code does not write to stderr. The CLI (next commit) plugs
`$stderr` in.
Prints the JSON request and raw response to stderr for wire-level
debugging. The formatted result still goes to stdout unchanged, so
piping through the CLI is unaffected; the log stream is cleanly
separable. Each line carries a `host:port` prefix so operators can
`grep` a single miner out of a multi-miner fan-out.

Parsed via `OptionParser#permute!` so the flag works before or after
the command (`cgminer_api_client -v summary` or `summary -v`). Writes
happen inside a closed-over `Mutex` to prevent interleaving when
per-miner threads produce responses larger than PIPE_BUF.

`DEBUG=1` behavior is unchanged — that env var still only controls the
one-shot backtrace on top-level errors.
New CLI usage block under "Exit Codes and Streams" showing the
request/response log format, plus a note that addpool/setconfig/
ascset/pgaset secrets are redacted in the log. CHANGELOG gets matching
Added entries for both the CLI flag and the `on_wire:` library kwarg.
Three production hazards in the path introduced by PR #3:

1. `@on_wire&.call` propagated callback exceptions out of
   `Miner#perform_request`, breaking real queries when the operator's
   callback raised. Route all three call sites through a
   `safe_on_wire` helper that rescues `StandardError` and swallows —
   wire logging is best-effort telemetry, not a query precondition.

2. `OptionParser#permute!` raised on unknown flags (e.g. `-x`) with
   no handler, crashing the CLI before the usage banner printed.
   Rescue `OptionParser::ParseError`, print the message + USAGE, exit
   64. Pre-PR behavior was to pass the unknown flag to cgminer as a
   positional arg; the new exit-64 path is more conventional.

3. `warn` inside the mutex-wrapped wire-log lambda raised
   `Errno::EPIPE` / `IOError` when stderr was closed mid-run, aborting
   the per-miner query thread. Rescue inside the lambda; dropping a
   log line is strictly better than tearing down the fan-out.

Two specs added: an `on_wire` callback that raises (miner keeps
returning) and an unknown-flag CLI invocation (exit 64 + usage).
Three behaviors that the existing suite would have silently let a
refactor break:

- :response_repaired: no spec forced the `}{` / `[,{` gsub to actually
  mutate the response, so the callback emission at
  miner.rb:113-117 and the CLI's `wire_prefix[:response_repaired]`
  lookup were both untested. Added a unit spec that feeds a
  `{"foo":[{"a":1}{"b":2}]}` fixture through a stubbed socket and
  asserts both callbacks fire with the right payload shapes.

- :response with control bytes: the existing `ok_response` has no
  bytes < 0x20, so pre- and post-escape were identical and a refactor
  that moved the callback above the `chars.map` escape would pass.
  Added a spec asserting the callback payload contains the
  `\uXXXX`-escaped form, not the raw byte.

- MinerPool non-nil on_wire pass-through: the pool spec only covered
  the nil-default case. A wrapping refactor could silently give each
  Miner a distinct wrapper, breaking the CLI mutex's pool-wide scope.
  Added an identity-pass-through spec.
- The mutex rationale in bin/cgminer_api_client cited PIPE_BUF as the
  reason writes could tear mid-line. PIPE_BUF atomicity applies to
  pipes/FIFOs, not stderr generally; the real reason is that Ruby's
  `warn` / `$stderr.write` isn't atomic across threads for
  arbitrary-length payloads. Reword the comment to teach the right
  invariant.

- The on_wire describe block in miner_spec.rb claimed
  "perform_request is stubbed." It isn't — stub_wire only stubs
  open_socket, and perform_request runs end-to-end. That distinction
  matters when debugging failures (a breakpoint in perform_request
  actually hits). Reword to reflect reality.

- One example was named "redacts when invoked via method_missing
  sugar" but addpool is defined via Privileged::Pool#addpool —
  method_missing is never triggered. Rename and rephrase the comment
  so a reader isn't sent hunting for a method_missing path that
  doesn't exist.

Also add a one-liner to REDACTED_PARAM_INDEX's docstring reminding
future contributors to register new secret-bearing admin commands.
@jramos jramos merged commit f6e96a2 into master Apr 21, 2026
10 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