Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ Contributors: add user-facing changes under **[Unreleased]** in your PR to `deve

### Added

- **`@telemetry-tracker/core` 1.5.0** — publish `ingestError()` so fatal Node handlers can await ingest before exit (Refs [#711](https://github.com/Telemetry-Tracker/telemetry-tracker/issues/711))
- **`@telemetry-tracker/node` 1.4.0** — depends on core `^1.5.0`; flush-then-exit for `uncaughtException` / `unhandledRejection` (opt out of rejection exit via `exitOnUnhandledRejection: false`); middleware times response finish and calls `next()` once (Refs [#711](https://github.com/Telemetry-Tracker/telemetry-tracker/issues/711), [#719](https://github.com/Telemetry-Tracker/telemetry-tracker/issues/719), [#720](https://github.com/Telemetry-Tracker/telemetry-tracker/issues/720), [#632](https://github.com/Telemetry-Tracker/telemetry-tracker/issues/632))
- **`@telemetry-tracker/vite-plugin` 1.1.0** — publish `sourceMappingURL`-based `bundle_url` resolution (Refs [#718](https://github.com/Telemetry-Tracker/telemetry-tracker/issues/718))

### Security

- **Post-login redirects (TT-017)** — `next` on `/login` (and legacy `signIn=1` flows) is validated against the app origin so protocol-relative, backslash, control-character, and absolute external values fall back to `/dashboard/overview`. Legitimate paths with query strings (e.g. `/dashboard/errors?range=7d`) are preserved.
Expand All @@ -24,6 +28,8 @@ Contributors: add user-facing changes under **[Unreleased]** in your PR to `deve

### Changed

- **SDK publish** — `pnpm publish:packages` requires a clean tagged `origin/main` checkout with per-package version tags pushed, stamps `gitHead`, blocks direct folder publishes / `workspace:*`, aborts if core fails before dependents, and runs publish-guard tests in CI

### Database

---
Expand Down
21 changes: 20 additions & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,26 @@ SDKs are published as `@telemetry-tracker/*` on npm:
| `packages/telemetry-react-native` | `@telemetry-tracker/react-native` |
| `packages/telemetry-vite-plugin` | `@telemetry-tracker/vite-plugin` |

Publish (maintainers): create the `@telemetry-tracker` npm org, `npm login`, then `pnpm publish:packages`. After the first publish under the new scope, deprecate the legacy `@tacko/telemetry-*` packages with a message pointing to `@telemetry-tracker/*`.
Publish (maintainers): from a **clean checkout of `origin/main`** with per-package release tags pushed (so `gitHead` on npm matches the commit):

```bash
git fetch origin main --tags
git checkout main && git pull origin main
# tags on this commit, e.g. sdk-core-v1.5.0 sdk-node-v1.4.0 sdk-vite-plugin-v1.1.0
pnpm publish:packages -- --only=core,node,vite-plugin --otp=123456
# local dry run (only this combo may skip clean/tag checks):
pnpm publish:dry -- --only=core,node,vite-plugin --allow-dirty
```

The publish script:

- refuses a dirty tree, an untagged HEAD, HEAD ≠ `origin/main`, or tags not pushed to origin
- requires a tag matching each package version (`sdk-<alias>-v<version>` or `<name>@<version>`)
- stamps `gitHead`, rewrites `workspace:*` → `^<core version>` for the tarball only
- sets `TELEMETRY_SDK_RELEASE_PUBLISH=1` (package `prepublishOnly` blocks direct folder publishes)
- **aborts** if core publish fails so node is not published against a missing core

`--allow-dirty` alone is rejected for real publishes. After the first publish under the new scope, deprecate the legacy `@tacko/telemetry-*` packages with a message pointing to `@telemetry-tracker/*`.

Design and entitlement rules are summarized in [docs/ENTITLEMENTS.md](docs/ENTITLEMENTS.md); architecture in [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md); deployment in [DEPLOYMENT.md](DEPLOYMENT.md) and [docs/RAILWAY.md](docs/RAILWAY.md); RBAC in [docs/RBAC.md](docs/RBAC.md).

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -272,7 +272,7 @@ Please follow the [Code of Conduct](CODE_OF_CONDUCT.md). Report security issues
| SDK guides | [docs/sdk-core.md](docs/sdk-core.md), [docs/sdk-next.md](docs/sdk-next.md), [docs/sdk-node.md](docs/sdk-node.md), [docs/sdk-nestjs.md](docs/sdk-nestjs.md), [docs/sdk-vue.md](docs/sdk-vue.md), [docs/sdk-nuxt.md](docs/sdk-nuxt.md), [docs/sdk-react-native.md](docs/sdk-react-native.md) |
| Source maps | [docs/source-maps.md](docs/source-maps.md) |

**Publish SDK packages:** `npm login` → `pnpm publish:packages` (see [CONTRIBUTING.md](CONTRIBUTING.md) and root `package.json` scripts).
**Publish SDK packages:** from a clean tagged checkout, `npm login` → `pnpm publish:packages` (optional `--only=core,node,vite-plugin`). See [CONTRIBUTING.md](CONTRIBUTING.md) and root `package.json` scripts.

