update rum
This commit is contained in:
1 parent
7b68f60202
commit
193e6c6e39
15 files changed
+2760
-1080
No files matched your search
+205
-6
@@ -31,6 +31,21 @@ Load the RUM script and initialize it once, before the application starts:
|
||||
</script>
|
||||
```
|
||||
|
||||
To avoid parser-blocking downloads, use a deferred script with declarative
|
||||
initialization instead of the two scripts above:
|
||||
|
||||
```html
|
||||
<script defer src="https://otel.example.com/static/otel-rum.js"
|
||||
data-service-name="checkout-web"
|
||||
data-release="2026.09.18-a1b2c3"
|
||||
data-environment="production"
|
||||
data-base-url="https://otel.example.com"></script>
|
||||
```
|
||||
|
||||
Declarative initialization runs at `DOMContentLoaded`. Use the earlier,
|
||||
explicit initialization when capturing errors and requests during application
|
||||
startup matters more than avoiding parser blocking. Do not initialize twice.
|
||||
|
||||
The collector captures uncaught errors, unhandled promise rejections, failed
|
||||
HTTP requests, breadcrumbs, Web Vitals, and optional trace context. Correlate
|
||||
frontend requests with backend spans when possible:
|
||||
@@ -52,6 +67,170 @@ try {
|
||||
Do not put secrets, tokens, or sensitive customer data in breadcrumbs or
|
||||
custom attributes.
|
||||
|
||||
### Cookieless diagnostics and consent
|
||||
|
||||
Neither RUM nor Todo reads or writes cookies, sessionStorage, or localStorage.
|
||||
Configuration and consent are held only in memory. Integrate with the
|
||||
application's existing consent manager rather than adding a tracking cookie
|
||||
or another banner:
|
||||
|
||||
```js
|
||||
const rum = OtelRUM.init({ serviceName: "checkout-web" });
|
||||
rum.setConsent("granted");
|
||||
rum.setConsent("denied");
|
||||
```
|
||||
|
||||
For a previously accepted choice, supply `consent: "granted"` at initialization
|
||||
or `data-consent="granted"` on the script. Omitted consent means `"denied"`;
|
||||
explicit configuration overrides the attribute. `rum.getConsent()` returns
|
||||
the current choice. The application is responsible for persisting that choice
|
||||
and applying it on the next page load.
|
||||
|
||||
The SDK separates error diagnostics from optional performance collection:
|
||||
|
||||
| Policy | Error payload | Performance collection |
|
||||
| --- | --- | --- |
|
||||
| Default `counts` | Generic event, severity, recognised error type, HTTP status, release/environment | Off without consent |
|
||||
| Configured `minimal` | Counts plus application-sanitized diagnostic fields | Still off without consent |
|
||||
| `consented` | Detailed errors, stacks, context, breadcrumbs and page metadata | On unless disabled |
|
||||
| Explicit, host-allowlisted `development` | Detailed diagnostics for an already-approved development site | On unless disabled |
|
||||
|
||||
`rum.getPrivacyMode()` reports the configured active policy. An individual
|
||||
minimal event falls back to counts if its sanitizer does not approve it.
|
||||
Counts/minimal payloads exclude automatic URLs, user agents, viewport/connection
|
||||
details, session ids, DOM selectors, breadcrumbs, context and custom attributes.
|
||||
Per-request trace-header instrumentation still runs without persistent visitor
|
||||
identity, but counts/minimal log records do not include trace ids. Neither
|
||||
policy is a guarantee of anonymity or a consent exemption.
|
||||
|
||||
### Minimal actual-error diagnostics
|
||||
|
||||
Only configure this policy after separately assessing the diagnostic purpose,
|
||||
ePrivacy position and GDPR legal basis; it is not automatically enabled when
|
||||
tracking consent is rejected. Provide an application-owned sanitizer that maps
|
||||
known errors to useful, reviewed diagnostics:
|
||||
|
||||
```js
|
||||
const safeDiagnostics = new Map([
|
||||
["checkout.price-missing", {
|
||||
message: "Checkout price is missing",
|
||||
code: "CHECKOUT_PRICE_MISSING"
|
||||
}]
|
||||
]);
|
||||
|
||||
const rum = OtelRUM.init({
|
||||
serviceName: "checkout-web",
|
||||
minimalDiagnostics: {
|
||||
route: "/checkout/:step",
|
||||
sanitize: event => safeDiagnostics.get(event.message) || null
|
||||
}
|
||||
});
|
||||
|
||||
rum.captureMessage("checkout.price-missing");
|
||||
```
|
||||
|
||||
The sanitizer receives bounded `message`, optional `type` and `stacktrace`.
|
||||
It may return `{message, code?, stacktrace?}`; only those fields and the
|
||||
configured route template are added. Return `null` for unknown/free-text
|
||||
events. Missing, throwing or invalid sanitizers fall back to counts without
|
||||
sending the original message or stack. Output lengths are bounded, but **the
|
||||
SDK trusts sanitizer output: it does not automatically scrub returned text**.
|
||||
Only return reviewed messages and necessary scrubbed stack frames; never
|
||||
forward `event` unchanged. Exclude credentials, query strings, fragments,
|
||||
user-specific paths, user content and sensitive data. The route must be an
|
||||
application-controlled template, not `location.pathname`.
|
||||
|
||||
On withdrawal, detailed queued records are dropped and an independently
|
||||
configured minimal policy continues. Without that policy, counts continue.
|
||||
Do not use this to relabel consent-required processing after refusal. Even
|
||||
counts require a deployment-specific assessment. See `docs/EU-RUM-PRIVACY.md`.
|
||||
|
||||
### Development sites with existing access and acceptance
|
||||
|
||||
Development mode is explicit and requires an **exact hostname allowlist**:
|
||||
|
||||
```js
|
||||
const rum = OtelRUM.init({
|
||||
serviceName: "checkout-web",
|
||||
environment: "development",
|
||||
devMode: accessState.loggedIn && accessState.diagnosticsAccepted,
|
||||
devModeHosts: ["localhost", "checkout.dev.example.com"]
|
||||
});
|
||||
```
|
||||
|
||||
Here `accessState` is provided by the host application's existing login and
|
||||
acceptance flow. Do not initialize development mode before that flow completes.
|
||||
Login alone does not establish consent or another legal basis. The SDK cannot
|
||||
verify the login, notice, acceptance or purpose; the application must do so.
|
||||
The allowlist is an accidental-production safeguard, not an authentication
|
||||
boundary. Wildcards/subdomains do not match automatically; an environment
|
||||
label never enables this mode. Ports are not part of the hostname match.
|
||||
|
||||
For declarative setup after the access gate, use `data-dev-mode="true"` and
|
||||
`data-dev-mode-hosts="localhost,checkout.dev.example.com"`. Explicit
|
||||
`devMode: false` overrides the attribute. Manual `captureMessage(message,
|
||||
"INFO")`, exceptions, context and breadcrumbs are available for development
|
||||
improvement logs. Do not include passwords, tokens or sensitive user content.
|
||||
|
||||
Development mode does not manufacture a consent grant: `getConsent()` still
|
||||
returns the actual configured choice. Call `rum.setConsent("denied")` on logout
|
||||
or withdrawal; it disables development mode even if consent was already denied,
|
||||
discards queued data and returns to minimal/counts. Re-enabling requires a new
|
||||
page initialization after the access gate. Todo remains consent-controlled
|
||||
separately, and is still disabled by default.
|
||||
|
||||
### Performance permission and withdrawal
|
||||
|
||||
Web Vitals is not loaded until consent is granted or allowed development mode
|
||||
is active. `captureWebVitals: false` disables it in all modes. If a separate
|
||||
assessment establishes permission for cookieless performance collection, set
|
||||
`captureWebVitalsWithoutConsent: true` or `data-vitals-without-consent="true"`.
|
||||
Explicit `false` overrides the attribute; this option is not a legal exemption.
|
||||
In that mode only values, deltas and numeric attribution are sent without
|
||||
consent. A random, in-memory page-view id deduplicates snapshots within one
|
||||
load; metric ids, selectors and other text attribution are omitted.
|
||||
|
||||
Detailed diagnostics use an in-memory-only session id, renewed on reload or
|
||||
re-grant. Every consent change discards queued records, breadcrumbs, context
|
||||
and pending vitals; data already sent cannot be recalled. Reapply identifying
|
||||
context only after permission. Withdrawal suppresses later vitals callbacks
|
||||
and reports unless cookieless performance was independently enabled. Observers
|
||||
already installed inside the vendored Web Vitals library cannot be unloaded by
|
||||
this SDK; reload the page after withdrawal if complete observer teardown is
|
||||
required. If consent is revoked before its download completes, no metric
|
||||
subscriptions are started.
|
||||
|
||||
Telemetry uses `fetch` with `credentials: "omit"`, `referrerPolicy: "no-referrer"`
|
||||
and `keepalive: true` in every mode; no cookie-bearing `sendBeacon` fallback is used. If fetch
|
||||
is unavailable or fails, telemetry is dropped. Browser-managed network headers
|
||||
and client IP addresses remain visible to the receiving infrastructure, so
|
||||
this is client-side data minimization, not a guarantee of legal anonymization
|
||||
or a determination that consent is unnecessary. Review server logging,
|
||||
retention, enrichment and applicable requirements separately.
|
||||
|
||||
### Building the browser scripts
|
||||
|
||||
The maintained TypeScript sources are `rum/src/otel-rum.ts` and
|
||||
`rum/src/otel-todo.ts`; public options and APIs are defined in
|
||||
`rum/src/types.ts`. Do not edit the generated `static/otel-rum.js` or
|
||||
`static/otel-todo.js` directly.
|
||||
|
||||
Run `make build-rum` to install locked dependencies, typecheck with strict
|
||||
TypeScript, and generate separate minified IIFE bundles at the existing
|
||||
`/static/otel-rum.js` and `/static/otel-todo.js` URLs. Todo stays separate so
|
||||
it is never downloaded unless enabled. Run `make test-rum` for the same build
|
||||
plus regression tests against the generated JavaScript. After installing
|
||||
dependencies, `npm --prefix rum test` also runs the build and tests.
|
||||
|
||||
`make build` builds the browser scripts and Go server. Docker builds and tests
|
||||
the browser scripts in an isolated Node stage automatically, so `build.sh`
|
||||
also uses fresh bundles. Generated JavaScript remains checked in for direct
|
||||
`go run ./cmd/server` development; rebuild it whenever TypeScript changes.
|
||||
|
||||
External source maps are generated locally but are not downloaded by browsers
|
||||
automatically. Docker omits them by default; `PUBLIC_SOURCEMAPS=true` retains
|
||||
them, as for the UI. TypeScript and esbuild are build-time dependencies only.
|
||||
|
||||
## React error boundaries
|
||||
|
||||
Rendering errors are not reliably caught by global `window.onerror` handlers.
|
||||
@@ -93,13 +272,27 @@ for individual views and the application root.
|
||||
|
||||
`otel-todo.js` adds a small floating symbol and a keyboard shortcut
|
||||
(default `Alt+Shift+T`) that opens a corner dialog for filing a todo/issue
|
||||
from the page, with an optional component picker. The current path and hash
|
||||
are posted with it.
|
||||
from the page, with an optional component picker. User-submitted text, selected
|
||||
components and explicitly picked element references are sent as requested;
|
||||
URLs, user agents and session metadata are attached only with consent. A note
|
||||
can itself contain personal information, so it is not guaranteed anonymous.
|
||||
|
||||
`otel-rum.js` loads it automatically (from the same directory as the RUM
|
||||
script) and reuses its service name, base URL and ingest key. Opt out with
|
||||
`OtelRUM.init({ excludeTodo: true })` or `data-exclude-todo="true"`. Or load it
|
||||
standalone:
|
||||
Todo is **disabled by default** in `otel-rum.js`. Opt in with
|
||||
`OtelRUM.init({ enableTodo: true })` or `data-enable-todo="true"` on the RUM
|
||||
script. It reuses the service name, base URL and ingest key, and loads from
|
||||
the same directory as the RUM script.
|
||||
|
||||
When enabled, both loading and initialization wait until after `window.load`,
|
||||
then run during `requestIdleCallback` with no forced timeout. If idle callbacks
|
||||
are unavailable, loading waits three seconds after `load`. On a continuously
|
||||
busy page, idle loading may be delayed indefinitely. Error capture and request
|
||||
instrumentation do not wait for Todo.
|
||||
|
||||
Explicit `enableTodo: false` overrides the opt-in attribute. The legacy
|
||||
`excludeTodo: true` and `data-exclude-todo="true"` remain supported and veto
|
||||
opt-in. Passing `excludeTodo: false` no longer enables Todo.
|
||||
|
||||
Alternatively, load it standalone (this bypasses RUM's idle scheduling):
|
||||
|
||||
```html
|
||||
<script src="https://otel.example.com/static/otel-todo.js"
|
||||
@@ -111,6 +304,12 @@ standalone:
|
||||
Components are also discovered from `[data-component="..."]` elements. Other
|
||||
options (on the RUM or todo script, or `OtelRUM.init({ todo: {...} })`): `data-shortcut`, `data-hide-button="true"`, `data-base-url`.
|
||||
|
||||
When loaded by RUM, Todo reads the current RUM consent at submission time,
|
||||
including after withdrawal. Standalone Todo defaults to denied consent; use
|
||||
`OtelTodo.instance.setConsent("granted" | "denied")` from the consent manager,
|
||||
or initialize with `consent` / `data-consent`. Submissions always omit browser
|
||||
credentials and referrers, and no browser storage is used.
|
||||
|
||||
- `POST /v1/todos` (ingest key, CORS) body `{project, text, component, path, hash, url, ua, session}`
|
||||
- `GET /api/todos?project=<service>&limit=100` (authenticated; omit `project` for all), newest first
|
||||
|
||||
|
||||
Reference in new issue
Block a user