@tapizlabs/telemetry
v0.1.1
Published
Framework-agnostic error & event capture SDK for Tapiz products. Captures uncaught errors, batches and ships them to a Tapiz ingest endpoint.
Maintainers
Readme
@tapizlabs/telemetry
Framework-agnostic error & event capture SDK for Tapiz products. The client half of Tapiz's self-hosted error monitoring: it captures uncaught errors in the browser, Node, and Edge runtimes, scrubs PII on the client, groups events by a stable fingerprint, batches them, and ships them to an ingest endpoint.
It does not depend on React, Next, or any UI framework.
Backend: events are received and viewed in Tapiz Sentinel, the self-hosted ingest + dashboard ("Tapiz's Sentry"). The SDK works against any endpoint that accepts the wire contract, but Sentinel is the reference server.
Install
npm install @tapizlabs/telemetryRequires Node 18+ (uses crypto.randomUUID, fetch). ESM-only.
Quick start
import { init, captureError, captureMessage } from "@tapizlabs/telemetry";
init({
endpoint: process.env.NEXT_PUBLIC_TELEMETRY_URL!, // https://sentinel.tapiz.site/api/ingest
project: "boards", // must match the Sentinel project slug
ingestKey: process.env.NEXT_PUBLIC_TELEMETRY_KEY, // per-app key from Sentinel
release: process.env.NEXT_PUBLIC_APP_VERSION, // optional, for release grouping
environment: process.env.VERCEL_ENV ?? "production",
sampleRate: 1, // lower under heavy traffic
});
try {
riskyThing();
} catch (e) {
captureError(e, { context: { route: "/board" } });
}
captureMessage("checkout started", { severity: "info" });init() auto-installs global handlers for the detected runtime
(window.onerror + unhandledrejection in the browser; uncaughtException +
unhandledRejection in Node). Pass installGlobalHandlers: false to opt out.
Calling init() again replaces the client and tears down the previous handlers.
Next.js integration
Four small files wire the SDK into a Next.js (App Router) app. After this, both client and server/edge uncaught errors flow to your endpoint automatically.
1. src/lib/telemetry.ts — single init helper:
import { init } from "@tapizlabs/telemetry";
// No-op when the endpoint is unset (handy for local dev).
export function initTelemetry() {
const endpoint = process.env.NEXT_PUBLIC_TELEMETRY_URL;
if (!endpoint) return;
init({
endpoint,
project: "boards",
ingestKey: process.env.NEXT_PUBLIC_TELEMETRY_KEY,
release: process.env.NEXT_PUBLIC_APP_VERSION,
environment: process.env.VERCEL_ENV ?? "development",
});
}2. src/instrumentation-client.ts — browser:
import { initTelemetry } from "@/lib/telemetry";
initTelemetry();3. src/instrumentation.ts — server + edge (register() runs once per runtime):
export async function register() {
const { initTelemetry } = await import("@/lib/telemetry");
initTelemetry();
}4. src/app/global-error.tsx — capture React render crashes, then re-render:
"use client";
import { useEffect } from "react";
import { captureError } from "@tapizlabs/telemetry";
export default function GlobalError({ error }: { error: Error }) {
useEffect(() => {
captureError(error);
}, [error]);
return (
<html>
<body>Something went wrong.</body>
</html>
);
}Environment variables (e.g. Vercel project settings):
NEXT_PUBLIC_TELEMETRY_URL = https://sentinel.tapiz.site/api/ingest
NEXT_PUBLIC_TELEMETRY_KEY = <ingest key from Sentinel>
NEXT_PUBLIC_APP_VERSION = <optional, for release grouping>Leaving NEXT_PUBLIC_TELEMETRY_URL unset disables telemetry — convenient locally.
Manual capture
import { captureError, captureMessage, addBreadcrumb } from "@tapizlabs/telemetry";
addBreadcrumb({ category: "ui", message: "opened board" });
try {
saveBoard();
} catch (e) {
captureError(e, { context: { boardId } });
}
captureMessage("payment started", { severity: "info" });Viewing events (Tapiz Sentinel)
The SDK only sends events. To receive, group, and inspect them you run Tapiz Sentinel — a self-hosted Next.js + MySQL ingest service and dashboard. Per consuming app:
- Register the project in Sentinel to mint an ingest key:
ThePROJECT_SLUG=boards PROJECT_NAME="Tapiz Boards" npm run seed:project # → prints TELEMETRY_KEY=...slugmust equal theprojectyou pass toinit(). - Configure the app with
NEXT_PUBLIC_TELEMETRY_URL(Sentinel's/api/ingest) andNEXT_PUBLIC_TELEMETRY_KEY(the key from step 1). - Inspect at Sentinel's
/admin/issues— grouped issues with stack traces, context, breadcrumbs, first/last seen, and occurrence counts.
Sentinel authenticates the batch via the x-tapiz-key header (sent automatically
when ingestKey is set), rate-limits, and deduplicates by (project, fingerprint).
What it does
- Capture —
captureError(err),captureMessage(msg), plus auto global handlers. - Scrub — emails, long digit runs, bearer/JWT-like tokens, secret
key=valuepairs, and sensitive keys (password,token,email,jmbg, …) are redacted across the entire event (message, stack frames, url, context, breadcrumbs) before anything leaves the process. Seescrub.ts. - Fingerprint — events group by error type + top in-app stack frames, not
by message, so
User 4821 not foundandUser 9 not foundare one issue. Seefingerprint.ts. - Batch — buffered and flushed by size (20) or age (3s), with a
sendBeaconflush on page hide so fatal errors aren't lost on unload. - Sample / filter —
sampleRatedrops a fraction before work;beforeSendcan mutate or drop any event (the privacy scrub still runs after it).
What it deliberately does NOT do
- No performance tracing, session replay, or release-health.
- It does not re-queue on a failed flush — an error storm against a down endpoint must not grow memory unbounded. Delivery is best-effort.
- Source-map remapping happens server-side on ingest; the SDK only tags
releaseso the server can associate maps.
Configuration
init(options) accepts:
| Option | Default | Purpose |
| --- | --- | --- |
| endpoint (required) | — | Ingest URL, e.g. https://sentinel.tapiz.site/api/ingest. |
| project (required) | — | App/project slug; must match the Sentinel project. |
| ingestKey | — | Per-app key, sent as the x-tapiz-key header. |
| release | — | App version, for release grouping / source-map association. |
| environment | "production" | Environment label. |
| sampleRate | 1 | 0..1 fraction of events actually sent. |
| maxBreadcrumbs | 20 | Max breadcrumbs retained per event. |
| beforeSend | — | Last-chance hook to mutate or drop an event (return null to drop). |
| installGlobalHandlers | true | Auto-attach runtime error handlers. |
| fetchImpl | global fetch | Injectable fetch for non-browser / testing. |
| debug | false | Surface internal SDK failures instead of staying silent. |
API
| Export | Purpose |
| --- | --- |
| init(options) | Create the shared client + install global handlers. |
| captureError(err, opts?) | Capture an Error / thrown value. |
| captureMessage(msg, opts?) | Capture a message event. |
| addBreadcrumb(crumb) | Attach a recent-action trail to later events. |
| flush() | Force-send buffered events (await before process exit). |
| getClient() | The active TelemetryClient, or null. |
| TelemetryClient | Class for isolated/multi-tenant use without the singleton. |
| computeFingerprint, parseStack, scrub* | Pure helpers, also used by the ingest service. |
Wire contract
TelemetryEvent / IngestBatch / IngestResponse in types.ts
are the contract shared with the ingest service. The SDK POSTs an IngestBatch as
JSON to endpoint, with x-tapiz-key set when ingestKey is provided. Any server
honoring this contract can act as the backend.
Development
npm install
npm run build
npm run typecheck
npm testLicense
MIT © Tapiz Labs
