ANSI colours survive the trip
Your grep --color output, your test runner's greens and reds, your
framework's log levels — all rendered in the browser. Or strip them with
--no-tty.
MIT licensed · no agents · no persistence layer
rtail takes every line on stdin and broadcasts it over
UDP. rtail-server, if it is listening, streams those lines straight
into your browser — live, from as many machines as you like. That is the whole
idea.
$ node server.js 2>&1 | rtail --id api.myproject.com
Not a screenshot. The frame below is the real rtail-server webapp —
the same bundle, components and stylesheet the server ships — driven by a
generated log source instead of a UDP socket, because this page is static.
Click a stream, filter it, pause it, switch the theme.
Three streams, a few lines a second. On a real server the streams are whatever you named your pipes, and the lines are whatever your processes printed.
There is no agent to install, no log format to adopt, and nothing to configure.
Send any command's output into rtail. It passes every line
through to stdout untouched, so redirects and existing log files
keep working exactly as before.
Each line leaves as a single UDP datagram. Fire and forget: there is no handshake, no back-pressure, and if nothing is listening on the other end, your process neither blocks nor notices.
rtail-server receives the datagrams, groups them by stream id,
and dispatches each one to the browser over socket.io. Open the page and the
lines are already moving.
Your grep --color output, your test runner's greens and reds, your
framework's log levels — all rendered in the browser. Or strip them with
--no-tty.
Print an object and it arrives parsed, pretty-printed and syntax highlighted, instead of as one long unreadable line. Unquoted keys and trailing commas are fine.
Name each pipe with --id and every host, service or environment
gets its own entry in the sidebar. Star the ones you watch most.
Terms are ANDed and - negates one. "quoted" matches a
phrase, /regexp/ when you need one, and level:error or
duration>250 address parsed JSON fields rather than the text
around them. Scroll up and the stream pauses server-side, so a parked tab costs
nothing.
Pair it with tail -F and rotation is a non-event. Dates already in
your lines are parsed out and used as the timestamp.
The webapp is Preact and TypeScript, about 33 KB gzipped. The server keeps a fixed ring buffer per stream and writes nothing to disk.
Two pieces: a server that listens, and a client that pipes.
It ships as a container image, which is the recommended way to run it. Port 8888 serves the webapp; 9999/udp receives the lines.
$ docker run -d --name rtail -p 8888:8888 -p 9999:9999/udp ghcr.io/kilianc/rtail
Then open localhost:8888.
The client is a UNIX pipe, so it belongs on the host whose output you are
tailing — not in the container. It needs Node.js 22.18 or newer: rtail
ships as TypeScript and relies on Node stripping the types at load time,
so what you read in cli/ is exactly what npm installs.
$ npm install -g rtail
Point the client at the host running the server with --host, name
the stream with --id, and you are done.
$ node server.js 2>&1 | rtail --host logs.example.com --id api.myproject.com
$ tail -F log.txt | rtail --id nginx
$ echo "Server rebooted!" | rtail --id `hostname`