shoc-frontend-new/docs/adr/0002-sentry-observability.md
2026-09-03 17:12:49 -03:00

94 lines
5.2 KiB
Markdown

# 0002. Browser error and transaction telemetry with Sentry
## Status
Accepted
## Context
The SeaHaven admin SPA has no centralized browser error reporting or distributed
transaction tracing. Operational failures therefore have to be reconstructed
from user reports and isolated backend logs. We need browser navigation and API
request transactions to join the backend trace while preserving the existing UI,
routing, authentication, and server-state behavior. Every emitted transaction
must also identify its exact build, trace, transaction, operation, function, and
opaque actor without sending credentials, request bodies, query values, or
personal data.
## Decision
Use the pinned `@sentry/react` 10.73.0 SDK. Initialize it before the application
router is created, and wrap `createBrowserRouter` with Sentry's current React
Router compatibility API. Configure the browser tracing integration with a 100%
transaction sample rate, and propagate trace headers only to the SPA origin and
the configured `VITE_API_URL` base. Pass `Sentry.reactErrorHandler()` as the
`onCaughtError`, `onUncaughtError`, and `onRecoverableError` callbacks of
`ReactDOM.createRoot`, so React 19's root error hooks capture caught, uncaught,
and recoverable component errors as Sentry events.
All shared API helpers, direct Ky calls, vendor-link `fetch` calls, and vendor
document `XMLHttpRequest` calls run inside explicit `http.client` transactions.
They use normalized route templates and attach `code.function`, `operation.type`,
HTTP method, `trace_id`, and `transaction_id`. Browser navigation transactions
receive the same searchable trace identifiers in `beforeSendTransaction`.
Authenticated sessions set only the application's opaque numeric/string user ID;
logout and session expiry clear it.
The DSN comes from `VITE_SENTRY_DSN`. It is intentionally treated as public
browser configuration because every `VITE_` value is embedded in the built
JavaScript. `.env.production` commits the public DSN of the real
`shoc-frontend` Sentry project, so dev and staging deploys are live; a missing
or blank DSN in any other build leaves telemetry inactive without preventing
the application from starting. Default personally identifiable information
collection remains disabled.
The Sentry environment label comes from `VITE_SENTRY_ENVIRONMENT` when set and
non-blank, falling back to the Vite build mode otherwise. The committed
`.env.production` labels dev deploys `development`, the staging deploy job
overrides the label to `staging`, and a future production pipeline can set
`production` without code changes.
Every deployment embeds `shoc-frontend@<40-character commit SHA>` as the Sentry
release and exposes the same SHA as the `app.commit` tag. CI fails if it cannot
resolve that identity. Vite creates hidden source maps, and the dev/staging
deployment workflows upload them to the private `shoc-frontend` Sentry project
using the repository's `SENTRY_AUTH_TOKEN` secret. The public S3 sync explicitly
excludes `*.map`; the upload credential is never available to the browser build.
Before-send processors reduce request data to normalized URL plus HTTP method,
reduce users to opaque ID only, attach native trace/transaction identifiers as
searchable tags, and reduce breadcrumbs to safe network method/status/normalized
URL metadata. Root and child span attributes use the same allowlist, retaining
function, operation, actor type, normalized route, method/status, service host,
timing, and native IDs while removing raw URLs and query values. Bodies, headers,
cookies, query strings/values, free-form breadcrumb messages, usernames, email
addresses, IP addresses, tokens, and filenames are not intentionally sent.
`sendDefaultPii` remains disabled.
### Alternatives considered
- **Manual error and timing calls** — rejected because they would miss route
transitions and distributed request context, and would require every feature
to maintain its own instrumentation.
- **Propagate tracing headers to every request** — rejected because third-party
requests must not receive SeaHaven trace metadata and broad propagation can
introduce cross-origin request failures.
- **Publish source maps with the SPA** — rejected because source code belongs in
Sentry's private artifact store, not the public application bucket.
## Consequences
- Browser navigations and API requests are recorded as connected transactions
when the deployment supplies a valid DSN.
- Events are labeled by `VITE_SENTRY_ENVIRONMENT` (`development` on dev
deploys, `staging` on the staging job), falling back to the Vite build mode
when unset.
- Every transaction is sampled. This meets the current requirement but increases
event volume and should be reviewed against Sentry quota and retention after
real traffic is measured.
- Cross-origin API tracing depends on the backend accepting `sentry-trace` and
`baggage` request headers; the current API CORS policy allows request headers.
- Deployed stack traces resolve against the exact commit's private source maps
once the scoped upload secret is present and the deployment workflow runs.
- Rollback is isolated: remove `@sentry/react`, the initialization module and
import, restore the unwrapped router factory, and remove the Sentry env entries.