**GitHub social preview:** In repo **Settings → General → Social preview**, use `https://telemetry-tracker.com/og-banner.png` (1024×409 marketing banner) once the dashboard is deployed. Install path for docs and marketing: `@telemetry-tracker/core` (see npm badges above).

Expand Down
11 changes: 7 additions & 4 deletions apps/dashboard/app/docs/node/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,15 +52,18 @@ trackError(new Error("DB connection failed"), { db: "primary" });`}
<h2>Global error handlers</h2>
<p>
After <code>init()</code>, <code>uncaughtException</code> and{" "}
<code>unhandledRejection</code> are patched to send errors to the ingest API (and then
rethrow / continue so your process can still exit or log as usual).
<code>unhandledRejection</code> are patched to send errors to the ingest API, flush (up to
2s), then <code>process.exit(1)</code> — matching Node’s default crash behaviour. Set{" "}
<code>exitOnUnhandledRejection: false</code> if you only want rejections reported without
exiting.
</p>

<h2>Request middleware</h2>
<p>
Optional: use <code>middleware()</code> to send a <code>$request</code> event per HTTP
request (method, url, duration). Attach it to your server framework (Express, Fastify,
NestJS, etc.) so it runs for each request.
request (method, url, <code>duration_ms</code> until the response finishes). Attach it to
your server framework (Express, Fastify, NestJS, etc.) so it runs for each request.{" "}
<code>next()</code> is called exactly once.
</p>
<CodeBlock
code={`import { middleware } from "@telemetry-tracker/node";
Expand Down
8 changes: 5 additions & 3 deletions apps/dashboard/app/error-tracking/nodejs/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,9 +71,11 @@ export default function NodeJsErrorTrackingPage() {
<p>
<code>@telemetry-tracker/node</code> wraps core for servers. After <code>init()</code>,
it installs handlers for <code>uncaughtException</code> and{" "}
<code>unhandledRejection</code>, then rethrows / continues so your process can still exit
or log as usual. Optional request middleware sends a <code>$request</code> event per HTTP
call.
<code>unhandledRejection</code>, flushes the error to ingest, then exits with code 1
(Node’s default). Set <code>exitOnUnhandledRejection: false</code> to only report
rejections and keep the process running. Optional request middleware sends a{" "}
<code>$request</code> event per HTTP call with <code>duration_ms</code> until the
response finishes.
</p>
}
>
Expand Down
35 changes: 20 additions & 15 deletions docs/sdk-node.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,14 @@ In a monorepo workspace:
pnpm add @telemetry-tracker/node
```

Requires `@telemetry-tracker/core` **^1.5.0** (provides `ingestError` for fatal flushes).

## Setup

Call **`init(config)`** once at process startup (e.g. before starting your HTTP server). This will:

- Initialize the core SDK.
- Register `process.on("uncaughtException")` and `process.on("unhandledRejection")` to report those errors before rethrowing (or exiting).
- Register `process.on("uncaughtException")` and `process.on("unhandledRejection")` to report those errors, flush ingest (up to 2s), then exit.

```ts
import { init, trackEvent, trackError } from "@telemetry-tracker/node";
Expand All @@ -25,6 +27,7 @@ init({
app: "my-backend",
apiKey: process.env.TELEMETRY_API_KEY,
platform: "node", // default
// exitOnUnhandledRejection: true, // default — report, flush, exit(1)
});
```

Expand All @@ -41,14 +44,21 @@ init({

Config extends [telemetry-core](sdk-core.md#initconfig) and requires `app`; `platform` defaults to `"node"`.

| Option | Default | Description |
|--------|---------|-------------|
| `exitOnUnhandledRejection` | `true` | After reporting an unhandled rejection, flush and `process.exit(1)` (Node’s default since v15). Set `false` to only report and keep running. |
| `fatalFlushTimeoutMs` | `2000` | Max wait for fatal ingest before exit. Cleared when ingest settles (does not keep the process alive). |

## Global error handlers

After `init()`:

- **uncaughtException**: Error is reported with `{ source: "uncaughtException" }`, then rethrown (process typically exits).
- **unhandledRejection**: Reason is reported as an error with `{ source: "unhandledRejection" }`.
- **uncaughtException**: Error is reported with `{ source: "uncaughtException" }`, ingest is flushed (≤ `fatalFlushTimeoutMs`, default 2s), then the process exits with code 1. Non-Error throws (`null`, strings, objects, …) are normalized first.
- **unhandledRejection**: Reason is reported with `{ source: "unhandledRejection" }`. By default the process then flushes and exits with code 1 (same as Node without the SDK). Set `exitOnUnhandledRejection: false` to keep the legacy “report only” behaviour.

With `node --unhandled-rejections=strict`, rejections are also raised as uncaught exceptions; the SDK still reports once and exits 1 (in-flight ingest is awaited if you already called `trackError(err)` before rethrowing).

You can still use `trackError` in try/catch or domain handlers for extra context.
You can still use `trackError` in try/catch for extra context.

## Request middleware

Expand All @@ -58,26 +68,21 @@ You can still use `trackError` in try/catch or domain handlers for extra context
(req, res, next) => void
```

