mirror of
https://github.com/Sea-Haven-Industries/shoc-frontend-new.git
synced 2026-09-30 04:33:11 +00:00
feat: add browser Sentry tracing
This commit is contained in:
parent
c70587941c
commit
c1863c4274
8 changed files with 206 additions and 1 deletions
|
|
@ -8,5 +8,9 @@
|
|||
# Production: absolute URLs MUST include the /api path segment
|
||||
VITE_API_URL=https://api.seahavenindustries.com/api
|
||||
|
||||
# Sentry browser DSN. This value is public configuration and is baked into the bundle.
|
||||
# Leave blank to disable telemetry for that build.
|
||||
VITE_SENTRY_DSN=
|
||||
|
||||
# Development proxy target (used by vite.config.ts)
|
||||
#VITE_API_TARGET=http://localhost:5141
|
||||
|
|
|
|||
|
|
@ -2,3 +2,6 @@
|
|||
# NOTE: baked into the build at `vite build`, so this is the DEV value. Staging
|
||||
# and prod builds must override VITE_API_URL per environment (api.staging..., etc.).
|
||||
VITE_API_URL=https://api.dev.seahaven.com/api
|
||||
|
||||
# Public browser configuration; deployment must provide the environment's Sentry DSN.
|
||||
VITE_SENTRY_DSN=
|
||||
|
|
|
|||
56
docs/adr/0002-sentry-observability.md
Normal file
56
docs/adr/0002-sentry-observability.md
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
# 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.
|
||||
|
||||
## 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.
|
||||
|
||||
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. A missing or blank DSN leaves telemetry inactive without preventing
|
||||
the application from starting. Default personally identifiable information
|
||||
collection remains disabled.
|
||||
|
||||
Source-map upload is outside this change. It requires a Sentry organization,
|
||||
project, and scoped upload token that are not currently available, plus an
|
||||
explicit deployment design for handling that credential without exposing it in
|
||||
the browser bundle.
|
||||
|
||||
### 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.
|
||||
- **Add the Sentry Vite plugin now** — rejected until the organization, project,
|
||||
upload credential, and deployment ownership are defined.
|
||||
|
||||
## Consequences
|
||||
|
||||
- Browser navigations and API requests are recorded as connected transactions
|
||||
when the deployment supplies a valid DSN.
|
||||
- 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.
|
||||
- Production stack traces remain minified until source-map upload is designed.
|
||||
- Rollback is isolated: remove `@sentry/react`, the initialization module and
|
||||
import, restore the unwrapped router factory, and remove the Sentry env entries.
|
||||
107
package-lock.json
generated
107
package-lock.json
generated
|
|
@ -21,6 +21,7 @@
|
|||
"@hookform/resolvers": "^5.4.0",
|
||||
"@mui/icons-material": "^9.2.0",
|
||||
"@mui/material": "^9.2.0",
|
||||
"@sentry/react": "10.73.0",
|
||||
"@tanstack/query-broadcast-client-experimental": "5.101.2",
|
||||
"@tanstack/react-query": "5.101.2",
|
||||
"@tanstack/react-query-devtools": "5.101.2",
|
||||
|
|
@ -1974,6 +1975,112 @@
|
|||
"integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@sentry/browser": {
|
||||
"version": "10.73.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-10.73.0.tgz",
|
||||
"integrity": "sha512-HqTe1S5RrWLufhX2LaFP3yNoMxfNDroh120bq1zdGHZfFDBMJQ0CDXxHO+L4UJfQ5dWdCCzWbXIAiZuWGa/DFQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@sentry/browser-utils": "10.73.0",
|
||||
"@sentry/conventions": "^0.16.0",
|
||||
"@sentry/core": "10.73.0",
|
||||
"@sentry/feedback": "10.73.0",
|
||||
"@sentry/replay": "10.73.0",
|
||||
"@sentry/replay-canvas": "10.73.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@sentry/browser-utils": {
|
||||
"version": "10.73.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/browser-utils/-/browser-utils-10.73.0.tgz",
|
||||
"integrity": "sha512-qQygxJZ+RV779+iL1+lrJ4f4sZLgbgW0/JWPNp0YlcEAE62yCsdKbqoTEjB/EugdS4mSjBMX0chZC6rblu2Ycw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@sentry/conventions": "^0.16.0",
|
||||
"@sentry/core": "10.73.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@sentry/conventions": {
|
||||
"version": "0.16.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/conventions/-/conventions-0.16.0.tgz",
|
||||
"integrity": "sha512-fO9PLmHdVURcSPUpWCItWAtgKiMwGdJHbovoSEyLplX5sxs2ugvI4CBPTrkkgqhObnZOD0CnWBKDzSVQYBKEyQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
}
|
||||
},
|
||||
"node_modules/@sentry/core": {
|
||||
"version": "10.73.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-10.73.0.tgz",
|
||||
"integrity": "sha512-FLO1UgH19RyasVpofu612WCOgb2nEH0dZy+R72d7p65XU9i0wxlMKm3+sgfwKmiSJp1Qhilaaxs4Jg6BbiM5HA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@sentry/conventions": "^0.16.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@sentry/feedback": {
|
||||
"version": "10.73.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/feedback/-/feedback-10.73.0.tgz",
|
||||
"integrity": "sha512-D6nSngX+e46Mae2/oh2bxBvxNK1z2NERbuMAhB5sx9x4xMBWIyGnYYTECehvEqV9+AqGAgxxhOZoYIG3AmRwww==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@sentry/core": "10.73.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@sentry/react": {
|
||||
"version": "10.73.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/react/-/react-10.73.0.tgz",
|
||||
"integrity": "sha512-wJrzS98ddPvhGS/MKNHZyE8X7ecd4KwKdz7fHino2qkqBBrF4cxWr+q/uLTIk5PYWMkeJ4oKMjHAjOqmzGR4tg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@sentry/browser": "10.73.0",
|
||||
"@sentry/conventions": "^0.16.0",
|
||||
"@sentry/core": "10.73.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.14.0 || 17.x || 18.x || 19.x"
|
||||
}
|
||||
},
|
||||
"node_modules/@sentry/replay": {
|
||||
"version": "10.73.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/replay/-/replay-10.73.0.tgz",
|
||||
"integrity": "sha512-nN2wjN/Y0J5BOJV5hqRHUEBfxwUsipp1PKjcDHh6Fpxnrtfldu3Y99E8cQInseo5heFdzEvrOHBBrqWZXOHVKQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@sentry/browser-utils": "10.73.0",
|
||||
"@sentry/core": "10.73.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@sentry/replay-canvas": {
|
||||
"version": "10.73.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/replay-canvas/-/replay-canvas-10.73.0.tgz",
|
||||
"integrity": "sha512-sxa2lKkHPfF/j5xFpW7gocthWXRqyoHz8KCPy6yGc8plT477nl57iGSKhQDYsx1Ny10TLjs7YkIuPP1GY/Ax2Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@sentry/core": "10.73.0",
|
||||
"@sentry/replay": "10.73.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@simple-libs/child-process-utils": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@simple-libs/child-process-utils/-/child-process-utils-2.0.0.tgz",
|
||||
|
|
|
|||
|
|
@ -55,6 +55,7 @@
|
|||
"@hookform/resolvers": "^5.4.0",
|
||||
"@mui/icons-material": "^9.2.0",
|
||||
"@mui/material": "^9.2.0",
|
||||
"@sentry/react": "10.73.0",
|
||||
"@tanstack/query-broadcast-client-experimental": "5.101.2",
|
||||
"@tanstack/react-query": "5.101.2",
|
||||
"@tanstack/react-query-devtools": "5.101.2",
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import "@/observability/sentry";
|
||||
|
||||
import React from "react";
|
||||
import ReactDOM from "react-dom/client";
|
||||
import { AppRoot } from "@/app-root";
|
||||
|
|
|
|||
30
src/observability/sentry.ts
Normal file
30
src/observability/sentry.ts
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
import * as Sentry from "@sentry/react";
|
||||
import { useEffect } from "react";
|
||||
import {
|
||||
createRoutesFromChildren,
|
||||
matchRoutes,
|
||||
useLocation,
|
||||
useNavigationType,
|
||||
} from "react-router";
|
||||
|
||||
import { env } from "@/lib/env";
|
||||
|
||||
const sentryDsn = import.meta.env.VITE_SENTRY_DSN?.trim() || undefined;
|
||||
const apiBaseUrl = new URL(env.apiUrl, window.location.origin).toString();
|
||||
|
||||
Sentry.init({
|
||||
dsn: sentryDsn,
|
||||
environment: import.meta.env.MODE,
|
||||
integrations: [
|
||||
Sentry.reactRouterBrowserTracingIntegration({
|
||||
useEffect,
|
||||
useLocation,
|
||||
useNavigationType,
|
||||
createRoutesFromChildren,
|
||||
matchRoutes,
|
||||
}),
|
||||
],
|
||||
sendDefaultPii: false,
|
||||
tracesSampleRate: 1.0,
|
||||
tracePropagationTargets: [window.location.origin, apiBaseUrl],
|
||||
});
|
||||
|
|
@ -1,8 +1,10 @@
|
|||
import { createBrowserRouter } from "react-router";
|
||||
import * as Sentry from "@sentry/react";
|
||||
import { createBrowserRouter as createBrowserRouterBase } from "react-router";
|
||||
import { RouterProvider } from "react-router/dom";
|
||||
|
||||
import { routes } from "@/routing/build-route-tree";
|
||||
|
||||
const createBrowserRouter = Sentry.wrapCreateBrowserRouter(createBrowserRouterBase);
|
||||
const router = createBrowserRouter(routes);
|
||||
|
||||
export function Routes() {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue