Skip to content

leroux/stdio-spy

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

3 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

stdio-spy

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 the stdio-replay companion binary.

Install

cargo install --path .

Installs two binaries: stdio-spy (the recorder) and stdio-replay (replays binary captures).

Usage

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.

Explicit log path

stdio-spy --log /tmp/capture.log -- command [args...]

Binary capture and replay

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 -- nethack

The 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 records

By 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.

Transparent binary wrapping

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.

Analyzing captures

Extract unique patterns from stdout:

grep '<<< STDOUT' capture.log | sed 's/^[^{]*//' | head -20

Count lines by direction:

grep -c '>>> STDIN' capture.log
grep -c '<<< STDOUT' capture.log
grep -c '<<< STDERR' capture.log

Text log format

UTC 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

Binary log format

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.

Architecture

  • 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 backpressurepoll() 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 (nix for POSIX APIs)

License

MIT

About

Bidirectional stdio proxy/logger — tee for stdin/stdout/stderr

Resources

License

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages