diff --git a/docs/observability/apm/how-to/enable-session-replay.md b/docs/observability/apm/how-to/enable-session-replay.md index 08792bbf..7fca1e49 100644 --- a/docs/observability/apm/how-to/enable-session-replay.md +++ b/docs/observability/apm/how-to/enable-session-replay.md @@ -89,6 +89,6 @@ and the drawer shows the masked replay alongside the stack trace. ## Related -- [`@nais/apm` API reference](../reference/apm-client-api.md#init-options) — +- [`@nais/apm` API reference](../reference/apm-client-api.md#initoptions) — full `sessionReplay` options. - [Track frontend errors with `@nais/apm`](../tutorials/track-frontend-errors.md) diff --git a/docs/observability/apm/reference/apm-client-api.md b/docs/observability/apm/reference/apm-client-api.md index 7919aeda..da154456 100644 --- a/docs/observability/apm/reference/apm-client-api.md +++ b/docs/observability/apm/reference/apm-client-api.md @@ -18,7 +18,7 @@ with an ergonomic, capture-oriented API. For installation, see docs/observability/** and this marker whenever a new @nais/apm release ships. Do not reformat the marker — the workflow's sed matches it literally. --> -Latest published release: **0.2.0** — pin this exact version when you install (pre-1.0, see the note below). +Latest published release: **0.4.0** — pin this exact version when you install (pre-1.0, see the note below). !!! note "Pre-1.0" The public API may change across `0.x` minor releases (new options, renamed @@ -35,7 +35,7 @@ existing instance. Returns the underlying Faro instance. init(options?: InitOptions): Faro ``` -### `InitOptions` +### InitOptions All fields are optional. `app`, `version`, `environment`, and `telemetryUrl` each resolve independently (see [Configuration resolution](#configuration-resolution)). @@ -50,7 +50,7 @@ each resolve independently (see [Configuration resolution](#configuration-resolu | `ignoreErrors` | `Patterns` | Extra patterns appended to `DEFAULT_IGNORE_ERRORS`. | | `dangerouslyDisablePiiScrubbing` | `boolean` | Disables built-in PII scrubbing. See [Privacy](#privacy-pii-scrubbing). Default `false`. | | `faro` | `Partial` | Escape hatch: raw Faro overrides, merged last (except `beforeSend`, which stays composed with the scrubber). | -| `sessionReplay` | object | Opt-in session replay. See [`sessionReplay`](#init-options) below. | +| `sessionReplay` | object | Opt-in session replay. See [`sessionReplay`](#initoptions) below. | | `screenshotOnError` | `boolean` | Opt-in masked DOM snapshot per new error. Off by default; auto-disabled when `sessionReplay` is enabled. | `sessionReplay` fields: `enabled` (`boolean`), `mode` (`'on-error'` default, or @@ -133,10 +133,11 @@ setUser(user: User): void clearUser(): void ``` -`User`: `{ id?, email?, username? }`. Set a hashed subject as `id` where you can. +`User`: `{ id?, email?, username? }`. Pass an **opaque, non-identifying** +`id` only — see [`setUser` and user identity](#setuser-and-user-identity). ```ts -setUser({ id: hashedSubject, email: 'user@example.com', username: 'jdoe' }); +setUser({ id: hashedSubject }); // opaque id only — email/username/idents are dropped // on logout: clearUser(); ``` @@ -227,6 +228,22 @@ GDPR consequences of everything the app sends. Scrubbing is regex-based and best-effort — a safety net, **not** a GDPR guarantee. Don't put personal data in error messages in the first place. +### setUser and user identity + +Telemetry lands in a **shared Loki instance that every team can read**, so a +user's identity must never reach it. `setUser` enforces this in code — it is not +a convention you can opt out of: + +- it **drops** any `id` / `username` / `attributes` value that looks like a + fødselsnummer, email, or raw NAV ident (and warns once); +- it drops the `email` field **unconditionally** (it is deprecated). + +Pass an **opaque, non-identifying** id — a salted hash, never a raw ident: + +```ts +setUser({ id: hash(fnr) }); // an ident/email/fnr passed here is silently dropped +``` + ## Local development On `localhost` (or anywhere no collector URL resolves), `init()` warns once, @@ -242,8 +259,52 @@ instrumentation that captures `console.error('msg', err)` with a real stack trace), `resolveConfig` / `versionFromImage`, `FEEDBACK_EVENT_NAME`, and `VERSION`. Most apps never need these directly. -## Known limitations (0.1.0) - -- No tracing support yet (`@grafana/faro-web-tracing` integration planned). -- No `@nais/apm/react` entry point yet (e.g. an `ErrorBoundary`). -- Published to the GitHub Package Registry only. +## Limitations & differences from Sentry + +`@nais/apm` gives you Sentry-shaped ergonomics but is deliberately **not** a +drop-in for the whole Sentry API. This is the canonical list of what differs and +what has no equivalent — migrating off Sentry? Read it before you delete +`@sentry/*`. + +### Behaves differently + +- **No event ID from `captureException`.** `Sentry.captureException` returns an + event ID; `@nais/apm`'s `captureException` returns `void` (a Faro limitation), + and there is no `lastEventId()`. Any + pattern that relied on the returned id — showing a reference code to the user, + wiring a crash-report dialog (`showReportDialog`) to an event — does not carry + over. Use your own correlation id (e.g. a `crypto.randomUUID()` you also attach + as `context`) instead. +- **`setTag` is context, not an indexed label.** Faro has no first-class tag + concept, so a `setTag` value rides along as **context** on every subsequent + capture rather than as a searchable, indexed label. +- **`setUser` drops PII.** Only opaque/hashed ids survive — see + [`setUser` and user identity](#setuser-and-user-identity). +- **Replay is a preview feature and defaults to the events tier.** + `sessionReplay: { enabled: true }` records a DOM-free interaction timeline, not + Sentry's full DOM recording. Full masked-DOM capture (`tier: 'dom'`) pushes DOM + into shared Loki and is gated on the personvernombud process. Replay is off by + default. See [Enable session replay](../how-to/enable-session-replay.md). +- **No bundler plugin or source-map upload.** There is no `@sentry/webpack-plugin` + / `withSentryConfig` equivalent and no `SENTRY_AUTH_TOKEN`. Minified stack + traces are resolved **server-side** by the platform collector from `.map` files + on the CDN — you only emit and deploy sourcemaps, nothing to upload. See + [Sourcemap deobfuscation](../../frontend/how-to/sourcemaps.md). + +### Deliberately unsupported Sentry APIs + +These have **no `@nais/apm` equivalent**: + +| Sentry API | Notes | +| ---------- | ----- | +| `addBreadcrumb()` / `beforeBreadcrumb` | No manual breadcrumb API. The events-tier replay timeline captures interactions automatically, but you can't push custom breadcrumbs. | +| `withScope` / `configureScope` / `getCurrentScope` | No per-scope isolation. Context set via `setTag` / `setContext` is module-global. | +| `startSpan` / `startInactiveSpan` / `startTransaction` | No manual span/transaction API. Tracing is on/off auto-instrumentation of fetch/XHR only. | +| `setExtra` / `setExtras` / `setTags` (plural) | Use `setTag` (single) and `setContext`. | +| `lastEventId()` | `captureException` returns `void`. | +| `showReportDialog()` / crash-report modal | `captureFeedback()` is programmatic and preview/internal-pilot only — no built-in widget. | +| `withProfiler` / profiling, release-health / session tracking, attachments | Not supported. | +| React Router v5 / v7 and data routers | Route tracking covers React Router v6 and the Next.js App Router only. | + +If you depend on one of these, log it before you delete Sentry and track it +against the [`@nais/apm` CHANGELOG](https://github.com/nais/apm/blob/main/CHANGELOG.md). diff --git a/docs/observability/apm/reference/issues-model.md b/docs/observability/apm/reference/issues-model.md index c141169a..6d6eaaef 100644 --- a/docs/observability/apm/reference/issues-model.md +++ b/docs/observability/apm/reference/issues-model.md @@ -40,7 +40,7 @@ The fingerprint is decided by the first of these tiers that applies: 1. **Explicit override.** If the error carries a custom grouping key — the `fingerprint` option on - [`captureException`](apm-client-api.md#captureexceptionerror-options) — that + [`captureException`](apm-client-api.md) — that wins. Use it to force-group (or force-split) errors you know belong together. 2. **Type + normalized message.** The exception type combined with its message after normalization — dynamic parts like ids, numbers, and UUIDs are replaced diff --git a/docs/observability/apm/tutorials/track-frontend-errors.md b/docs/observability/apm/tutorials/track-frontend-errors.md index 68bba9dd..203b553f 100644 --- a/docs/observability/apm/tutorials/track-frontend-errors.md +++ b/docs/observability/apm/tutorials/track-frontend-errors.md @@ -56,8 +56,8 @@ Pin an exact version — `@nais/apm` is pre-1.0, so the API can change in minor releases (see the pre-release note above): ```sh -npm install @nais/apm@0.2.0 -# or: pnpm add @nais/apm@0.2.0 / yarn add @nais/apm@0.2.0 +npm install @nais/apm@0.4.0 +# or: pnpm add @nais/apm@0.4.0 / yarn add @nais/apm@0.4.0 ``` ## 3. Initialize (zero config) diff --git a/docs/observability/frontend/how-to/migrate-from-sentry.md b/docs/observability/frontend/how-to/migrate-from-sentry.md index 96671fad..c2f58422 100644 --- a/docs/observability/frontend/how-to/migrate-from-sentry.md +++ b/docs/observability/frontend/how-to/migrate-from-sentry.md @@ -32,21 +32,21 @@ The end state is **idiomatic `@nais/apm`** with Sentry **fully removed**: no same *shapes* (`captureException`, `setUser`, an error boundary) under its own names, so you migrate call sites once and delete Sentry for good. A handful of Sentry features have no equivalent yet — see - [What's different](#whats-different-read-this) before you start. + [Differences from Sentry](#differences-from-sentry) before you start. !!! note "Status: pre-release" `@nais/apm` is pre-1.0. Pin an exact version and read the [CHANGELOG](https://github.com/nais/apm/blob/main/CHANGELOG.md) before - upgrading. This guide targets `0.2.0`. + upgrading. This guide targets `0.4.0`. ## Concept map | Sentry | `@nais/apm` | Notes | | ------ | ----------- | ----- | | `Sentry.init({ dsn, ... })` | [`init()`](../../apm/reference/apm-client-api.md#initoptions) / [`initNaisAPMClient()`](../../apm/tutorials/track-frontend-errors.md#3-initialize-zero-config) | Zero-config on Nais — **no DSN**. App name, version, environment, collector URL all resolve automatically. | -| `Sentry.captureException(e)` | `captureException(e, { context, fingerprint })` | Returns **`void`** — no event ID (see [What's different](#no-event-id-from-captureexception)). | +| `Sentry.captureException(e)` | `captureException(e, { context, fingerprint })` | Returns **`void`** — no event ID ([Limitations](../../apm/reference/apm-client-api.md#limitations-differences-from-sentry)). | | `Sentry.captureMessage(m, level)` | `captureMessage(m, level)` | Same severity levels. | -| `Sentry.setUser({ id })` | `setUser({ id })` | **PII is dropped.** Opaque/hashed ids only — [see below](#setuser-drops-pii). | +| `Sentry.setUser({ id })` | `setUser({ id })` | **PII is dropped.** Opaque/hashed ids only — [see Privacy](../../apm/reference/apm-client-api.md#setuser-and-user-identity). | | `Sentry.setUser(null)` | `clearUser()` | On logout. | | `Sentry.setTag(k, v)` | `setTag(k, v)` | Approximation — rides as context, **not** an indexed label. | | `Sentry.setContext(name, obj)` | `setContext(name, obj)` | Flattened as `name.key`; `setContext(name, null)` removes it. | @@ -56,7 +56,7 @@ The end state is **idiomatic `@nais/apm`** with Sentry **fully removed**: no | React Router integration | `enableApmReactRouterV6()` + `` | React Router v6. | | `Sentry.captureRouterTransitionStart` (Next.js) | `useApmRouteTracking()` hook | Next.js App Router. | | `browserTracingIntegration` / `tracesSampleRate` | `init({ tracing: true })` | On/off; auto-instruments fetch/XHR. See [trace propagation](trace-propagation.md). | -| `replayIntegration()` | `init({ sessionReplay: { enabled: true } })` | Defaults to the **events tier** (no DOM) — [see below](#replay-defaults-to-the-events-tier). | +| `replayIntegration()` | `init({ sessionReplay: { enabled: true } })` | Defaults to the **events tier** (no DOM) — [Limitations](../../apm/reference/apm-client-api.md#limitations-differences-from-sentry). | | `ignoreErrors: [...]` | `init({ ignoreErrors: [...] })` | Appended to `DEFAULT_IGNORE_ERRORS` (extensions, ResizeObserver, `Script error.`). | | `beforeSend(event)` | `init({ beforeSend })` | Runs **before** the mandatory PII scrubber. | | `@sentry/webpack-plugin` / `withSentryConfig` sourcemap upload | *(nothing to configure)* | Stack traces resolve **server-side** from the CDN — [sourcemaps](sourcemaps.md). | @@ -88,7 +88,7 @@ Delete the DSN and auth-token wiring while you're here — you won't need them: [Track frontend errors](../../apm/tutorials/track-frontend-errors.md#1-configure-the-package-registry): ```sh -npm install @nais/apm@0.2.0 +npm install @nais/apm@0.4.0 ``` The React helpers (error boundary, route tracking, Next.js client init) live in @@ -231,7 +231,7 @@ HOC mirrors `Sentry.withErrorBoundary`. The `fallback` can also be a render prop !!! warning "React Router v5/v7 and data routers are not wired yet" Route tracking currently covers **React Router v6** and the **Next.js App - Router** only. See [What's different](#not-yet-supported). + Router** only. See [Limitations](../../apm/reference/apm-client-api.md#limitations-differences-from-sentry). ### 5. Tracing (optional) @@ -283,8 +283,8 @@ init({ sessionReplay: { enabled: true, mode: 'on-error' } }); This defaults to the **events tier** — a DOM-free interaction timeline, safe by construction. It is **not** a like-for-like replacement for Sentry's full DOM recording; opting into DOM capture is a deliberate, personvernombud-gated -decision. Read [What's different](#replay-defaults-to-the-events-tier) and -[Enable session replay](../../apm/how-to/enable-session-replay.md) before you +decision. Read the [session replay limitations](../../apm/reference/apm-client-api.md#limitations-differences-from-sentry) +and [Enable session replay](../../apm/how-to/enable-session-replay.md) before you turn anything on. ### 8. Move alerting to Grafana @@ -294,101 +294,36 @@ Sentry alert rules don't come across. Recreate them as (error rate, exception spike, new exceptions, web vitals) — delivered through your team's Grafana contact points. -## What's different (read this) - -This is the honest section. A clean migration means understanding where -`@nais/apm` is deliberately *not* Sentry. - -### No event ID from `captureException` - -`Sentry.captureException` returns an event ID; `@nais/apm`'s returns `void` (a -Faro limitation). There is **no `lastEventId()`**. If any code relied on the -returned id — to show a reference code to the user, or to link a crash-report -dialog to an event — that pattern doesn't carry over. Use your own correlation id -(e.g. a `crypto.randomUUID()` you also attach as `context`) instead. - -### `setUser` drops PII - -This is the personvern rule, and it is enforced in code — call it out on your -team before you migrate. NAV telemetry lands in a **shared Loki instance that -every team can read**, so identities must never reach it. `setUser`: - -- **drops** any `id` / `username` / `attributes` value that looks like a - fødselsnummer, email, or raw NAV ident (and warns once); -- drops the `email` field **unconditionally** (it is deprecated). - -Pass an **opaque, non-identifying** id — a salted hash, never a raw ident: - -```ts -// Before (Sentry) — teams already hash, keep doing it: -Sentry.setUser({ id: hash(fnr) }); - -// After (@nais/apm) — same idea, PII is a hard floor: -setUser({ id: hash(fnr) }); // an ident/email/fnr passed here is silently dropped -``` - -The transport also runs a mandatory PII scrubber over every signal (fnr → `[fnr]`, -email → `[email]`, token URL params → `[redacted]`). It's a safety net, not a -GDPR guarantee — don't put personal data in error messages in the first place. - -### Issues live in the Grafana Issues tab - -Errors show up in the **Issues tab** of your service in -[Nais APM](<>), not in -Sentry's issue stream. The [triage model](../../apm/how-to/triage-an-issue.md) is -different: Resolve / Ignore / Assign is shared team state stored in Grafana -annotations, a resolved issue that recurs after a newer deploy is flagged -**Regressed**, and you can personally **mute** without touching shared state. -Grouping is driven by [fingerprinting](../../apm/reference/issues-model.md) — pass -a `fingerprint` to `captureException` to control it, the same idea as Sentry's -`fingerprint`. - -### Source maps resolve server-side - -No `sentry-cli`, no `@sentry/webpack-plugin`, no upload step, no `SENTRY_AUTH_TOKEN`. -Deobfuscation happens in the collector by fetching `.map` files from `cdn.nav.no`. -The catch: it **only works for bundles served from the CDN**. A purely -server-rendered app that serves its own JS from the pod won't get resolved stack -traces unless it also ships static assets to the CDN. See -[Sourcemaps](sourcemaps.md#requirements). - -### Alerts are Grafana alerts - -Sentry's alert rules, notification integrations, and issue-owner routing don't -migrate. Alerting is Grafana alerting, seeded from -[Nais APM templates](../../apm/how-to/create-alerts.md). Note there's **no -SLO/burn-rate template yet**. - -### Replay defaults to the events tier - -Sentry Replay records the DOM. `@nais/apm`'s `sessionReplay: { enabled: true }` -defaults to the **events tier**: a lightweight interaction timeline (navigation, -clicks, coarse scroll) with **no DOM node tree** — structurally nothing to leak. -Full masked-DOM recording exists (`tier: 'dom'`) but pushes DOM into shared Loki -and is **gated on the personvernombud process** — do not enable it on -citizen-facing apps without sign-off. Replay is a **preview** feature and off by -default. - -### Not yet supported - -These Sentry features have **no `@nais/apm` equivalent** at `0.2.0`. State this -plainly to your team so nobody is surprised mid-migration: - -| Sentry feature | Status in `@nais/apm` | -| -------------- | --------------------- | -| `addBreadcrumb()` / `beforeBreadcrumb` | **Not supported.** No manual breadcrumb API. The events-tier replay timeline captures interactions automatically, but you can't push custom breadcrumbs. | -| Manual spans / performance — `startSpan`, `startInactiveSpan`, `startTransaction` | **Not supported.** Tracing is on/off auto-instrumentation of fetch/XHR only; there is no manual span/transaction API. | -| Scopes — `withScope`, `configureScope`, `getCurrentScope` | **Not supported.** Context set via `setTag` / `setContext` is module-global; there's no per-scope isolation. | -| `setExtra` / `setExtras` / `setTags` (plural) | **Not supported.** Use `setTag` (single) and `setContext`. | -| `lastEventId()` | **Not supported** — `captureException` returns `void`. | -| `showReportDialog()` / crash-report modal | **Not supported.** `captureFeedback()` is programmatic and preview/internal-pilot only — no built-in widget. | -| Profiling, release-health/session tracking, attachments | **Not supported.** | -| React Router v5 / v7 / data routers | **Not yet** — v6 and Next.js App Router only. | - -If you depend on one of these, note it before you delete Sentry, and track it -against the [`@nais/apm` CHANGELOG](https://github.com/nais/apm/blob/main/CHANGELOG.md). -`setTag` also differs subtly: Faro has no indexed tag concept, so a tag rides -along as **context** on every capture rather than as a searchable label. +## Differences from Sentry + +`@nais/apm` is deliberately not a drop-in for the whole Sentry API. Before you +delete `@sentry/*`, read the canonical +[Limitations & differences from Sentry](../../apm/reference/apm-client-api.md#limitations-differences-from-sentry) +— it covers the unsupported APIs (`addBreadcrumb`, scopes, manual spans, +`setExtra`/`setExtras`, `lastEventId`, `showReportDialog`, `withProfiler`, +React Router v5/v7), the source-map and replay model, and more. + +The **two gotchas** that bite migrations hardest: + +!!! warning "Two headline gotchas" + - **No event ID from `captureException`.** It returns `void` — no + `lastEventId()`, so `showReportDialog`/eventId patterns don't carry over. + [Details →](../../apm/reference/apm-client-api.md#limitations-differences-from-sentry) + - **`setUser` drops PII.** Only opaque/hashed ids survive; email/idents/fnr + are silently dropped. + [Details →](../../apm/reference/apm-client-api.md#setuser-and-user-identity) + +A few migration-specific pointers, each covered in full on its own page: + +- **Issues** show up in the Grafana **Issues tab**, not Sentry's stream — the + triage model (Resolve / Ignore / Assign, Regressed, personal mute) is + explained in [Triage an issue](../../apm/how-to/triage-an-issue.md). Grouping + is driven by [fingerprinting](../../apm/reference/issues-model.md). +- **Alerts** are Grafana alerts seeded from + [Nais APM templates](../../apm/how-to/create-alerts.md) — Sentry's alert rules + and routing don't migrate. +- **Source maps** resolve server-side; there's nothing to upload. See + [Sourcemap deobfuscation](sourcemaps.md#requirements). ## Migration checklist @@ -404,7 +339,7 @@ along as **context** on every capture rather than as a searchable label. - [ ] Enabled `tracing: true` if you used `browserTracingIntegration` (and CORS allows `traceparent`). - [ ] Removed sourcemap upload; verified `.map` files ship to the CDN ([sourcemaps](sourcemaps.md)). - [ ] Recreated alerts as [Grafana alerts](../../apm/how-to/create-alerts.md). -- [ ] Audited [not-yet-supported](#not-yet-supported) features and logged any gaps. +- [ ] Audited [Limitations & differences from Sentry](../../apm/reference/apm-client-api.md#limitations-differences-from-sentry) and logged any gaps. - [ ] Deployed, triggered a test error, and confirmed it appears in the [Issues tab](../../apm/tutorials/track-frontend-errors.md#6-see-your-errors-as-issues). ## Related diff --git a/docs/observability/frontend/how-to/quickstart-nextjs.md b/docs/observability/frontend/how-to/quickstart-nextjs.md index c6ed6cbc..490377f2 100644 --- a/docs/observability/frontend/how-to/quickstart-nextjs.md +++ b/docs/observability/frontend/how-to/quickstart-nextjs.md @@ -27,7 +27,7 @@ This guide covers both routers: !!! note "Status: pre-release" `@nais/apm` is pre-1.0. Pin an exact version and read the [CHANGELOG](https://github.com/nais/apm/blob/main/CHANGELOG.md) before - upgrading. This guide targets `0.2.0`. + upgrading. This guide targets `0.4.0`. !!! tip "Migrating off Sentry?" If your app calls `Sentry.init` today, follow @@ -46,8 +46,8 @@ This guide covers both routers: ## Install ```sh -pnpm add @nais/apm@0.2.0 -# or: npm install @nais/apm@0.2.0 / yarn add @nais/apm@0.2.0 +pnpm add @nais/apm@0.4.0 +# or: npm install @nais/apm@0.4.0 / yarn add @nais/apm@0.4.0 ``` The React helpers used below — `initNaisAPMClient`, `ApmErrorBoundary`, @@ -252,13 +252,8 @@ Router. ## Ship readable stack traces -Production stack traces point at minified JavaScript. The Nais telemetry collector -maps them back to your source **server-side** by fetching your `.map` files from -the CDN — no upload step, no auth token. You only need to emit sourcemaps and ship -them alongside your bundle. - -For Next.js, enable browser sourcemaps and follow the CDN requirement in -[Sourcemap deobfuscation](sourcemaps.md): +Stack traces resolve **server-side** from sourcemaps on the CDN — nothing to +upload ([how it works](sourcemaps.md)). You just have to emit them. For Next.js: ```js // next.config.js @@ -268,28 +263,19 @@ module.exports = { ``` !!! warning "Server-rendered assets don't get deobfuscated" - Sourcemap resolution only works for bundles served from `cdn.nav.no`. A purely + Resolution only works for bundles served from the CDN. A purely server-rendered Next app that serves its own JS from the pod won't get resolved stack traces unless it also ships static assets to the CDN. See [Sourcemaps → Requirements](sourcemaps.md#requirements). ## Don't send personal data -Nais telemetry lands in a **shared Loki instance every team can read**, so -identities must never reach it. Two rules: - -- `setUser` takes an **opaque, non-identifying** id only — a salted hash, never a - raw NAV ident, fødselsnummer, or email. Values that look like PII are dropped in - code, and the `email` field is dropped unconditionally. - - ```ts - import { setUser } from '@nais/apm'; - setUser({ id: hash(fnr) }); // an ident/email/fnr passed here is silently dropped - ``` - -- A mandatory PII scrubber runs over every outgoing signal (fnr → `[fnr]`, - email → `[email]`, token URL params → `[redacted]`). It's a safety net, not a - GDPR guarantee — don't put personal data in error messages in the first place. +Telemetry lands in a **shared Loki instance every team can read**, so identities +must never reach it. `setUser` takes an **opaque/hashed id** only — emails, +idents, and fødselsnummer are dropped in code — and a mandatory PII scrubber runs +over every outgoing signal. See +[Privacy: PII scrubbing](../../apm/reference/apm-client-api.md#setuser-and-user-identity) +for the full rules. ## See your errors as issues diff --git a/docs/observability/frontend/how-to/quickstart-react-vite.md b/docs/observability/frontend/how-to/quickstart-react-vite.md index 991a3e2c..b55641d3 100644 --- a/docs/observability/frontend/how-to/quickstart-react-vite.md +++ b/docs/observability/frontend/how-to/quickstart-react-vite.md @@ -19,7 +19,7 @@ React Router. !!! note "Status: pre-release" `@nais/apm` is pre-1.0. Pin an exact version and read the [CHANGELOG](https://github.com/nais/apm/blob/main/CHANGELOG.md) before - upgrading. This guide targets `0.2.0`. + upgrading. This guide targets `0.4.0`. !!! tip "Migrating off Sentry?" If your app calls `Sentry.init` today, follow @@ -38,8 +38,8 @@ React Router. ## Install ```sh -pnpm add @nais/apm@0.2.0 -# or: npm install @nais/apm@0.2.0 / yarn add @nais/apm@0.2.0 +pnpm add @nais/apm@0.4.0 +# or: npm install @nais/apm@0.4.0 / yarn add @nais/apm@0.4.0 ``` The React helpers used below — `ApmErrorBoundary`, `enableApmReactRouterV6`, @@ -179,10 +179,8 @@ still has to allow the `traceparent` CORS header and export spans to Tempo — s ## Ship readable stack traces -Production stack traces point at minified JavaScript. The Nais telemetry collector -maps them back to your source **server-side** by fetching your `.map` files from -the CDN — no upload step, no auth token. You only need to emit sourcemaps and ship -them alongside your bundle. For Vite: +Stack traces resolve **server-side** from sourcemaps on the CDN — nothing to +upload ([how it works](sourcemaps.md)). You just have to emit them. For Vite: ```js // vite.config.js @@ -193,27 +191,17 @@ export default { }; ``` -A Vite SPA served from `cdn.nav.no` is the ideal case for deobfuscation — the -`.map` files sit right next to the bundle. Follow -[Sourcemap deobfuscation](sourcemaps.md) for the CDN requirement. +A Vite SPA served from the CDN is the ideal case — the `.map` files sit next to +the bundle. See [Sourcemap deobfuscation → Requirements](sourcemaps.md#requirements). ## Don't send personal data -Nais telemetry lands in a **shared Loki instance every team can read**, so -identities must never reach it. Two rules: - -- `setUser` takes an **opaque, non-identifying** id only — a salted hash, never a - raw NAV ident, fødselsnummer, or email. Values that look like PII are dropped in - code, and the `email` field is dropped unconditionally. - - ```ts - import { setUser } from '@nais/apm'; - setUser({ id: hash(fnr) }); // an ident/email/fnr passed here is silently dropped - ``` - -- A mandatory PII scrubber runs over every outgoing signal (fnr → `[fnr]`, - email → `[email]`, token URL params → `[redacted]`). It's a safety net, not a - GDPR guarantee — don't put personal data in error messages in the first place. +Telemetry lands in a **shared Loki instance every team can read**, so identities +must never reach it. `setUser` takes an **opaque/hashed id** only — emails, +idents, and fødselsnummer are dropped in code — and a mandatory PII scrubber runs +over every outgoing signal. See +[Privacy: PII scrubbing](../../apm/reference/apm-client-api.md#setuser-and-user-identity) +for the full rules. ## See your errors as issues