feat: connect sentry to dash and api
This commit is contained in:
@@ -40,7 +40,12 @@ CLOUDFLARE_ACCESS_KEY=""
|
|||||||
CLOUDFLARE_ACCOUNT_ID=""
|
CLOUDFLARE_ACCOUNT_ID=""
|
||||||
CLOUDFLARE_BUCKET_DOMAIN=""
|
CLOUDFLARE_BUCKET_DOMAIN=""
|
||||||
CLOUDFLARE_SECRET_ACCESS_KEY=""
|
CLOUDFLARE_SECRET_ACCESS_KEY=""
|
||||||
SENTRY_DNS=""
|
|
||||||
|
# Sentry (server-side error tracking). Leave SENTRY_DSN unset to disable.
|
||||||
|
# Skipped automatically in dev (NODE_ENV !== 'production') and self-hosted (PUBLIC_IS_SELFHOSTED=true).
|
||||||
|
SENTRY_DSN=""
|
||||||
|
SENTRY_ENVIRONMENT="production"
|
||||||
|
SENTRY_TRACES_SAMPLE_RATE="0"
|
||||||
|
|
||||||
# BullMQ dashboard (/admin/queues) ───────────────────────────────────────────
|
# BullMQ dashboard (/admin/queues) ───────────────────────────────────────────
|
||||||
# In dev the dashboard is open. In production it's hidden unless ONE of the
|
# In dev the dashboard is open. In production it's hidden unless ONE of the
|
||||||
|
|||||||
@@ -54,7 +54,7 @@
|
|||||||
"@hono/standard-validator": "^0.1.5",
|
"@hono/standard-validator": "^0.1.5",
|
||||||
"@hono/zod-validator": "^0.7.6",
|
"@hono/zod-validator": "^0.7.6",
|
||||||
"@scalar/hono-api-reference": "^0.9.18",
|
"@scalar/hono-api-reference": "^0.9.18",
|
||||||
"@sentry/profiling-node": "^7.110.1",
|
"@sentry/node": "^10.53.1",
|
||||||
"ai": "^6.0.158",
|
"ai": "^6.0.158",
|
||||||
"axios": "^1.4.0",
|
"axios": "^1.4.0",
|
||||||
"css-tree": "^3.2.1",
|
"css-tree": "^3.2.1",
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import 'dotenv/config';
|
import 'dotenv/config';
|
||||||
|
|
||||||
|
import * as Sentry from '@sentry/node';
|
||||||
import { consumeHandoffPayload, storeHandoffPayload } from '@api/utils/redis/oauth-handoff';
|
import { consumeHandoffPayload, storeHandoffPayload } from '@api/utils/redis/oauth-handoff';
|
||||||
import { isPublicCorsPath, sessionCors } from '@api/middlewares/cors';
|
import { isPublicCorsPath, sessionCors } from '@api/middlewares/cors';
|
||||||
import { randomBytes } from 'crypto';
|
import { randomBytes } from 'crypto';
|
||||||
@@ -78,6 +79,13 @@ export const app = new Hono()
|
|||||||
c.set('user', session.user);
|
c.set('user', session.user);
|
||||||
c.set('session', session.session);
|
c.set('session', session.session);
|
||||||
c.set('orgRoles', (session as { orgRoles?: Record<string, number> }).orgRoles ?? {});
|
c.set('orgRoles', (session as { orgRoles?: Record<string, number> }).orgRoles ?? {});
|
||||||
|
|
||||||
|
Sentry.setUser({
|
||||||
|
id: session.user.id,
|
||||||
|
...(session.user.email && { email: session.user.email }),
|
||||||
|
...(session.user.name && { username: session.user.name })
|
||||||
|
});
|
||||||
|
|
||||||
await next();
|
await next();
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -206,6 +214,7 @@ export const app = new Hono()
|
|||||||
|
|
||||||
// Error handling
|
// Error handling
|
||||||
.onError((err, c) => {
|
.onError((err, c) => {
|
||||||
|
Sentry.captureException(err);
|
||||||
console.error('Error:', err);
|
console.error('Error:', err);
|
||||||
return c.json({ error: 'Internal Server Error' }, 500);
|
return c.json({ error: 'Internal Server Error' }, 500);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -78,6 +78,10 @@ const envSchema = z.object({
|
|||||||
|
|
||||||
return Number.isFinite(parsed) && parsed >= 1 ? Math.trunc(parsed) : 15;
|
return Number.isFinite(parsed) && parsed >= 1 ? Math.trunc(parsed) : 15;
|
||||||
}),
|
}),
|
||||||
|
/** Sentry error tracking (server-side). Leave SENTRY_DSN unset to disable. */
|
||||||
|
SENTRY_DSN: z.string().optional(),
|
||||||
|
SENTRY_ENVIRONMENT: z.string().optional(),
|
||||||
|
SENTRY_TRACES_SAMPLE_RATE: z.string().optional(),
|
||||||
/** Max fetch_documentation_url calls per org per UTC day (default 500). */
|
/** Max fetch_documentation_url calls per org per UTC day (default 500). */
|
||||||
AGENT_MAX_FETCHES_PER_ORG_PER_DAY: z
|
AGENT_MAX_FETCHES_PER_ORG_PER_DAY: z
|
||||||
.string()
|
.string()
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import 'dotenv/config';
|
import 'dotenv/config';
|
||||||
|
import './instrument';
|
||||||
|
|
||||||
import { API_PORT } from '@api/constants';
|
import { API_PORT } from '@api/constants';
|
||||||
import { app } from '@api/app';
|
import { app } from '@api/app';
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
/**
|
||||||
|
* Must be imported as the FIRST thing in `apps/api/src/index.ts`. The Sentry
|
||||||
|
* Node SDK relies on auto-instrumentation that hooks into Node's `require`
|
||||||
|
* resolution and module loaders; any code that runs before this file initializes
|
||||||
|
* Sentry will not be traced.
|
||||||
|
*/
|
||||||
|
import * as Sentry from '@sentry/node';
|
||||||
|
|
||||||
|
const dsn = process.env.SENTRY_DSN?.trim();
|
||||||
|
const isProd = process.env.NODE_ENV === 'production';
|
||||||
|
const isSelfHosted = process.env.PUBLIC_IS_SELFHOSTED === 'true';
|
||||||
|
|
||||||
|
if (dsn && isProd && !isSelfHosted) {
|
||||||
|
Sentry.init({
|
||||||
|
dsn,
|
||||||
|
environment: process.env.SENTRY_ENVIRONMENT?.trim() || 'production',
|
||||||
|
tracesSampleRate: Number(process.env.SENTRY_TRACES_SAMPLE_RATE ?? 0),
|
||||||
|
sendDefaultPii: false
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -45,3 +45,10 @@ PRIVATE_APP_SUBDOMAINS=app
|
|||||||
POLAR_ACCESS_TOKEN=
|
POLAR_ACCESS_TOKEN=
|
||||||
POLAR_WEBHOOK_SECRET=
|
POLAR_WEBHOOK_SECRET=
|
||||||
POLAR_TOKEN_PACK_PRODUCT_ID=
|
POLAR_TOKEN_PACK_PRODUCT_ID=
|
||||||
|
|
||||||
|
# Sentry (client-side error tracking). Leave PUBLIC_SENTRY_DSN unset to disable.
|
||||||
|
PUBLIC_SENTRY_DSN=
|
||||||
|
PUBLIC_SENTRY_ENVIRONMENT=production
|
||||||
|
PUBLIC_SENTRY_TRACES_SAMPLE_RATE=0
|
||||||
|
PUBLIC_SENTRY_REPLAYS_SESSION_SAMPLE_RATE=0
|
||||||
|
PUBLIC_SENTRY_REPLAYS_ON_ERROR_SAMPLE_RATE=1
|
||||||
|
|||||||
@@ -32,6 +32,7 @@
|
|||||||
"@lucide/svelte": "^0.545.0",
|
"@lucide/svelte": "^0.545.0",
|
||||||
"@polar-sh/sdk": "^0.47.1",
|
"@polar-sh/sdk": "^0.47.1",
|
||||||
"@polar-sh/sveltekit": "^0.7.5",
|
"@polar-sh/sveltekit": "^0.7.5",
|
||||||
|
"@sentry/sveltekit": "^10.53.1",
|
||||||
"@sveltejs/adapter-auto": "^3.0.0",
|
"@sveltejs/adapter-auto": "^3.0.0",
|
||||||
"@sveltejs/adapter-node": "^2.0.0",
|
"@sveltejs/adapter-node": "^2.0.0",
|
||||||
"@sveltejs/adapter-vercel": "^4.0.0",
|
"@sveltejs/adapter-vercel": "^4.0.0",
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import * as Sentry from '@sentry/sveltekit';
|
||||||
|
import { dev } from '$app/environment';
|
||||||
|
import { env } from '$env/dynamic/public';
|
||||||
|
import { handleErrorWithSentry } from '@sentry/sveltekit';
|
||||||
|
|
||||||
|
const dsn = env.PUBLIC_SENTRY_DSN?.trim();
|
||||||
|
const isSelfHosted = env.PUBLIC_IS_SELFHOSTED === 'true';
|
||||||
|
|
||||||
|
if (dsn && !dev && !isSelfHosted) {
|
||||||
|
Sentry.init({
|
||||||
|
dsn,
|
||||||
|
environment: env.PUBLIC_SENTRY_ENVIRONMENT?.trim() || 'production',
|
||||||
|
tracesSampleRate: Number(env.PUBLIC_SENTRY_TRACES_SAMPLE_RATE ?? 0),
|
||||||
|
replaysSessionSampleRate: Number(env.PUBLIC_SENTRY_REPLAYS_SESSION_SAMPLE_RATE ?? 0),
|
||||||
|
replaysOnErrorSampleRate: Number(env.PUBLIC_SENTRY_REPLAYS_ON_ERROR_SAMPLE_RATE ?? 1),
|
||||||
|
integrations: [Sentry.replayIntegration()]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export const handleError = handleErrorWithSentry();
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
import { PUBLIC_IS_SELFHOSTED } from '$env/static/public';
|
import { PUBLIC_IS_SELFHOSTED } from '$env/static/public';
|
||||||
import { initPosthog, type PosthogBootstrapUser } from '$lib/utils/services/posthog';
|
import { initPosthog, type PosthogBootstrapUser } from '$lib/utils/services/posthog';
|
||||||
import { initSentry } from '$lib/utils/services/sentry';
|
|
||||||
import { initUmami } from '$lib/utils/services/umami';
|
import { initUmami } from '$lib/utils/services/umami';
|
||||||
import { licenseApi } from '$features/license/api/license.svelte';
|
import { licenseApi } from '$features/license/api/license.svelte';
|
||||||
|
|
||||||
@@ -10,7 +9,6 @@ export function setupAnalytics(user?: PosthogBootstrapUser) {
|
|||||||
if (isInitialized) return;
|
if (isInitialized) return;
|
||||||
isInitialized = true;
|
isInitialized = true;
|
||||||
|
|
||||||
initSentry();
|
|
||||||
initPosthog(user);
|
initPosthog(user);
|
||||||
initUmami();
|
initUmami();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { appInitApi } from '$features/app/init.svelte';
|
import { appInitApi } from '$features/app/init.svelte';
|
||||||
import { authClient } from '$lib/utils/services/auth/client';
|
import { authClient } from '$lib/utils/services/auth/client';
|
||||||
import { capturePosthogEvent } from '$lib/utils/services/posthog';
|
import { capturePosthogEvent } from '$lib/utils/services/posthog';
|
||||||
|
import { clearSentryUser } from '$lib/utils/services/sentry';
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
import posthog from 'posthog-js';
|
import posthog from 'posthog-js';
|
||||||
import { resolve } from '$app/paths';
|
import { resolve } from '$app/paths';
|
||||||
@@ -16,6 +17,7 @@ export async function logout(redirect = true) {
|
|||||||
|
|
||||||
capturePosthogEvent('user_logged_out');
|
capturePosthogEvent('user_logged_out');
|
||||||
posthog.reset();
|
posthog.reset();
|
||||||
|
clearSentryUser();
|
||||||
|
|
||||||
if (redirect) {
|
if (redirect) {
|
||||||
goto(resolve('/login', {}));
|
goto(resolve('/login', {}));
|
||||||
|
|||||||
@@ -289,8 +289,6 @@ export function getApiHeaders(
|
|||||||
): { headers: { cookie: string; 'cio-org-id'?: string } } {
|
): { headers: { cookie: string; 'cio-org-id'?: string } } {
|
||||||
const cioCookies = getCioCookieString(cookies);
|
const cioCookies = getCioCookieString(cookies);
|
||||||
|
|
||||||
console.log('cioCookies', cioCookies);
|
|
||||||
console.log('cookies', cookies.getAll());
|
|
||||||
const headers: { cookie: string; 'cio-org-id'?: string } = {
|
const headers: { cookie: string; 'cio-org-id'?: string } = {
|
||||||
cookie: cioCookies || ''
|
cookie: cioCookies || ''
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,26 +1,30 @@
|
|||||||
|
import * as Sentry from '@sentry/sveltekit';
|
||||||
import { dev } from '$app/environment';
|
import { dev } from '$app/environment';
|
||||||
// import * as Sentry from '@sentry/browser';
|
import { env } from '$env/dynamic/public';
|
||||||
// import { CaptureConsole } from '@sentry/integrations';
|
|
||||||
|
|
||||||
export const initSentry = (): void => {
|
const isEnabled = () =>
|
||||||
if (dev) return;
|
Boolean(env.PUBLIC_SENTRY_DSN?.trim()) && !dev && env.PUBLIC_IS_SELFHOSTED !== 'true';
|
||||||
|
|
||||||
// Sentry.init({
|
export type SentryUser = {
|
||||||
// dsn: 'https://c966f7e8cb1d4306be20b26bb4f0cc96@o476906.ingest.sentry.io/5999999',
|
id: string;
|
||||||
// integrations: [
|
email?: string | null;
|
||||||
// new CaptureConsole({
|
username?: string | null;
|
||||||
// levels: ['error']
|
fullname?: string | null;
|
||||||
// }),
|
|
||||||
// new Sentry.Replay()
|
|
||||||
// ],
|
|
||||||
// environment: 'production',
|
|
||||||
// replaysSessionSampleRate: 0.5,
|
|
||||||
// replaysOnErrorSampleRate: 1.0
|
|
||||||
// });
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const setSentryUser = (user: Record<string, unknown>): void => {
|
export const setSentryUser = (user: SentryUser): void => {
|
||||||
if (dev) return;
|
if (!isEnabled()) return;
|
||||||
|
|
||||||
// Sentry.setUser(user);
|
Sentry.setUser({
|
||||||
|
id: user.id,
|
||||||
|
...(user.email && { email: user.email }),
|
||||||
|
...(user.username && { username: user.username }),
|
||||||
|
...(user.fullname && { fullname: user.fullname })
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const clearSentryUser = (): void => {
|
||||||
|
if (!isEnabled()) return;
|
||||||
|
|
||||||
|
Sentry.setUser(null);
|
||||||
};
|
};
|
||||||
|
|||||||
Generated
+1040
-106
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user