Merge pull request 'feat(observability): browser Sentry SDK (gated on VITE_SENTRY_DSN)' (#68) from feat/sentry-browser into dev
Reviewed-on: #68
This commit was merged in pull request #68.
This commit is contained in:
@@ -22,6 +22,7 @@
|
|||||||
"@remotion/player": "^4.0.422",
|
"@remotion/player": "^4.0.422",
|
||||||
"@sase/shared": "workspace:*",
|
"@sase/shared": "workspace:*",
|
||||||
"@sase/ui": "workspace:*",
|
"@sase/ui": "workspace:*",
|
||||||
|
"@sentry/react": "^9.0.0",
|
||||||
"@tanstack/react-query": "^5.62.0",
|
"@tanstack/react-query": "^5.62.0",
|
||||||
"@tanstack/react-router": "^1.120.0",
|
"@tanstack/react-router": "^1.120.0",
|
||||||
"better-auth": "^1.2.0",
|
"better-auth": "^1.2.0",
|
||||||
|
|||||||
57
apps/web/src/lib/sentry.ts
Normal file
57
apps/web/src/lib/sentry.ts
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
/**
|
||||||
|
* Sentry browser SDK init — gated on VITE_SENTRY_DSN.
|
||||||
|
*
|
||||||
|
* Co-exists with Faro (Grafana) and PostHog: Sentry handles uncaught JS errors
|
||||||
|
* and on-error session replay; Faro handles RUM/traces; PostHog handles product
|
||||||
|
* analytics. No conflict between the three at the global error-handler layer.
|
||||||
|
*
|
||||||
|
* KVKK note: `sendDefaultPii: false` and `maskAllText: true` on replay.
|
||||||
|
*/
|
||||||
|
import type { init as SentryInit } from "@sentry/react";
|
||||||
|
|
||||||
|
let initialized = false;
|
||||||
|
|
||||||
|
export async function initSentry() {
|
||||||
|
if (initialized) return;
|
||||||
|
const dsn = import.meta.env.VITE_SENTRY_DSN;
|
||||||
|
if (!dsn) {
|
||||||
|
if (import.meta.env.DEV) {
|
||||||
|
console.info("[sentry] VITE_SENTRY_DSN unset — skipping init");
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const environment =
|
||||||
|
import.meta.env.VITE_SENTRY_ENVIRONMENT ?? import.meta.env.MODE ?? "production";
|
||||||
|
const release = import.meta.env.VITE_SENTRY_RELEASE;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const Sentry = await import("@sentry/react");
|
||||||
|
(Sentry.init as typeof SentryInit)({
|
||||||
|
dsn,
|
||||||
|
environment,
|
||||||
|
release,
|
||||||
|
integrations: [
|
||||||
|
Sentry.browserTracingIntegration(),
|
||||||
|
Sentry.replayIntegration({ maskAllText: true, blockAllMedia: true }),
|
||||||
|
],
|
||||||
|
// Performance: 10% trace sample rate (low volume site, can raise later).
|
||||||
|
tracesSampleRate: 0.1,
|
||||||
|
// Replay: only when an error fires; no session-replay otherwise (cost + privacy).
|
||||||
|
replaysSessionSampleRate: 0.0,
|
||||||
|
replaysOnErrorSampleRate: 1.0,
|
||||||
|
// KVKK: do not auto-attach IP / cookies / user agent fingerprints.
|
||||||
|
sendDefaultPii: false,
|
||||||
|
// Drop framework noise that isn't actionable.
|
||||||
|
ignoreErrors: [
|
||||||
|
"ResizeObserver loop limit exceeded",
|
||||||
|
"ResizeObserver loop completed with undelivered notifications.",
|
||||||
|
"Non-Error promise rejection captured",
|
||||||
|
],
|
||||||
|
});
|
||||||
|
initialized = true;
|
||||||
|
console.log("[sentry] browser SDK initialized");
|
||||||
|
} catch (err) {
|
||||||
|
console.warn("[sentry] init failed:", (err as Error).message);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,9 +7,13 @@ import { initFaro } from "./lib/faro";
|
|||||||
import { initLocale } from "./lib/i18n";
|
import { initLocale } from "./lib/i18n";
|
||||||
import { initMetaPixel } from "./lib/meta-pixel";
|
import { initMetaPixel } from "./lib/meta-pixel";
|
||||||
import { initPostHog } from "./lib/posthog";
|
import { initPostHog } from "./lib/posthog";
|
||||||
|
import { initSentry } from "./lib/sentry";
|
||||||
import { routeTree } from "./routeTree.gen";
|
import { routeTree } from "./routeTree.gen";
|
||||||
import "./globals.css";
|
import "./globals.css";
|
||||||
|
|
||||||
|
// Sentry first so it can capture errors thrown by other init code.
|
||||||
|
initSentry();
|
||||||
|
|
||||||
// Read persisted locale from localStorage before first render — without this
|
// Read persisted locale from localStorage before first render — without this
|
||||||
// a reload always falls back to the TR default even if the user picked EN.
|
// a reload always falls back to the TR default even if the user picked EN.
|
||||||
initLocale();
|
initLocale();
|
||||||
|
|||||||
@@ -30,6 +30,9 @@ export default defineConfig({
|
|||||||
},
|
},
|
||||||
build: {
|
build: {
|
||||||
outDir: "dist",
|
outDir: "dist",
|
||||||
sourcemap: false,
|
// 'hidden' emits source maps but strips the //# sourceMappingURL comment
|
||||||
|
// from JS bundles. Sentry can still consume them (via release upload), but
|
||||||
|
// browsers won't fetch them, so end-user stack traces stay minified.
|
||||||
|
sourcemap: "hidden",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -10,6 +10,11 @@ services:
|
|||||||
- VITE_META_PIXEL_ID=${VITE_META_PIXEL_ID:-}
|
- VITE_META_PIXEL_ID=${VITE_META_PIXEL_ID:-}
|
||||||
- VITE_CHATWOOT_BASE_URL=${VITE_CHATWOOT_BASE_URL:-}
|
- VITE_CHATWOOT_BASE_URL=${VITE_CHATWOOT_BASE_URL:-}
|
||||||
- VITE_CHATWOOT_WEBSITE_TOKEN=${VITE_CHATWOOT_WEBSITE_TOKEN:-}
|
- VITE_CHATWOOT_WEBSITE_TOKEN=${VITE_CHATWOOT_WEBSITE_TOKEN:-}
|
||||||
|
# Sentry browser SDK — gated on DSN presence (unset = skip init).
|
||||||
|
# VITE_* args must be build-time; runtime env won't reach the bundle.
|
||||||
|
- VITE_SENTRY_DSN=${VITE_SENTRY_DSN:-}
|
||||||
|
- VITE_SENTRY_ENVIRONMENT=${VITE_SENTRY_ENVIRONMENT:-production}
|
||||||
|
- VITE_SENTRY_RELEASE=${VITE_SENTRY_RELEASE:-}
|
||||||
environment:
|
environment:
|
||||||
- NODE_ENV=production
|
- NODE_ENV=production
|
||||||
- PORT=4000
|
- PORT=4000
|
||||||
|
|||||||
Reference in New Issue
Block a user