update rum
Build and Publish / Metadata (push) Successful in 4s
Build and Publish / BuildAndPushAmd64 (push) Failing after 1m27s

This commit is contained in:
mats committed 2026-11-04 13:04:34 +01:00
1 parent 7b68f60202
commit 193e6c6e39
15 files changed
+2760 -1080

No files matched your search

+205 -6
View File
@@ -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