LogWisp started as a way to stop SSH-ing into servers to tail and grep log
files (the story). It is now one static binary, lw, that
runs anything from a shell filter to a multi-node aggregation topology.
- Pipelines
- Each pipeline is independent: sources feed a flow that rate-limits, filters and formats, and the flow fans out to any number of sinks.
- Never blocks
- A full queue drops and counts its own entries rather than stalling the stages around it, so a slow reader costs only itself.
- Chaining
- Links carry the structured entry, not formatted text, so a relay can filter and reformat as if the entries were local, and every entry keeps the node it came from.
- Security
- TLS 1.2/1.3 everywhere, mutual TLS with identity-based authorization, and
Argon2id-SCRAM passwords.
lw tlsmakes the certificates, or a listener makes its own at startup; unknown configuration keys are refused. - Access
- Every listener can refuse addresses by allow and deny lists before the TLS handshake, cap each client's connections (and over HTTP its request rate), and read the client's address from a load balancer's PROXY header.
- Viewer
- A browser at an HTTP sink gets a live page that follows the stream, with a search and a level filter; a sink that keeps a backlog shows the latest entries first. Behind a site's TLS proxy it signs in with SCRAM.
- Reload
- Edit the TOML and the service rebuilds itself. A candidate that fails validation leaves the running pipelines untouched.
- Filter
- With no file and no options,
lwis a Unix filter: stdin to stdout, line for line.
Every plugin LogWisp ships, in one pipeline. A configuration picks any number of sources and sinks; the flow stages are each optional and run in this order.
Sources
fileTails every matching file in a directory, through rotationconsoleReads stdin; the source when none is giventcp_chainlistens Entries from another node over one persistent streamhttp_chainlistens Entries from another node in batches over POSTrandomGenerates random logs records for testing
Flow
rate_limitToken bucket, and a cap on entry sizefiltersInclude and exclude RE2 patterns, in orderformatraw, txt or json, with a sanitizer policyheartbeatA keep-alive entry on a timer
Sinks
consolestdout or stderr, levels in colourfileRotating files with size caps and retentionhttplistens Server-Sent Events and status; a browser viewer with searchtcplistens Formatted lines broadcast to every clienttcp_chaindials To the next node; holds entries across reconnectshttp_chaindials To the next node in batches, with retry
- Entries
- A source turns each line into a structured entry: time, level, message, optional JSON fields, and the node it came from. The formatter renders it for the sinks, which also receive the entry itself.
- Drops
- Every queue is bounded. A full one drops the entry for that stage, sink or client alone and counts it. The console sink waits instead, so a filter loses no line to a slow reader; the TCP chain sink holds a line across reconnects, so an outage downstream surfaces as counted drops upstream, not silent loss.
- Topologies
- An edge node tails local files and dials an aggregator; the aggregator
listens for its edges and serves one stream, or writes one archive. Presets
build both:
lw --preset edgeandlw --preset aggregator.
The ViF multiplayer lobby streams every game server’s log through one LogWisp pipeline. Open the Server log panel there and host a game to watch it fill.
From
ViF game serversEach writes JSON lines to a log volume on its node
Sources
fileTails the volume's *.jsonl files, each line as written
Flow
rate_limit400 entries a second, bursts of 800filtersExcludes TRACE records and admission chatterformatraw: the JSON line, byte for byte
Sinks
httplistens Server-Sent Events on loopback
To
The lobby pageRelayed by the game allocator, served by nginx at /vif/api/logs
Runtime
- Platforms: Linux and FreeBSD, natively or in a container
- Toolchain: Go 1.27, no CGO, one static binary
- Dependencies: own Go modules for configuration, logging, TOML, SCRAM, colour and terminal output
- Configuration: a TOML file, or the same keys as flags and environment
- Presets:
pipe,tail,serve,edgeandaggregator - License: BSD-3-Clause
Build
- Clone
git clone https://github.com/lixenwraith/logwisp --depth 1- Build
cd logwisp && make build- Run
./bin/lw --preset tail,path=/var/log/app- Deploy
make imagebuilds ascratchimage that runs read-only with no capabilities. Natively,deploy/lw-deploy.shsets up an edge, an aggregator or a standalone node as a systemd or rc.d service.
Quick start
# a filter: errors and warnings only
tail -F app.log | ./bin/lw --filter include,patterns=ERROR,patterns=WARN
# stdin as a live SSE stream, with a browser viewer at http://127.0.0.1:8080/
journalctl -f | ./bin/lw --sink http,host=127.0.0.1,port=8080
# a directory's logs, served to one subnet
./bin/lw --preset serve,path=/var/log/app,listen=0.0.0.0:8080,allow=192.0.2.0/24
# any command line as a configuration file
./bin/lw --preset serve,path=/var/log/app --dump > logwisp.toml