It records a `$request` event with:
`duration_ms` is measured from middleware entry until the **response** emits `finish` or `close` (not the request body `end`). `next()` is called exactly once.

- `method`, `url`, `duration_ms`
- Optionally `body` when `opts.trackRequestBody === true`
Options:

The implementation assumes a minimal `req`: `method`, `url`, and optionally `body` and `on(event, listener)`. It is not tied to Express or Fastify; you can adapt it or use it in a custom stack. Example (conceptual):
- `trackRequestBody` (default `false`): when true, includes `req.body` in the `$request` event properties (use carefully — may contain PII).

```ts
import { init, middleware } from "@telemetry-tracker/node";

init({ ingestUrl: "http://localhost:3001", app: "api", apiKey: process.env.TELEMETRY_API_KEY, environment: "development" });
init({ ingestUrl: "...", app: "api" });

const telemetryMiddleware = middleware({ trackRequestBody: false });

// Use in your stack; call next() so the request continues.
function handleRequest(req, res) {
telemetryMiddleware(req, res, () => {
// your handler
});
}
// Express
app.use(telemetryMiddleware);
```

For Express you’d typically do `app.use(telemetryMiddleware)` if the middleware calls `next()` and matches Express’ (req, res, next) shape. Our middleware is generic and may need a thin wrapper to match your framework’s expectations. For **NestJS**, see [sdk-nestjs.md](sdk-nestjs.md).
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,14 @@
"dev:dashboard": "pnpm --filter dashboard dev",
"build": "pnpm -r run build",
"lint": "eslint apps packages --max-warnings 0",
"test": "pnpm --filter api test && pnpm --filter dashboard test && pnpm --filter @telemetry-tracker/core test && pnpm --filter @telemetry-tracker/vite-plugin test && pnpm --filter @telemetry-tracker/node test && pnpm --filter @telemetry-tracker/next test",
"test": "pnpm --filter api test && pnpm --filter dashboard test && pnpm --filter @telemetry-tracker/core test && pnpm --filter @telemetry-tracker/vite-plugin test && pnpm --filter @telemetry-tracker/node test && pnpm --filter @telemetry-tracker/next test && pnpm test:publish-guards",
"db:generate": "pnpm --filter api exec prisma generate",
"db:migrate": "pnpm --filter api exec prisma migrate dev",
"db:studio": "pnpm --filter api exec prisma studio",
"db:seed-api-key": "pnpm --filter api seed:dev-api-key",
"publish:packages": "pnpm run build && node scripts/publish-packages.mjs",
"publish:dry": "pnpm run build && node scripts/publish-packages.mjs --dry-run"
"publish:dry": "pnpm run build && node scripts/publish-packages.mjs --dry-run",
"test:publish-guards": "node --test scripts/lib/publish-guards.test.mjs"
},
"devDependencies": {
"@eslint/eslintrc": "^3.3.5",
Expand Down
11 changes: 3 additions & 8 deletions packages/telemetry-core/dist/index.d.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { SDK_VERSION } from "./version.js";
export { SDK_VERSION };
export { toReportableError } from "./to-reportable-error.js";
export { scrubPiiText, scrubPiiRecord } from "./pii-scrub.js";
export { WEB_VITAL_EVENT_NAME, installWebVitals, rateWebVital, buildWebVitalProperties, setWebVitalsCaptureEnabled, isWebVitalsCaptureEnabled, type WebVitalEventProperties, type WebVitalMetricName, type WebVitalRating, } from "./web-vitals.js";
export declare function getAnonymousId(): string;
Expand Down Expand Up @@ -49,15 +50,9 @@ declare function resolveClientPiiScrub(cfg: TelemetryConfig | null): {
/** @internal exported for tests */
export { resolveClientPiiScrub };
export declare function trackEvent(name: string, properties?: Record<string, unknown>): void;
export declare function trackError(error: Error | {
message: string;
stack?: string;
}, context?: Record<string, unknown>): void;
export declare function trackError(error: unknown, context?: Record<string, unknown>): void;
/** Send an error and resolve after the ingest request settles. Fatal handlers await this. */
export declare function ingestError(error: Error | {
message: string;
stack?: string;
}, context?: Record<string, unknown>): Promise<void>;
export declare function ingestError(error: unknown, context?: Record<string, unknown>): Promise<void>;
export declare function screen(name: string): void;
export declare function getUserId(): string | null;
export declare function getConfigOrNull(): TelemetryConfig | null;
Expand Down
2 changes: 1 addition & 1 deletion packages/telemetry-core/dist/index.d.ts.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading