@devlite/nodejs
v0.1.4
Published
DevLite Node.js SDK — AI-powered observability with 1-3 line integration. Automatic error grouping, source context, user impact tracking, and sensitive-data scrubbing built in.
Maintainers
Readme
@devlite/nodejs
AI-powered observability SDK for Node.js. Add two lines, get automatic request tracking, error capture, error grouping, source context, user tracking, distributed tracing, fast database insights, and slow-endpoint detection — no config files, no manual instrumentation.
Try it in 60 seconds
git clone https://github.com/Ishimwe-Kevin/devlite-app
cd devlite-app/devlite-sdk/examples/demo-express
npm install
npm start # terminal 1 — instrumented Express "checkout" service
npm run demo # terminal 2 — generates realistic traffic & bugsThe demo app contains two intentional bugs plus a slow endpoint, so the
dashboard fills with grouped errors, source context, user impact, and metrics
— perfect for a live walkthrough. Point it anywhere with
DEVLITE_API_KEY / DEVLITE_ENDPOINT.
Install
npm i @devlite/nodejs60-second quickstart
const devlite = require('@devlite/nodejs');
devlite.init({ apiKey: 'dl_live_xxxxx' });That's it — every HTTP request, uncaught exception, and unhandled promise rejection is now captured automatically, with:
- Grouped errors — the same bug occurring 1,000 times shows up as one issue, not 1,000
- Real source code context — the actual lines around the crash, not just a stack trace
- Automatic sensitive-data scrubbing — emails, tokens, passwords redacted before anything leaves your process
- User impact tracking — know exactly which users hit which bugs
Point apiKey/endpoint at the hosted platform (default) or your own DevLite ingest API — see devlite-ingest-api in this repo.
Full usage
Distributed tracing — zero config
Every request the SDK sees is a trace. Outbound http/https (including anything built on them: Axios, got, node-fetch…) and global fetch calls made while handling that request automatically get a W3C traceparent header pointing at the same trace, so any downstream service — DevLite or OpenTelemetry-compatible — joins the same trace across the wire. Each outbound call is also recorded as a child span (http_out/fetch) with host, status code and latency, giving the dashboard a real per-request waterfall.
devlite.init({ apiKey: 'dl_live_xxxxx' });
// that's it — every axios/got/fetch/http call is now traced, across servicesInbound requests already carrying a traceparent header (from a caller on DevLite, or any OTel propagator) continue that trace instead of starting a new one.
Automatic database instrumentation — the "I can't live without this" part
The most common Node drivers are instrumented automatically — no wrappers, no work:
- pg (
Client.query) · mysql2 (Connection.query/execute) · better-sqlite3 (statement.run/get/all/iterate)
Every query executed inside a traced request becomes a child span carrying the (scrubbed) SQL, its duration, and — when it's slower than sqlSlowThresholdMs (default 500ms, exactly the level the backend flagger uses) — a slow_request receipt. That's the exact data that powers the dashboard's slow-query, "add an index", and N+1 findings. Patch uses the app's node_modules, so it works even if you haven't linked the SDK.
Drivers we can't auto-patch (Prisma, Sequelize, Knex, mongoose…)? One line:
devlite.trackSql({ query: 'SELECT * FROM users WHERE id = $1', durationMs: 620, params: ['42'] });Express (recommended add-on)
The core patch works with any framework, but adding the Express middleware upgrades data quality by resolving matched routes (/users/:id) instead of raw URLs (/users/8123):
const express = require('express');
const devlite = require('@devlite/nodejs');
devlite.init({ apiKey: 'dl_live_xxxxx' });
const app = express();
app.use(devlite.expressMiddleware());
app.get('/users/:id', (req, res) => { /* ... */ });
// Add AFTER all routes:
app.use(devlite.expressErrorHandler());Configuration options
devlite.init({
apiKey: 'dl_live_xxxxx', // required
environment: 'production', // default: process.env.NODE_ENV
serviceName: 'payments-api', // default: current folder name
release: 'v2.1.3', // e.g. git sha, shown in deployment views
beforeSend: (event) => event, // run on EVERY event right before enqueue. Return the
// (possibly mutated) event to keep it, or null to drop it.
// A throwing hook drops the event — never crashes the SDK.
sampleRate: 1.0, // 0.0–1.0, lower on very high-traffic services.
// Sampling is COHERENT: a sampled-out request drops its
// errors/spans/logs/metrics together. Deployments and
// out-of-request crashes (uncaught exceptions) always survive.
distributedTracing: true, // emit + accept W3C traceparent: end-to-end traces across
// every process/service. The one line that makes DevLite
// trace-first without being locked in.
autoInstrumentSql: true, // auto-patch pg / mysql2 / better-sqlite3. Turn off only
// if you prefer manual trackSql() everywhere.
sqlSlowThresholdMs: 500, // queries slower than this emit a slow_request (the same
// threshold the backend recommendation engine uses).
slowRequestMs: 1000, // HTTP requests slower than this are flagged as slow.
requestTimeoutMs: 10000, // hard timeout per ingest flush so a dead endpoint can't
// hang your app.
captureSourceContext: true, // attach the actual surrounding source code to errors
scrubSensitiveData: true, // auto-redact emails, JWTs, tokens, credit cards, etc.
captureConsoleBreadcrumbs: true, // console.warn/error already in your code become breadcrumbs
captureConsoleLogs: true, // ALSO forward console.* as `log` events so the dashboard's
// Logs stream works out of the box (set false to silence noisy apps)
captureBody: false, // capture request headers (redacted) — off by default
flushIntervalMs: 5000, // how often batched events are sent
gzip: true, // compress request bodies (Content-Encoding: gzip) — cuts bandwidth 10-20x
debug: false, // log SDK internals to console
onError: (err) => { /* SDK-internal send failures, e.g. for your own logging */ },
});Manual capture API
Auto-instrumentation covers most cases, but you can add explicit signal too:
// Capture a handled error with extra context
devlite.captureError(err, { userId: '123', action: 'checkout' });
// Log a non-error event
devlite.captureMessage('Payment retried after timeout', 'warning');
// Report a custom metric — feeds forecasting/anomaly detection
devlite.reportMetric({ name: 'order.total', value: 42.5, unit: 'USD', tags: { region: 'lagos' } });
// Capture a structured log line with arbitrary fields
devlite.captureLog('checkout completed', 'info', { orderId: 'ord_123', durationMs: 250 });
// Trace a unit of work as a span
const span = devlite.startSpan('checkout.process', { traceId: 'abc', parentId: 'def' });
span.end('ok'); // 'ok' | 'error' — sends { name, durationMs, status, ... }
devlite.reportSpan({ name: 'db.query', durationMs: 120, status: 'error', tags: { table: 'orders' } }); // or manual
// Tell DevLite about a deployment (powers before/after performance views)
devlite.reportDeployment({ version: 'v2.1.3', commitSha: 'a1b2c3d' });
// Add a breadcrumb — recent breadcrumbs are attached to the next captured error,
// which significantly improves the AI's root-cause explanation.
devlite.addBreadcrumb({ type: 'business_event', note: 'user started checkout' });
// Tag all subsequent events — tags are merged into each error's `extra`,
// so the dashboard shows your context alongside the message.
devlite.setTag('region', 'lagos');
devlite.setTag('deploy', 'canary');
// Host metrics — CPU / RSS / uptime streamed as `metric` events
const host = devlite.startHostMetrics({ intervalMs: 15000 }); // options optional
// host.stop(); — stop collecting
// Proof-of-life: verify the SDK → ingest pipeline in a health check or on startup
const health = await devlite.checkConnectivity();
console.log(health.ok, health.accepted, health.quota, health.latencyMs);
// Introspect: see what's going on under the hood at any time
console.log(devlite.getStats());
// { lastAccepted: 3, lastQuota: 1000, droppedCount: 0, lastErrorAt: null, lastFlushMs: 42, queued: 0 }Serverless / short-lived processes
Since events are batched, call flush() before your function returns to make sure nothing is lost:
await devlite.flush();Graceful shutdown
The SDK automatically flushes on SIGTERM/SIGINT. If you manage shutdown yourself, call:
await devlite.close();What gets sent
- HTTP requests: method, path, status code, duration,
traceId+spanId - Slow requests (>1s): flagged separately for APM analysis
- Outbound HTTP/fetch child spans (
http_out/fetch): host, method, status, duration — automatically part of the request's trace - Database query spans (
kind: sql): the scrubbed SQL text, duration, status — automatically feeds slow-query / missing-index / N+1 AI recommendations - Errors: message, stack trace, recent breadcrumbs, route + method (when in a request)
- Metrics: custom name/value/unit/tags via
reportMetric() - Structured logs: level + message + fields via
captureLog() - Trace spans: name, timing,
traceId,parentIdviastartSpan()/reportSpan()/auto-patch - Deployments, manual messages, and tags (if you call the relevant methods)
- Host metrics (CPU, RSS, uptime) via
startHostMetrics({ intervalMs })— streamed asmetricevents - Ingest ack stats (
accepted,quota): surfaced ingetStats()so you know whether the pipeline is clean
Request/response bodies and most headers are not captured unless you explicitly set captureBody: true — and even then, sensitive headers (authorization, cookie, x-api-key) are redacted by default.
What makes this competitive (vs. bolt-on monitoring tools)
- Distributed tracing that requires zero thought. Every outbound
http/https/fetchcall made while handling a request automatically gets a W3Ctraceparentheader — no manualsetSpan()wrapping, no context-propagation boilerplate, no lock-in to a single transport. Request-carrying apps, background workers, microservice meshes: it just works. Sentry, Rollbar, Bugsnag don't offer this. - Automatic database instrumentation that powers AI recommendations, not just dashboards. pg/mysql2/better-sqlite3 queries become
sql-tagged spans the instant the SDK is mounted. The dashboard's slow-query, "add an index" and N+1 detection work out of the box — no log parsing, no log-histogram guessing. The 500ms threshold matches the backend engine exactly. - Proof-of-life and intake visibility.
checkConnectivity()hits the ingest endpoint on startup and returns whether the pipeline is alive — no logs to grep, no separate health check.getStats()surfaceslastAccepted,lastQuota, anddroppedCountso you can prove data integrity in a status page or runbook. - Automatic error grouping — the same underlying bug, however many times it fires, is fingerprinted and grouped into one issue with an occurrence count, instead of flooding your dashboard with duplicates.
devlite.captureError(err); // auto-grouped by error type + message + stack shape devlite.captureError(err, { fingerprint: 'payment-timeout' }); // or force your own grouping key - Source code context — every captured error includes the actual lines of code around the crash, read live from disk, not just a bare stack trace.
- User impact tracking —
devlite.setUser({ id, email })scopes to the current request (viaAsyncLocalStorage, so concurrent requests never leak each other's identity) and tags every error captured during it, directly answering "which users are affected?"app.use((req, res, next) => { devlite.setUser({ id: req.user.id, email: req.user.email }); next(); }); - Automatic console breadcrumbs —
console.warn/console.errorcalls you already have in your code automatically become breadcrumbs attached to the next error, with zero extra instrumentation. beforeSendeverywhere — the Sentry-style event hook runs on every event right before enqueue: mutate to enrich, returnnullto drop (e.g. filter out health-check noise), and a throwing hook degrades to "drop the event" instead of crashing your app.- Automatic sensitive-data scrubbing — on by default. Emails, JWTs, bearer tokens, AWS keys, credit card numbers, and any field literally named
password/token/secret/etc. are redacted before anything leaves your process — even inside nested objects and source code snippets. Disable only if you're certain you want raw data:captureSourceContext/scrubSensitiveDataconfig flags.
Design principles
- Never blocks your app. All sends are async, batched, and retried with backoff. If DevLite's backend is unreachable, your app keeps running.
- Never swallows errors. DevLite observes errors, it doesn't intercept or suppress them — your existing error handling behaves exactly as before.
- Bounded memory. The internal queue has a hard size cap; if the network is down for a long time, oldest events are dropped rather than growing memory unbounded.
License
MIT
