tinylogs
v0.3.0
Published
A tiny self-hosted central log receiver + live dashboard.
Maintainers
Readme
tinylogs
A tiny, self-hosted central log receiver + live dashboard. Apps push log lines
over HTTP; you watch them in a fast, dense browser dashboard with label filtering.
Single Node process, single SQLite file. Runs anywhere npx runs.
Quick start
npx tinylogs init # wizard: port, admin user/pass, retention → writes tinylogs.config.json + prints an ingest token
npx tinylogs start # runs the receiver + dashboard (default http://127.0.0.1:4700)Open the dashboard, log in with the admin user you created.
Non-interactive init (CI/containers):
TINYLOGS_PASSWORD=hunter2 npx tinylogs init --yes --port 4700Install & run as a service
Install globally so tinylogs is on your PATH (recommended for a long-running server):
npm i -g tinylogs # puts `tinylogs` on PATH
tinylogs init # create tinylogs.config.json + tinylogs.db in the current dir
tinylogs start -d # run in the background (writes tinylogs.pid + tinylogs.log)
tinylogs status # running? pid, url, uptime, version
tinylogs stop # stop it
tinylogs restart # stop + start -d
tinylogs update # update to the latest published versionnpx tinylogs … still works for one-off use. start -d, status, and stop are supported on
Linux/macOS; on Windows run the foreground tinylogs start under a service manager (NSSM / Task
Scheduler). The PID and log files live next to your config file.
Sending logs
Anything that can make an HTTP request can send logs. service and message are
required; labels are arbitrary string key/values. level is just a label with
special coloring.
curl -XPOST http://localhost:4700/ingest \
-H "Authorization: Bearer <INGEST_TOKEN>" \
-H "Content-Type: application/json" \
-d '{"service":"debtors-bot","message":"reminder sent","labels":{"level":"info","user":"12345"}}'Batch by sending an array. Response: {"accepted": N} (400 if service/message
missing, 401 if the token is wrong).
Node client
Install the standalone, zero-dependency client package @tinylogs/client (npm install @tinylogs/client):
import { TinyLogsClient } from '@tinylogs/client';
const logs = new TinyLogsClient({
url: 'http://localhost:4700',
token: process.env.TINYLOGS_TOKEN!,
service: 'debtors-bot',
});
logs.info('reminder sent', { user: '12345' });
logs.error('boom', { user: '12345' });
// fire-and-forget: never throws, never blocks your app, batches automatically.
await logs.close(); // flush on shutdownConfiguration
tinylogs.config.json (created by init, in the current directory by default):
| Field | Meaning | Default |
|-------|---------|---------|
| port / host | Listen address | 4700 / 127.0.0.1 |
| dbPath | SQLite file (relative to the config file) | tinylogs.db |
| retentionDays | Delete logs older than this | 14 |
| maxSizeMB | Cap the DB file; oldest rows pruned first | 500 |
| bufferSize | In-memory live-tail buffer | 2000 |
| sessionSecret / ingestTokenHash | Generated at init — do not edit | — |
Override the config location with --config <path> or TINYLOGS_CONFIG.
Rotate the ingest token with npx tinylogs rotate-token.
Security
No default password —
initforces you to set one.TLS is delegated to a reverse proxy. tinylogs serves plain HTTP; put nginx or Caddy in front for HTTPS. Example (Caddy):
logs.example.com { reverse_proxy 127.0.0.1:4700 }Dashboard auth is a signed httpOnly session cookie;
/api/*and/wsrequire it./api/loginis rate-limited against brute force.The ingest token is a separate shared secret, stored hashed; shown only once.
How it works
- Storage: SQLite in WAL mode.
logsholds each line (labels as JSON for one-read render);log_labelsexplodes labels for index-backedkey=valuefiltering. - Live tail: a bounded in-memory ring buffer seeds new browser tabs and feeds the WebSocket stream. The DB is the source of truth for history + search.
- Retention: a background task prunes by age and by size (whichever hits first) and VACUUMs; failures are logged, never fatal.
Non-goals
Not a clustered/distributed store, not metrics/tracing, not multi-tenant SaaS. One node, one SQLite file, one admin.
License
MIT © 2026 Nikita Medvedev
