feat(sentry): add error monitoring, tracing, session replay, and structured logging
Integrates @sentry/sveltekit 10.49.0 via the modern SvelteKit path (instrumentation.server.ts + experimental.instrumentation.server). - instrumentation.server.ts: server-side init with tracing, logging, and console capture (log/warn/error forwarded to Sentry) - hooks.client.ts: client-side init with browserTracingIntegration, replayIntegration (maskAllText/blockAllMedia), consoleLoggingIntegration, and handleErrorWithSentry - hooks.server.ts: adds handleErrorWithSentry export, composes existing appHandle with sentryHandle() via sequence(), and sets isolation scope attributes (user_id, username, permission_level) per request for log/trace correlation - svelte.config.js: enables experimental.instrumentation.server and experimental.tracing.server - vite.config.ts: adds sentrySvelteKit() plugin for source map uploads - .env.example: documents VITE_SENTRY_DSN, SENTRY_DSN, SENTRY_ORG, SENTRY_PROJECT, SENTRY_AUTH_TOKEN Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -3,3 +3,12 @@
|
||||
# No API_BASE_URL override needed.
|
||||
PUBLIC_TURNSTILE_SITE_KEY=0x4AAAAAACI5pu-lNWFc6Wu1
|
||||
TURNSTILE_SECRET_KEY=
|
||||
|
||||
# Sentry — error monitoring, tracing, session replay, logging
|
||||
# VITE_SENTRY_DSN is public (bundled into the browser build)
|
||||
VITE_SENTRY_DSN=
|
||||
SENTRY_DSN=
|
||||
SENTRY_ORG=
|
||||
SENTRY_PROJECT=
|
||||
# SENTRY_AUTH_TOKEN is secret; never commit it — use CI secrets or .env.local
|
||||
SENTRY_AUTH_TOKEN=
|
||||
|
||||
@@ -56,6 +56,7 @@
|
||||
"dependencies": {
|
||||
"@hey-api/client-fetch": "^0.13.1",
|
||||
"@lucide/svelte": "^1.8.0",
|
||||
"@sentry/sveltekit": "^10.49.0",
|
||||
"@zxing/browser": "^0.1.5",
|
||||
"bits-ui": "^2.17.3",
|
||||
"daisyui": "^5.5.19",
|
||||
|
||||
1418
pnpm-lock.yaml
generated
1418
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
18
src/hooks.client.ts
Normal file
18
src/hooks.client.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import * as Sentry from '@sentry/sveltekit';
|
||||
|
||||
Sentry.init({
|
||||
dsn: import.meta.env.VITE_SENTRY_DSN,
|
||||
environment: import.meta.env.MODE,
|
||||
sendDefaultPii: true,
|
||||
tracesSampleRate: 1.0,
|
||||
integrations: [
|
||||
Sentry.browserTracingIntegration(),
|
||||
Sentry.replayIntegration({ maskAllText: true, blockAllMedia: true }),
|
||||
Sentry.consoleLoggingIntegration({ levels: ['log', 'warn', 'error'] })
|
||||
],
|
||||
replaysSessionSampleRate: 0.1,
|
||||
replaysOnErrorSampleRate: 1.0,
|
||||
enableLogs: true
|
||||
});
|
||||
|
||||
export const handleError = Sentry.handleErrorWithSentry();
|
||||
@@ -1,8 +1,12 @@
|
||||
import * as Sentry from '@sentry/sveltekit';
|
||||
import type { Handle } from '@sveltejs/kit';
|
||||
import { sequence } from '@sveltejs/kit/hooks';
|
||||
import { getUserInfo } from '$lib/api';
|
||||
import { createApiClient } from '$lib/server/api';
|
||||
|
||||
export const handle: Handle = async ({ event, resolve }) => {
|
||||
export const handleError = Sentry.handleErrorWithSentry();
|
||||
|
||||
const appHandle: Handle = async ({ event, resolve }) => {
|
||||
event.locals.accessToken = event.cookies.get('access_token') ?? null;
|
||||
event.locals.user = null;
|
||||
|
||||
@@ -18,6 +22,14 @@ export const handle: Handle = async ({ event, resolve }) => {
|
||||
}
|
||||
}
|
||||
|
||||
if (event.locals.user) {
|
||||
Sentry.getIsolationScope().setAttributes({
|
||||
'user.id': event.locals.user.user_id,
|
||||
'user.username': event.locals.user.username,
|
||||
'user.permission_level': event.locals.user.permission_level
|
||||
});
|
||||
}
|
||||
|
||||
const theme = (event.cookies.get('theme') as 'dark' | 'light') ?? 'dark';
|
||||
return resolve(event, {
|
||||
transformPageChunk({ html }) {
|
||||
@@ -25,3 +37,5 @@ export const handle: Handle = async ({ event, resolve }) => {
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const handle = sequence(Sentry.sentryHandle(), appHandle);
|
||||
|
||||
10
src/instrumentation.server.ts
Normal file
10
src/instrumentation.server.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import * as Sentry from '@sentry/sveltekit';
|
||||
|
||||
Sentry.init({
|
||||
dsn: process.env.SENTRY_DSN,
|
||||
environment: process.env.NODE_ENV ?? 'production',
|
||||
sendDefaultPii: true,
|
||||
tracesSampleRate: 1.0,
|
||||
enableLogs: true,
|
||||
integrations: [Sentry.consoleLoggingIntegration({ levels: ['log', 'warn', 'error'] })]
|
||||
});
|
||||
@@ -10,7 +10,11 @@ const config = {
|
||||
},
|
||||
kit: {
|
||||
adapter: adapter(),
|
||||
paths: { base: '/app' }
|
||||
paths: { base: '/app' },
|
||||
experimental: {
|
||||
instrumentation: { server: true },
|
||||
tracing: { server: true }
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -1,12 +1,21 @@
|
||||
import tailwindcss from '@tailwindcss/vite';
|
||||
import { sveltekit } from '@sveltejs/kit/vite';
|
||||
import { sentrySvelteKit } from '@sentry/sveltekit';
|
||||
import { defineConfig } from 'vite';
|
||||
|
||||
const isTest = process.env.NODE_ENV === 'test';
|
||||
const apiTarget = isTest ? 'http://localhost:4010' : 'http://10.0.0.10:8000';
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [tailwindcss(), sveltekit()],
|
||||
plugins: [
|
||||
tailwindcss(),
|
||||
sentrySvelteKit({
|
||||
org: process.env.SENTRY_ORG,
|
||||
project: process.env.SENTRY_PROJECT,
|
||||
authToken: process.env.SENTRY_AUTH_TOKEN
|
||||
}),
|
||||
sveltekit()
|
||||
],
|
||||
server: {
|
||||
proxy: {
|
||||
// In test mode the mock server is the "API root" — strip the
|
||||
|
||||
Reference in New Issue
Block a user