Self-hosted, Sentry-compatible error tracking — small enough to read in an afternoon.
Your services already use @sentry/node? Change one line (the DSN) and errors flow into your own server. No SDK to swap, no vendor account, no per-seat pricing.
- Sentry SDK compatible — speaks the Sentry envelope protocol (
/api/<project>/envelope/) and the legacy store endpoint. Tested with@sentry/nodev7. - Issue grouping — events are fingerprinted by exception type + stack frames (line-number agnostic, so redeploys don't split issues) and grouped into issues with first/last seen and counts.
- Web dashboard — issue list with search and status tabs, stack trace viewer with source context, breadcrumbs, tags, event history, 24h frequency chart. Works on a phone: triage from bed without pinch-zooming.
- Webhook alerts — new issues and regressions post Slack-compatible JSON to a per-project webhook.
- Boring stack — Node + Fastify + PostgreSQL. One container plus Postgres.
git clone https://github.com/datamaker/lookout.git
cd lookout
docker compose up -d --buildOpen http://localhost:9000 — the first visit walks you through creating the admin account. Then create a project and copy its DSN into your app:
const Sentry = require('@sentry/node');
Sentry.init({
dsn: 'http://<public-key>@localhost:9000/1', // from the project settings page
environment: process.env.NODE_ENV,
});That's it — Sentry.captureException(err) and unhandled errors now land in lookout.
If you'd rather not pull in the full Sentry SDK, @datasee/lookout is a zero-dependency client (~6 kB) that lives in sdk/:
npm install @datasee/lookoutconst lookout = require('@datasee/lookout');
lookout.init({ dsn: 'http://<public-key>@localhost:9000/1' });
lookout.captureException(new Error('boom'));Uncaught exceptions and unhandled rejections are captured automatically; breadcrumbs, tags, user context, source context lines, and an Express error handler are included. See sdk/README.md.
| Env var | Default | Description |
|---|---|---|
PORT |
9000 |
HTTP port |
DATABASE_URL |
postgres://lookout:lookout@localhost:5434/lookout |
PostgreSQL connection string |
JWT_SECRET |
(auto-generated, persisted in DB) | Session token signing secret. Set it only if you rotate DBs. |
LOOKOUT_PUBLIC_URL |
(empty) | Public base URL, used for DSNs shown in the UI and links in alerts, e.g. https://lookout.example.com |
LOOKOUT_RETENTION_DAYS |
90 |
Raw events older than this are deleted hourly. Issues are kept. |
| Endpoint | Behavior |
|---|---|
POST /api/<project>/envelope/ |
Sentry envelope (gzip/deflate ok). event items are stored; transactions, sessions, and client reports are accepted and dropped. |
POST /api/<project>/store/ |
Legacy single-event JSON. |
Authentication uses the DSN public key (X-Sentry-Auth header or sentry_key query param), so any Sentry SDK that can target a custom DSN should work — @sentry/node, @sentry/browser, sentry-python, etc. Only v7-style error events are officially tested.
npm install
docker compose -f docker-compose.dev.yml up -d # postgres on :5434
npm run dev # API server on :9000 (runs migrations on boot)
npm run dev:web # Vite dev server on :5180, proxies /apinpm run build builds the dashboard into web/dist and the server into server/dist; the server serves the dashboard statically when web/dist exists.
- Grouping: default fingerprint is
sha256(exception type + (module:function) of up to 8 innermost in-app frames). Customfingerprintarrays on the event are respected, including{{ default }}. - Regressions: an event arriving for a
resolvedissue reopens it and fires the webhook again. - Auth model: email/password accounts with two roles —
admin(manage projects, webhooks, members) andmember(view and triage issues). The first visit creates the admin account; admins add members from the dashboard. Sessions are 7-day JWTs (bcrypt-hashed passwords). Ingestion is authenticated per-project by DSN key, independent of user accounts.
Performance tracing, session replay, source map processing, multi-user/teams, SSO. If you need those, use Sentry — this project exists for the "I just want error tracking on my own box" case.
MIT © JungBin Kwon