228 lines
8.4 KiB
Markdown
228 lines
8.4 KiB
Markdown
# Instrumentation guide
|
|
|
|
This document describes the recommended instrumentation contract for this
|
|
receiver. It is available at `/instrumentation.md` so frontend and backend
|
|
agents can read it before integrating telemetry.
|
|
|
|
## Shared identity
|
|
|
|
Use the same values across all signals from one deployed application:
|
|
|
|
- `service.name`: stable application name, for example `checkout-web`.
|
|
- `service.release`: immutable deploy identifier, for example
|
|
`2026.09.18-a1b2c3`.
|
|
- `service.environment`: `production`, `staging`, or `development`.
|
|
|
|
The release must exactly match the source-map upload release.
|
|
|
|
## Browser frontend
|
|
|
|
Load the RUM script and initialize it once, before the application starts:
|
|
|
|
```html
|
|
<script src="https://otel.example.com/static/otel-rum.js"></script>
|
|
<script>
|
|
const rum = OtelRUM.init({
|
|
serviceName: "checkout-web",
|
|
release: "2026.09.18-a1b2c3",
|
|
environment: "production",
|
|
baseUrl: "https://otel.example.com"
|
|
});
|
|
</script>
|
|
```
|
|
|
|
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:
|
|
|
|
```js
|
|
fetch("/api/orders", { headers: rum.traceHeaders() });
|
|
```
|
|
|
|
For manually handled errors:
|
|
|
|
```js
|
|
try {
|
|
riskyOperation();
|
|
} catch (error) {
|
|
rum.captureException(error, { "feature.name": "checkout" });
|
|
}
|
|
```
|
|
|
|
Do not put secrets, tokens, or sensitive customer data in breadcrumbs or
|
|
custom attributes.
|
|
|
|
## React error boundaries
|
|
|
|
Rendering errors are not reliably caught by global `window.onerror` handlers.
|
|
Report them from an error boundary and render a recovery screen:
|
|
|
|
```tsx
|
|
import React from "react";
|
|
|
|
export class AppErrorBoundary extends React.Component<
|
|
{ children: React.ReactNode },
|
|
{ error: Error | null }
|
|
> {
|
|
state = { error: null as Error | null };
|
|
|
|
static getDerivedStateFromError(error: Error) {
|
|
return { error };
|
|
}
|
|
|
|
componentDidCatch(error: Error, info: React.ErrorInfo) {
|
|
window.OtelRUM?.last?.captureException(error, {
|
|
"exception.type": "ReactErrorBoundary",
|
|
"react.component_stack": info.componentStack,
|
|
});
|
|
}
|
|
|
|
render() {
|
|
if (this.state.error) {
|
|
return <button onClick={() => this.setState({ error: null })}>Retry</button>;
|
|
}
|
|
return this.props.children;
|
|
}
|
|
}
|
|
```
|
|
|
|
Use the same pattern in your application, including a user-visible fallback
|
|
for individual views and the application root.
|
|
|
|
## Backend instrumentation
|
|
|
|
Instrument backend services with an OpenTelemetry SDK or auto-instrumentation
|
|
and export OTLP to this receiver:
|
|
|
|
- OTLP/gRPC: `otel.example.com:4317`
|
|
- OTLP/HTTP traces: `https://otel.example.com/v1/traces`
|
|
- OTLP/HTTP metrics: `https://otel.example.com/v1/metrics`
|
|
- OTLP/HTTP logs: `https://otel.example.com/v1/logs`
|
|
|
|
Set the resource attributes on every provider/exporter:
|
|
|
|
```text
|
|
service.name=checkout-api
|
|
service.release=2026.09.18-a1b2c3
|
|
service.environment=production
|
|
```
|
|
|
|
Create spans around inbound requests, database calls, external HTTP calls,
|
|
queue operations, and important business operations. Record HTTP status,
|
|
route, method, and exception details. Mark failed spans with an error status.
|
|
The receiver derives backend issues from error spans and ERROR/FATAL logs.
|
|
|
|
If HTTP ingest authentication is enabled, provide `OTEL_INGEST_KEY` as the
|
|
`X-Otel-Ingest-Key` header or `key` query parameter. Keep that key server-side;
|
|
browser clients should not receive it.
|
|
|
|
For non-interactive agents that need to query protected debug endpoints, use a
|
|
separate `OTEL_AGENT_TOKEN` service token. Send it as
|
|
`X-Otel-Agent-Token: $OTEL_AGENT_TOKEN`; do not put it in browser code or
|
|
telemetry payloads.
|
|
|
|
## Source maps
|
|
|
|
Build the frontend with the same immutable release used by RUM. The command
|
|
and output directory depend on your build tool; the example below assumes a
|
|
standard `dist/` output:
|
|
|
|
```sh
|
|
export RELEASE="2026.09.18-a1b2c3"
|
|
VITE_APP_RELEASE="$RELEASE" npm run build
|
|
```
|
|
|
|
After deployment, upload the contents of each generated JavaScript map to the
|
|
public multipart endpoint. No repository checkout or upload script is needed.
|
|
`source` must match the generated bundle URL/path captured in the browser
|
|
stack, while `file=@...` reads the local source-map file contents:
|
|
|
|
```sh
|
|
curl --fail-with-body --silent --show-error \
|
|
-H "X-Sourcemap-Key: $OTEL_SOURCEMAP_KEY" \
|
|
-X POST "https://otel.example.com/api/sourcemaps" \
|
|
-F "release=$RELEASE" \
|
|
-F "source=/assets/app-HASH.js" \
|
|
-F "file=@dist/assets/app-HASH.js.map;type=application/json"
|
|
```
|
|
|
|
For example, if the deployed JavaScript is available at
|
|
`https://app.example.com/assets/main-abc123.js`, upload its matching map with
|
|
`source=/assets/main-abc123.js`. The source-map filename on the build machine
|
|
does not need to match the URL, but the `source` field must.
|
|
|
|
Maps are parsed, written to `OTEL_SOURCEMAP_DIR`, and reloaded on receiver
|
|
start. Issue responses include a resolved stack when `service.release` and the
|
|
generated bundle path match. Uploaded maps are never returned, but when a map
|
|
embeds `sourcesContent` the RUM "JS errors" view shows a few lines of original
|
|
source around each mapped frame to signed-in users.
|
|
|
|
### Requirements checklist
|
|
|
|
A stack only resolves when every one of these holds. When it does not, check
|
|
them in this order:
|
|
|
|
1. **The build emits maps, with sources embedded.** Enable source maps in the
|
|
bundler (Vite `build.sourcemap: true`, webpack `devtool: "source-map"`).
|
|
Keep `sourcesContent` (the default) or the code view has nothing to show.
|
|
2. **The browser reports a release.** Pass the release to `OtelRUM.init({ release })`
|
|
(or `data-release` on the script tag), injected at build time. An event
|
|
with no release, or `dev`, can never match an upload.
|
|
3. **The uploaded release is byte-for-byte the same string.** Use one value for
|
|
both the bundle build and the upload (a git short SHA works).
|
|
4. **`source` matches the path in the browser stack.** Everything before
|
|
`/assets/` and any `?query` is ignored, so `source=/assets/main-abc123.js`
|
|
matches `https://app.example.com/assets/main-abc123.js`. Content-hashed
|
|
names must match exactly, and chunks each need their own upload.
|
|
5. **The map belongs to the deployed bundle.** Upload the maps from the very
|
|
build that produced the deployed files. A second local rebuild can differ
|
|
(dependencies, Node version) and silently mismatch. In Docker, copy the maps
|
|
out of the build stage (`docker build --target <stage>`, then `docker cp`)
|
|
instead of rebuilding. `build.sh` does this.
|
|
6. **Every chunk is uploaded.** Loop over all `*.js.map` files and fail the
|
|
deploy when none are found, since an empty loop otherwise succeeds silently.
|
|
7. **The receiver keeps its maps.** `OTEL_SOURCEMAP_DIR` must be on persistent
|
|
storage (a PVC in Kubernetes, not `emptyDir`). Maps written to an ephemeral
|
|
directory vanish on restart.
|
|
8. **Maps are not published with the site.** Delete `*.map` from the served
|
|
image unless public maps are intended (see below).
|
|
|
|
### Verifying a deploy
|
|
|
|
List what the receiver has loaded (requires a signed-in session or token):
|
|
|
|
```sh
|
|
curl -H "Authorization: Bearer $TOKEN" https://otel.example.com/api/sourcemaps
|
|
# [{"release":"2026.09.18-a1b2c3","source":"/assets/main-abc123.js"}, ...]
|
|
```
|
|
|
|
Then trigger a real uncaught error from application code, for example a
|
|
button that runs `setTimeout(() => { throw new Error("source-map check") })`
|
|
(this repository's RUM "JS errors" view has a *Generate test error* button),
|
|
and confirm the card shows **Source-mapped** with the original file and line.
|
|
|
|
Frame status in the stack explorer explains a miss: `no-release` (event has no
|
|
release), `no-map` (nothing uploaded for that release and file), or `unmapped`
|
|
(a map exists but has no mapping for that position).
|
|
|
|
The Docker build removes `.map` files from production images by default. For
|
|
development or staging, public maps can be enabled for Chrome DevTools:
|
|
|
|
```sh
|
|
PUBLIC_SOURCEMAPS=true ./build.sh
|
|
```
|
|
|
|
Public maps expose application source code and should not be enabled in
|
|
production unless that is intentional.
|
|
|
|
## Verification checklist
|
|
|
|
1. Trigger a controlled frontend exception.
|
|
2. Trigger a controlled backend error span or ERROR log.
|
|
3. Confirm both appear under Issues as separate frontend/backend issues.
|
|
4. Confirm the issue sample contains the expected release.
|
|
5. Confirm a mapped frontend stack shows original file and line information
|
|
(see *Requirements checklist* under Source maps if it does not).
|
|
6. Confirm trace IDs connect frontend requests to backend traces where headers
|
|
were propagated.
|