Bidirectional stdio proxy/logger. Sits between a parent process and a child command, forwarding stdin/stdout/stderr while logging everything with timestamps and direction markers.
Two output modes:
- Text (default) — line-oriented log with UTC timestamps and direction markers, easy to grep.
- Binary (
--binary) — byte-faithful capture of every read with microsecond timestamps. Use this for curses/TUI apps (vim,htop,nethack, …) where escape sequences would otherwise be mangled. Replay with thestdio-replaycompanion binary.
cargo install --path .Installs two binaries: stdio-spy (the recorder) and stdio-replay (replays binary captures).
stdio-spy command [args...]Runs command with a PTY for stdin/stdout (so TUI apps see a real terminal) and a pipe for stderr. All traffic is logged with direction markers:
2026-03-06 23:45:12,345 >>> STDIN hello
2026-03-06 23:45:13,456 <<< STDOUT world
2026-03-06 23:45:13,789 <<< STDERR warning: something
By default, logs go to ~/stdio-spy-captures/<command>_<timestamp>.log. The path is printed to stderr on startup.
stdio-spy --log /tmp/capture.log -- command [args...]For TUI apps and anything that emits terminal escape sequences, use binary mode — it preserves raw bytes and per-read microsecond timestamps:
stdio-spy --binary --log ~/nethack.bin -- nethackThe default extension switches to .bin and the file lives at ~/stdio-spy-captures/<command>_<timestamp>.bin if you don't pass --log.
Replay it later:
stdio-replay ~/nethack.bin # real-time playback
stdio-replay --speed 4 ~/nethack.bin # 4× faster
stdio-replay --no-delay ~/nethack.bin # instant scrub
stdio-replay --show-stdin ~/nethack.bin # also play STDIN recordsBy default stdio-replay writes only STDOUT and STDERR — what was on the screen — because the PTY echoes typed input back through STDOUT and replaying STDIN too would double every keystroke. Pass --show-stdin to surface input records, which is useful for non-echoed keys (arrows, vim/less commands, password prompts, anything in nethack).
If you Ctrl-C a replay mid-stream while the captured app was using the alternate screen buffer, run reset to restore your terminal.
To transparently intercept a binary (e.g., when you can't control what process spawns it), create a bash wrapper earlier in $PATH:
#!/bin/bash
exec stdio-spy --log ~/stdio-spy-captures/node_$(date +%Y%m%d_%H%M%S).log \
-- /usr/local/bin/node "$@"Save this as node in a directory that appears before the real node in your $PATH. Any program that spawns node will transparently go through stdio-spy.
Extract unique patterns from stdout:
grep '<<< STDOUT' capture.log | sed 's/^[^{]*//' | head -20Count lines by direction:
grep -c '>>> STDIN' capture.log
grep -c '<<< STDOUT' capture.log
grep -c '<<< STDERR' capture.logUTC timestamps with direction markers:
| Marker | Meaning |
|---|---|
>>> STDIN |
Data sent to the child process |
<<< STDOUT |
Data received from the child's stdout |
<<< STDERR |
Data received from the child's stderr |
Little-endian. One record per read(), so replay reproduces both the original timing and the exact byte stream.
header (10 bytes, written once)
magic 8 bytes "STDIOSPY"
version 2 bytes u16 LE (currently 1)
record (13-byte header + payload, repeats)
ts_us 8 bytes u64 LE microseconds since the Unix epoch
dir 1 byte 0=STDIN, 1=STDOUT, 2=STDERR
len 4 bytes u32 LE payload length
data len bytes raw bytes from this read
The recorder caps individual reads at 64 KiB; the replayer rejects records claiming more than 16 MiB as corruption.
- PTY for stdin/stdout — the child sees a real terminal, so TUI apps work correctly
- Pipe for stderr — kept separate from stdout for distinct logging
- Signal forwarding — SIGINT, SIGTERM, SIGHUP, SIGWINCH forwarded to child
- EOF handling — sends EOT (0x04) through PTY line discipline instead of closing the fd (avoids SIGHUP)
- Non-blocking I/O with backpressure —
poll()on master_fd, stderr pipe, and stdin; stops reading from a source when its destination buffer is non-empty, so flow control propagates end-to-end - Two log modes — line-oriented text for grep, byte-faithful binary for replay
- Minimal dependencies — single crate (
nixfor POSIX APIs)
MIT