| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Name | Name | Last commit date | ||
|---|---|---|---|---|
rtail is a command line utility that grabs every line in stdin and broadcasts it over UDP. That's it. Nothing fancy. Nothing complicated. Tail log files, app output, or whatever you wish, using rtail broadcasting to an rtail-server – See multiple streams in the browser, in realtime.
The server ships as a container image — this is the recommended way to run it.
$ docker run -d --name rtail -p 8888:8888 -p 9999:9999/udp ghcr.io/kilianc/rtail
Open http://localhost:8888 and start piping. There is a docker-compose.yml if you prefer:
$ docker compose up -d
Options are flags, or RTAIL_* environment variables:
$ docker run -d -p 8080:8080 -p 9999:9999/udp \
-e RTAIL_WEB_PORT=8080 ghcr.io/kilianc/rtail
$ docker run -d -p 8888:8888 -p 9999:9999/udp \
ghcr.io/kilianc/rtail --backlog 500
The image binds 0.0.0.0 inside the container, so the published ports are reachable from your whole network. It has no authentication — keep it on a trusted network, or put a reverse proxy in front of it.
The client is a UNIX pipe and 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, which is unflagged from 22.18 on:
$ npm install -g rtail
npm install -g rtail also gives you rtail-server, if you would rather run the server without Docker.
Whether you deploy your code on remote servers using multiple environments or simply have multiple projects, you must ssh to each machine running your code, in order to monitor the logs in realtime.
There are many log aggregation tools out there, but few of them are realtime. Most other tools require you to change your application source code to support their logging protocol/transport.
rtail is meant to be a replacement of logio, which isn't actively maintained anymore, doesn't support node v0.12., and uses TCP. (TCP requires strict client / server handshaking, is resource-hungry, and very difficult to scale.)
The rtail approach is very simple:
rtail is a realtime debugging and monitoring tool, which can display multiple aggregate streams via a modern web interface. There is no persistent layer, nor does the tool store any data. If you need a persistent layer, use something like loggly.
In your app init script:
$ node server.js 2>&1 | rtail --id "api.myproject.com" $ mycommand | rtail > server.log $ node server.js 2>&1 | rtail --mute
Supports JSON5 lines:
$ while true; do echo [1, 2, 3, "hello"]; sleep 1; done | rtail
$ echo { "foo": "bar" } | rtail
$ echo { format: 'JSON5' } | rtail
Using log files (log rotate safe!):
$ node server.js 2>&1 > log.txt $ tail -F log.txt | rtail
For fun and debugging:
$ cat ~/myfile.txt | rtail $ echo "Server rebooted!" | rtail --id `hostname`
$ rtail --help Usage: cmd | rtail [OPTIONS] Options: --host, -h The server host [string] [default: "127.0.0.1"] --port, -p The server port [string] [default: 9999] --id, --name The log stream id [string] [default: (moniker)] --mute, -m Don't pipe stdin with stdout [boolean] --tty Keeps ansi colors [boolean] [default: true] --parse-date Looks for dates to use as timestamp [boolean] [default: true] --help Show help [boolean] --version, -v Show version number [boolean] Examples: server | rtail > server.log localhost + file server | rtail --id api.domain.com Name the log stream server | rtail --host example.com Sends to example.com server | rtail --port 43567 Uses custom port server | rtail --mute No stdout server | rtail --no-tty Strips ansi colors server | rtail --no-date-parse Disable date parsing/stripping
rtail-server receives all messages broadcast from every rtail client, displaying all incoming log streams in a realtime web view. Under the hood, the server uses socket.io to pipe every incoming UDP message to the browser.
There is little to no configuration – The default UDP/HTTP ports can be changed, but that's it.
Use default values:
$ rtail-server
Always use latest, stable webapp:
$ rtail-server --web-version stable
Use custom ports:
$ rtail-server --web-port 8080 --udp-port 9090
Set debugging on:
$ DEBUG=rtail:* rtail-server
Open your browser and start tailing logs!
$ rtail-server --help Usage: rtail-server [OPTIONS] Options: --udp-host, --uh The listening UDP hostname [default: "127.0.0.1"] --udp-port, --up The listening UDP port [default: 9999] --web-host, --wh The listening HTTP hostname [default: "127.0.0.1"] --web-port, --wp The listening HTTP port [default: 8888] --web-version Define web app version to serve [string] --backlog, -b Lines of history kept per stream [number] [default: 100] --help, -h Show help [boolean] --version, -v Show version number [boolean]
Every option can also be set as an environment variable, prefixed with RTAIL_ — RTAIL_WEB_PORT=8080, RTAIL_BACKLOG=500, and so on. This is how the container image is configured.
Examples: rtail-server --web-port 8080 Use custom HTTP port rtail-server --udp-port 8080 Use custom UDP port rtail-server --web-version stable Always uses latest stable webapp rtail-server --web-version unstable Always uses latest unreleased webapp rtail-server --web-version 0.1.3 Use webapp v0.1.3
The filter box takes a small query language rather than a bare regexp. Terms are separated by spaces and all of them have to match; matching ignores case until a term contains a capital, the way ag and rg behave.
| Query | Matches |
|---|---|
| payment failed | both words, in any order |
| "payment failed" | that exact phrase |
| -healthcheck | lines without it |
| /GET \/1\/users/ | a regexp — add /i, /s, /m or /u for flags |
| level:error | the JSON field contains error |
| user.id=42 | the JSON field is exactly 42 |
| status!=200 | it is anything else |
| duration>250 | >, >=, < and <=, on numbers |
| tags[0]:api | paths index into arrays |
| trace_id:* | the field is there at all |
Field terms read the parsed payload, so level:error cannot be fooled by the word "error" sitting in a message three keys away. A field term never matches a line that does not carry the field — including status!=200, which means "has a status, and it is not 200". For "not 200, whether or not there is a status", negate the whole term instead: -status:200.
Matches are marked in the viewport, and the box counts how many lines survived. An unfinished regexp turns the box's hairline red and keeps filtering on the terms that did parse. Press f to jump to the box, Escape to clear it.
Fields picks JSON paths out of object lines: event=checkout.completed count=305 ok=false instead of the whole payload. The list is a census of the paths in the buffer, most common first, and takes a hand-typed path for anything that has not come past yet. The picks belong to the stream — the fields of a billing worker are not the fields of an nginx log — and are remembered per stream.
Not every payload is worth six rows of the viewport. Settings → JSON payloads decides what an object line looks like before you touch it:
The caret at the start of any object line overrides that for that line, whether it is showing a payload or extracted fields.
To scale and broadcast on multiple servers, instruct the rtail client to stream to the broadcast address. Every message will then be delivered to all servers in your subnet.
For the time being, the webapp doesn't have an authentication layer; it assumes that you will run it behind a VPN or reverse proxy, with a simple Authorization header check.
Note there are two images, and they are not the same thing: Dockerfile is the server you deploy, and tools/Dockerfile below is the development toolchain, which mounts your checkout.
The toolchain lives in a container (tools/Dockerfile), so Docker is the only thing you need installed — no Node.js, no npm, no global CLIs.
$ make dev
That builds the assets, starts rtail-server, feeds it three live demo streams, and serves the webapp. It prints the URL — make url prints it again. Stylesheets recompile on save; reload the browser to pick them up. Ctrl-C stops everything.
The first run also builds the toolchain image and installs dependencies, so it takes a minute; subsequent runs start immediately.
To leave it running in the background instead:
$ make up # start detached, wait for it, print the URL $ make logs # tail it $ make down # stop it
Other targets:
$ make build # build the webapp into app/ $ make dist # build the minified webapp into dist/ $ make test # unit + integration suite, with coverage thresholds $ make test-e2e # browser smoke suite (first run downloads Chromium) $ make typecheck # type-check everything $ make shell # open a shell inside the toolchain container $ make clean # remove generated assets and dependencies
Ports are derived from the worktree path, so several checkouts of this repo can run at once without colliding. Each gets its own HTTP port, UDP port, and container name, fixed across runs — sharing any of the three fails confusingly, since two servers can both bind the same UDP port and the loser simply never receives a log line.
make url reports the current one. Override with make dev PORT=9000 UDP_PORT=9001.
If you do have a Node.js toolchain on your machine, the same targets are plain npm scripts — npm install && npm run dev.
| CLI | TypeScript, ESM, Node ≥ 22.18, yargs, socket.io |
| Webapp | Preact + TypeScript, ~33 KB gzipped |
| Build | esbuild + dart-sass |
| Tests | the built-in node:test runner, jsdom, Playwright |
The webapp has no framework runtime beyond Preact: routing, preferences, popovers, and timestamp formatting are a few dozen lines each over the platform (history, localStorage, Intl) rather than dependencies.
The CLI has no build step at all. bin points straight at the .ts sources and Node strips the types as it loads them, so what you read in cli/ is exactly what npm installs — nothing is compiled, bundled, or minified on the way. tsc never emits; it only type-checks, and erasableSyntaxOnly keeps the source to constructs that can be erased (no enums, namespaces, or constructor parameter properties, all of which would need real codegen).
main is the only long-lived branch, and it is always releasable.
There are three layers, and a change usually wants a test in exactly one of them:
| Layer | Where | What it covers |
|---|---|---|
| Unit | test/unit | The CLI modules, the webapp's libraries, and each Preact component against jsdom |
| Integration | test/integration | The real bin entry points, spawned as processes and talking over a real UDP socket |
| End to end | test/e2e | The published build in a real browser, fed by the real client bin |
The first two run on the built-in node:test runner and are what you want almost always. They are fast (a few seconds) and enforce a 90% line, branch and function coverage floor — the run fails if a change drops below it.
$ make test
The browser suite is slower and needs its own image, so it is a separate target and a separate CI job:
$ make test-e2e
Everything is TypeScript; please keep it type-clean:
$ make typecheck
One wrinkle worth knowing: Node cannot load .tsx at all — type stripping is an erasure pass and JSX needs a real transform — so tools/tsx-hook.ts runs the components through esbuild on the way into the test runner, using the same JSX settings as tools/build.ts. Source maps are inline, so coverage still reports against the original .tsx lines.
CI runs the unit and integration suites plus the production build on Node 22.18 and 24, and the browser suite on 22.18.
The version field in package.json is the release trigger. Bump it in a pull request like any other change; when that pull request lands on main, CI re-runs the type-check, build, and tests, then tags the commit vX.Y.Z, cuts a GitHub release with generated notes, and pushes the multi-arch container image to ghcr.io/kilianc/rtail.
A version with a pre-release suffix (0.3.0-rc.1) is marked as a pre-release and is never tagged latest. Landing anything else on main releases nothing.
Dropped, and why:
❤ rTail? Consider sponsoring this project to keep it alive and free for the community.
This software is released under the MIT license cited below.
Copyright (c) 2014 Kilian Ciuffolo, me@nailik.org. All Rights Reserved. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
| Back | FazBrowse Home | New Git URL |