Kubernetes multi-pod and multi-container log tailer in Rust, inspired by stern.
rustern (rstn) tails logs from many pods and containers at once. It targets stern/kubectl parity for day-to-day tailing, plus a small set of rustern-plus features (exit-on, stats, cursor reconnect). You need a working kubeconfig and permission to list/watch pods and read logs in the target cluster.
- Users: install, quick start, and feature notes below.
- Contributors: architecture, pipeline order, tests, and perf harness →
crates/rustern-core/README.md.
Requires Rust 1.88+ (rust-version in Cargo.toml).
make release # → target/release/rstn
make install # → ~/.cargo/bin/rstn
make install-local # → ~/.local/bin/rstnVerify: rstn --version
Ensure ~/.cargo/bin (or your install dir) is on PATH:
export PATH="$HOME/.cargo/bin:$PATH"Uninstall: make uninstall
Examples match rstn --help. Replace namespaces and workload names with your cluster.
Follow logs for a deployment (resolves pod label selector from the workload):
rstn deploy/my-app -n my-namespace -fFollow by label selector (positional query omitted → implicit .*):
rstn -l app=nginx -n default -fOne-shot logs from the last hour (no follow):
rstn my-pod -n default --since 1h --no-followRegex across all namespaces:
rstn 'api.*' -A -fTail with timestamps (RFC3339 nano, stern default):
rstn deploy/api -n production -f -tRun rstn --help for the full flag list (containers, since/since-time, filters, output format, and more).
User-supplied regular expressions (--include, --exclude, --highlight, pod query, and related flags) are limited to 1024 characters per pattern and validated before connecting to the cluster.
Behavior is tracked against stern; not every flag matches yet.
- Pod name query with
-l/--field-selector: when--selector/-lor--field-selectoris set, the positional QUERY may be omitted (implicit.*). With-l, a positional.is still treated as.*in the runner (Rust regex.is one character). - Namespaces: repeat
-n/--namespaceand/or comma-separate in one value (-n a,b); trim, drop empties, dedupe while keeping first-seen order.-A/--all-namespacesconflicts with-nand ignores explicit namespaces. With neither, use the active kube context's namespace from kubeconfig (fallbackdefault; kubeconfig read errors fail before run). --max-log-requests: defaults match stern’s intended caps (50with follow /5with--no-follow) when omitted; when following, exceeding--max-log-requestsconcurrent openings ends with an error instead of blocking indefinitely.- Timestamps / log window:
-sis a short alias for--since(relative duration or seconds).--since-timeaccepts an RFC3339 timestamp (kubectl parity; mutually exclusive with--since).--previousfetches logs from the previous terminated container instance. Line prefixes are off unless-t/--timestampsis set (bare flag → sterndefault/ RFC3339 nano;short→MM-DD HH:MM:SS).--timezonedefaults to local (stern parity);utcor an IANA zone overrides.epochandomitare also accepted. - Which containers: repeatable
-E/--exclude-container(comma-separated in one flag allowed);--init-containers/--no-init-containersand--ephemeral-containers/--no-ephemeral-containersfollow stern-style defaults (both kinds included unless opted out);--container-state running|waiting|terminated|alllimits streams using Pod container statuses (filtered modes skip containers whose status is not known yet). -H/--highlight: merged with-i/--includeand applied after default formatter lines (bold-red emphasis akin to stern); ineffective for--format raw|json.--only-log-lines: stern suppresses +/- attach banners on stderr; rustern does not emit equivalents yet—the flag stays for parity and only logs at debug level internally.kind/namepod selection (stern parity): supported kinds arepod,replicationcontroller,service,daemonset,deployment,replicaset,statefulset, andjob(short aliases such asdeploy,rs,ds,sts,svc,rc,powork too). For a single namespace, rusternGETs the workload and builds the label selector from its spec (includingmatchExpressions). With multiple-nvalues, it resolves per namespace and uses the common selector when they agree; otherwise it falls back toapp=<name>. With-A/--all-namespaces, selectors can differ per namespace, so rustern always uses the legacyapp=<name>fallback. Not supported today:cronjob,horizontalpodautoscaler, and other controller kinds stern does not list either.
--exit-on REGEX(repeatable): exit code 1 on first raw log line matching any pattern (evaluated before-i/-e, so hidden lines still trigger exit). Useful for CI/smoke.--exit-on-level LEVEL: exit code 1 when classified log level is at or aboveLEVEL(trace<debug<info<warn<error). Uses--level-keywhen set; works with follow and one-shot.--stats: emit one human-readable runtime summary line to stderr every--stats-interval(default30s) without polluting stdout. Reports active log streams, forwarded lines per window, and lossy dropped lines when--lossyis enabled.--cursor-reconnect: opt-in reconnect for follow mode. rustern keeps the last seen timestamp per pod/container source in memory; if a stream disconnects, it re-opens withsinceTime=<last_timestamp-1s overlap>to avoid gaps at the seam.--since/--since-time/--tailonly apply to the first open for a given pod UID, and the flag is ignored with--no-follow.
From the repo root:
cargo build --workspace --locked
cargo test -p rustern-coreCI (pull requests to main, see .github/workflows/ci.yml):
| Job | Checks |
|---|---|
rust |
cargo fmt --all --check, cargo clippy --workspace --all-targets --locked -- -D warnings, cargo build --workspace --locked, cargo test --workspace --locked |
audit |
cargo audit (RustSec advisory-db) |
Internal design (runtime layers, mux/attach/watch, pipeline order, integration tests, criterion benches) lives in crates/rustern-core/README.md—not duplicated here.