feat: Add GlitchTip error monitoring with Sentry SDK
- Install @sentry/nextjs - Add sentry.client.config.ts, sentry.server.config.ts, sentry.edge.config.ts - Conditional initialization based on GLITCHTIP_DSN env variable - Add /api/glitchtip-tunnel route to bypass ad blockers - Update next.config.mjs with withSentryConfig wrapper - Integrate Sentry.captureException in error.tsx and global-error.tsx - Support env vars: GLITCHTIP_DSN, NEXT_PUBLIC_GLITCHTIP_DSN, GLITCHTIP_URL, etc.
This commit is contained in:
@@ -1,7 +1,10 @@
|
|||||||
|
import { withSentryConfig } from "@sentry/nextjs";
|
||||||
|
|
||||||
/** @type {import('next').Config} */
|
/** @type {import('next').Config} */
|
||||||
const nextConfig = {
|
const nextConfig = {
|
||||||
output: 'standalone',
|
output: 'standalone',
|
||||||
productionBrowserSourceMaps: false,
|
// Enable source maps for Sentry stack traces in production
|
||||||
|
productionBrowserSourceMaps: !!process.env.GLITCHTIP_DSN,
|
||||||
experimental: {
|
experimental: {
|
||||||
serverActions: {
|
serverActions: {
|
||||||
bodySizeLimit: '10mb',
|
bodySizeLimit: '10mb',
|
||||||
@@ -9,4 +12,33 @@ const nextConfig = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default nextConfig;
|
// Wrap with Sentry only if DSN is configured
|
||||||
|
const sentryEnabled = !!process.env.GLITCHTIP_DSN || !!process.env.NEXT_PUBLIC_GLITCHTIP_DSN;
|
||||||
|
|
||||||
|
const sentryWebpackPluginOptions = {
|
||||||
|
// Suppresses source map uploading logs during build
|
||||||
|
silent: true,
|
||||||
|
|
||||||
|
// Organization and project slugs (optional - for source map upload)
|
||||||
|
org: process.env.GLITCHTIP_ORG,
|
||||||
|
project: process.env.GLITCHTIP_PROJECT,
|
||||||
|
|
||||||
|
// GlitchTip server URL
|
||||||
|
sentryUrl: process.env.GLITCHTIP_URL,
|
||||||
|
|
||||||
|
// Auth token for source map upload
|
||||||
|
authToken: process.env.GLITCHTIP_AUTH_TOKEN,
|
||||||
|
|
||||||
|
// Hides source maps from generated client bundles
|
||||||
|
hideSourceMaps: true,
|
||||||
|
|
||||||
|
// Automatically tree-shake Sentry logger statements
|
||||||
|
disableLogger: true,
|
||||||
|
|
||||||
|
// Prevent bundling of native binaries
|
||||||
|
widenClientFileUpload: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default sentryEnabled
|
||||||
|
? withSentryConfig(nextConfig, sentryWebpackPluginOptions)
|
||||||
|
: nextConfig;
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
"@ai-sdk/google": "^2.0.51",
|
"@ai-sdk/google": "^2.0.51",
|
||||||
"@google/generative-ai": "^0.24.1",
|
"@google/generative-ai": "^0.24.1",
|
||||||
"@mistralai/mistralai": "^1.11.0",
|
"@mistralai/mistralai": "^1.11.0",
|
||||||
|
"@sentry/nextjs": "^10.34.0",
|
||||||
"@supabase/ssr": "^0.5.2",
|
"@supabase/ssr": "^0.5.2",
|
||||||
"@supabase/supabase-js": "^2.47.10",
|
"@supabase/supabase-js": "^2.47.10",
|
||||||
"@tanstack/react-query": "^5.62.7",
|
"@tanstack/react-query": "^5.62.7",
|
||||||
|
|||||||
1700
pnpm-lock.yaml
generated
1700
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
36
sentry.client.config.ts
Normal file
36
sentry.client.config.ts
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import * as Sentry from "@sentry/nextjs";
|
||||||
|
|
||||||
|
const GLITCHTIP_DSN = process.env.NEXT_PUBLIC_GLITCHTIP_DSN;
|
||||||
|
|
||||||
|
// Only initialize Sentry if DSN is configured
|
||||||
|
if (GLITCHTIP_DSN) {
|
||||||
|
Sentry.init({
|
||||||
|
dsn: GLITCHTIP_DSN,
|
||||||
|
|
||||||
|
// Environment
|
||||||
|
environment: process.env.NODE_ENV,
|
||||||
|
|
||||||
|
// Sample rate for error events (1.0 = 100%)
|
||||||
|
sampleRate: 1.0,
|
||||||
|
|
||||||
|
// Performance monitoring sample rate (0.1 = 10%)
|
||||||
|
tracesSampleRate: 0.1,
|
||||||
|
|
||||||
|
// Use tunnel to bypass ad blockers
|
||||||
|
tunnel: "/api/glitchtip-tunnel",
|
||||||
|
|
||||||
|
// Disable debug in production
|
||||||
|
debug: process.env.NODE_ENV === "development",
|
||||||
|
|
||||||
|
// Ignore common non-actionable errors
|
||||||
|
ignoreErrors: [
|
||||||
|
"ResizeObserver loop limit exceeded",
|
||||||
|
"ResizeObserver loop completed with undelivered notifications",
|
||||||
|
"Non-Error promise rejection captured",
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log("[Sentry] Client initialized with GlitchTip");
|
||||||
|
} else {
|
||||||
|
console.log("[Sentry] Client disabled - no DSN configured");
|
||||||
|
}
|
||||||
21
sentry.edge.config.ts
Normal file
21
sentry.edge.config.ts
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import * as Sentry from "@sentry/nextjs";
|
||||||
|
|
||||||
|
const GLITCHTIP_DSN = process.env.GLITCHTIP_DSN || process.env.NEXT_PUBLIC_GLITCHTIP_DSN;
|
||||||
|
|
||||||
|
// Only initialize Sentry if DSN is configured
|
||||||
|
if (GLITCHTIP_DSN) {
|
||||||
|
Sentry.init({
|
||||||
|
dsn: GLITCHTIP_DSN,
|
||||||
|
|
||||||
|
// Environment
|
||||||
|
environment: process.env.NODE_ENV,
|
||||||
|
|
||||||
|
// Sample rate for error events (1.0 = 100%)
|
||||||
|
sampleRate: 1.0,
|
||||||
|
|
||||||
|
// Performance monitoring sample rate (lower for edge)
|
||||||
|
tracesSampleRate: 0.05,
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log("[Sentry] Edge initialized with GlitchTip");
|
||||||
|
}
|
||||||
26
sentry.server.config.ts
Normal file
26
sentry.server.config.ts
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import * as Sentry from "@sentry/nextjs";
|
||||||
|
|
||||||
|
const GLITCHTIP_DSN = process.env.GLITCHTIP_DSN || process.env.NEXT_PUBLIC_GLITCHTIP_DSN;
|
||||||
|
|
||||||
|
// Only initialize Sentry if DSN is configured
|
||||||
|
if (GLITCHTIP_DSN) {
|
||||||
|
Sentry.init({
|
||||||
|
dsn: GLITCHTIP_DSN,
|
||||||
|
|
||||||
|
// Environment
|
||||||
|
environment: process.env.NODE_ENV,
|
||||||
|
|
||||||
|
// Sample rate for error events (1.0 = 100%)
|
||||||
|
sampleRate: 1.0,
|
||||||
|
|
||||||
|
// Performance monitoring sample rate (0.1 = 10%)
|
||||||
|
tracesSampleRate: 0.1,
|
||||||
|
|
||||||
|
// Disable debug in production
|
||||||
|
debug: process.env.NODE_ENV === "development",
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log("[Sentry] Server initialized with GlitchTip");
|
||||||
|
} else {
|
||||||
|
console.log("[Sentry] Server disabled - no DSN configured");
|
||||||
|
}
|
||||||
56
src/app/api/glitchtip-tunnel/route.ts
Normal file
56
src/app/api/glitchtip-tunnel/route.ts
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
import { NextRequest, NextResponse } from 'next/server';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GlitchTip/Sentry Tunnel API Route
|
||||||
|
*
|
||||||
|
* This tunnels error reports from the client through our own API,
|
||||||
|
* bypassing ad blockers that might block direct Sentry/GlitchTip requests.
|
||||||
|
*/
|
||||||
|
export async function POST(request: NextRequest) {
|
||||||
|
const dsn = process.env.NEXT_PUBLIC_GLITCHTIP_DSN;
|
||||||
|
|
||||||
|
if (!dsn) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ status: 'error', message: 'GlitchTip not configured' },
|
||||||
|
{ status: 503 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Parse the DSN to extract components
|
||||||
|
// DSN format: https://<key>@<host>/<project_id>
|
||||||
|
const dsnUrl = new URL(dsn);
|
||||||
|
const key = dsnUrl.username;
|
||||||
|
const host = dsnUrl.host;
|
||||||
|
const projectId = dsnUrl.pathname.replace('/', '');
|
||||||
|
|
||||||
|
const glitchtipUrl = `https://${host}/api/${projectId}/envelope/?sentry_version=7&sentry_key=${key}&sentry_client=sentry.javascript.nextjs`;
|
||||||
|
|
||||||
|
const body = await request.text();
|
||||||
|
|
||||||
|
const response = await fetch(glitchtipUrl, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'text/plain;charset=UTF-8',
|
||||||
|
'Accept': '*/*',
|
||||||
|
},
|
||||||
|
body: body,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
console.error('[GlitchTip Tunnel] Error:', response.status, await response.text());
|
||||||
|
return NextResponse.json(
|
||||||
|
{ status: 'error', message: 'Failed to forward to GlitchTip' },
|
||||||
|
{ status: response.status }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return NextResponse.json({ status: 'ok' });
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error('[GlitchTip Tunnel] Error:', error);
|
||||||
|
return NextResponse.json(
|
||||||
|
{ status: 'error', message: error.message },
|
||||||
|
{ status: 500 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { AlertTriangle, RefreshCcw } from 'lucide-react';
|
import { AlertTriangle, RefreshCcw } from 'lucide-react';
|
||||||
|
import * as Sentry from "@sentry/nextjs";
|
||||||
|
|
||||||
export default function Error({
|
export default function Error({
|
||||||
error,
|
error,
|
||||||
@@ -12,8 +13,11 @@ export default function Error({
|
|||||||
}) {
|
}) {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
console.error('App Crash Error:', error);
|
console.error('App Crash Error:', error);
|
||||||
|
// Report error to Sentry/GlitchTip
|
||||||
|
Sentry.captureException(error);
|
||||||
}, [error]);
|
}, [error]);
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex min-h-screen flex-col items-center justify-center p-6 bg-zinc-50 dark:bg-black text-center">
|
<div className="flex min-h-screen flex-col items-center justify-center p-6 bg-zinc-50 dark:bg-black text-center">
|
||||||
<div className="bg-white dark:bg-zinc-900 p-8 rounded-3xl border border-zinc-200 dark:border-zinc-800 shadow-xl max-w-md w-full space-y-6">
|
<div className="bg-white dark:bg-zinc-900 p-8 rounded-3xl border border-zinc-200 dark:border-zinc-800 shadow-xl max-w-md w-full space-y-6">
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { RefreshCcw } from 'lucide-react';
|
import { RefreshCcw } from 'lucide-react';
|
||||||
|
import * as Sentry from "@sentry/nextjs";
|
||||||
|
import { useEffect } from "react";
|
||||||
|
|
||||||
export default function GlobalError({
|
export default function GlobalError({
|
||||||
error,
|
error,
|
||||||
@@ -9,6 +11,11 @@ export default function GlobalError({
|
|||||||
error: Error & { digest?: string };
|
error: Error & { digest?: string };
|
||||||
reset: () => void;
|
reset: () => void;
|
||||||
}) {
|
}) {
|
||||||
|
useEffect(() => {
|
||||||
|
// Report error to Sentry/GlitchTip
|
||||||
|
Sentry.captureException(error);
|
||||||
|
}, [error]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<html lang="de">
|
<html lang="de">
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
@@ -227,14 +227,14 @@ export default function SessionsPage() {
|
|||||||
<div
|
<div
|
||||||
key={session.id}
|
key={session.id}
|
||||||
className={`p-4 bg-zinc-900 rounded-2xl border transition-all group ${activeSession?.id === session.id
|
className={`p-4 bg-zinc-900 rounded-2xl border transition-all group ${activeSession?.id === session.id
|
||||||
? 'border-orange-600/50'
|
? 'border-orange-600/50'
|
||||||
: 'border-zinc-800 hover:border-zinc-700'
|
: 'border-zinc-800 hover:border-zinc-700'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-4">
|
||||||
<div className={`w-12 h-12 rounded-xl flex items-center justify-center ${session.ended_at
|
<div className={`w-12 h-12 rounded-xl flex items-center justify-center ${session.ended_at
|
||||||
? 'bg-zinc-800 text-zinc-500'
|
? 'bg-zinc-800 text-zinc-500'
|
||||||
: 'bg-orange-600/20 text-orange-500'
|
: 'bg-orange-600/20 text-orange-500'
|
||||||
}`}>
|
}`}>
|
||||||
<GlassWater size={24} />
|
<GlassWater size={24} />
|
||||||
</div>
|
</div>
|
||||||
@@ -256,7 +256,7 @@ export default function SessionsPage() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{session.participants && session.participants.length > 0 && (
|
{session.participants && session.participants.length > 0 && (
|
||||||
<AvatarStack names={session.participants} maxShow={3} size="sm" />
|
<AvatarStack names={session.participants} limit={3} size="sm" />
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
|
|||||||
Reference in New Issue
Block a user