@bugwalk/server
v0.2.3
Published
Self-hostable Bugwalk server: Postgres schema, ingest, and issue detection
Downloads
684
Readme
@bugwalk/server
The self-hostable half of Bugwalk: the Postgres schema, the ingest endpoint, issue detection, and the background worker. Apache-2.0, no feature gates, no license key.
Most people should use the hosted service, which runs this same code and saves you a database. Self-host when your data cannot leave your infrastructure.
Run it
docker compose -f packages/server/docker-compose.yml up -d
docker compose -f packages/server/docker-compose.yml run --rm server \
node dist/cli.js create-project "My app"The second command prints a project key. Put it in your application and point the SDK at your server:
init({ dsn: 'hs_pub_...', endpoint: 'https://bugwalk.internal' });That is the only difference between self-hosted and hosted.
Without Docker
npm install @bugwalk/server
DATABASE_URL=postgres://... npx bugwalk-server start| Command | |
| --- | --- |
| start | Migrate, serve ingest, run the worker |
| migrate | Apply pending migrations and exit |
| create-project <name> | Create a project and print its key |
| Variable | Default | |
| --- | --- | --- |
| DATABASE_URL | — | Required |
| PORT | 4318 | |
| BUGWALK_ORIGINS | any | Comma-separated origins allowed to send browser events |
Java, .NET, Ruby, and PHP
The server accepts OTLP/HTTP traces at /v1/traces, so those ecosystems work through their
own official OpenTelemetry auto-instrumentation. There is no Bugwalk package to install:
OTEL_EXPORTER_OTLP_ENDPOINT=https://api.bugwalk.app
OTEL_EXPORTER_OTLP_PROTOCOL=http/json
OTEL_EXPORTER_OTLP_HEADERS=x-bugwalk-key=hs_pub_...Only the JSON encoding is accepted; protobuf would mean a code-generated dependency in the server for a format every exporter can already emit.
To get journeys rather than isolated spans, set two span attributes from your existing
middleware: session.id (the value of the x-bugwalk-session header the browser sent)
and enduser.id. Without them the requests are still recorded and still fingerprinted into
issues, but they will not join to a person.
Embedding it
Every piece is exported, so you can mount ingest inside an API you already run:
import { createPool, migrate, ingestBatch, startWorker } from '@bugwalk/server';
const db = createPool();
await migrate(db);
startWorker(db);
app.post('/v1/events', async (req, res) => {
const result = await ingestBatch(db, req.header('x-bugwalk-key'), req.body);
res.status(result.status).json(result.body);
});Reading is exported too: findPeople, sessionsFor, journeyFor, traceFor, and
listIssues.
What the worker does
Rolls up a session once it has been idle for 30 minutes, into an event count, an error count, and an outcome, so the dashboard does not aggregate raw events on every page load. Then it enforces retention by dropping whole month partitions, which is instant and returns the disk immediately, unlike deleting rows.
Both jobs take a Postgres advisory lock, so running several replicas does not duplicate work.
Issue detection
Rule-based, no AI involved. Errors and 5xx responses are fingerprinted on the error type, the first in-app stack frame, the route, and the status code. The message is deliberately excluded: messages embed ids and user input, which would split one bug into thousands of issues. Line numbers are excluded too, so an edit above the throw does not open a new issue.
An issue that was resolved and then happens again is reopened as regressed rather than
left closed.
Schema
projects id, name, public_key, retention_days
identities project_id, external_id, email, traits
sessions project_id, id, identity_id, environment, release
events partitioned by month on occurred_at
issues project_id, fingerprint, title, status, counts
issue_identities who each issue actually reached
session_summaries rolled up by the workerThe migrations are plain SQL in migrations/, readable without running
anything.
Scale
Postgres handles this comfortably into the tens of millions of events per month. Past that,
move raw events to ClickHouse and keep metadata, journeys, issues, and billing where they
are. Large payloads belong in object storage with the URI on the event rather than in a
jsonb column.
Apache-2.0
