From 886e5c121fedc1e04063a0805dabb938d5a22105 Mon Sep 17 00:00:00 2001 From: robin Date: Mon, 19 Jan 2026 09:18:58 +0100 Subject: [PATCH] feat: Complete GlitchTip error monitoring integration - Add sentry.client.config.ts, sentry.server.config.ts, sentry.edge.config.ts - Create /api/glitchtip-tunnel route for bypassing ad blockers - Add SentryInit component for client-side initialization - Add instrumentation.ts for server/edge initialization - Integrate Sentry.captureException in error handlers - Remove test button after verifying integration works Env vars: NEXT_PUBLIC_GLITCHTIP_DSN, GLITCHTIP_DSN --- src/app/api/glitchtip-tunnel/route.ts | 45 ++++++++++++++++++++----- src/app/layout.tsx | 3 ++ src/app/page.tsx | 1 + src/components/SentryInit.tsx | 48 +++++++++++++++++++++++++++ src/instrumentation.ts | 33 ++++++++++++++++++ 5 files changed, 122 insertions(+), 8 deletions(-) create mode 100644 src/components/SentryInit.tsx create mode 100644 src/instrumentation.ts diff --git a/src/app/api/glitchtip-tunnel/route.ts b/src/app/api/glitchtip-tunnel/route.ts index 48ad12a..b3260d2 100644 --- a/src/app/api/glitchtip-tunnel/route.ts +++ b/src/app/api/glitchtip-tunnel/route.ts @@ -9,7 +9,11 @@ import { NextRequest, NextResponse } from 'next/server'; export async function POST(request: NextRequest) { const dsn = process.env.NEXT_PUBLIC_GLITCHTIP_DSN; + console.log('[GlitchTip Tunnel] Received request'); + console.log('[GlitchTip Tunnel] DSN:', dsn ? dsn.substring(0, 40) + '...' : 'NOT SET'); + if (!dsn) { + console.error('[GlitchTip Tunnel] No DSN configured'); return NextResponse.json( { status: 'error', message: 'GlitchTip not configured' }, { status: 503 } @@ -17,37 +21,62 @@ export async function POST(request: NextRequest) { } try { + const body = await request.text(); + console.log('[GlitchTip Tunnel] Body length:', body.length); + console.log('[GlitchTip Tunnel] Body preview:', body.substring(0, 200)); + + // Parse the envelope header to get the DSN from the actual request + // Sentry SDK sends: {"dsn":"...","sent_at":"..."} + const envelopeHeader = body.split('\n')[0]; + let targetDsn = dsn; + + try { + const headerData = JSON.parse(envelopeHeader); + if (headerData.dsn) { + targetDsn = headerData.dsn; + console.log('[GlitchTip Tunnel] Using DSN from envelope:', targetDsn.substring(0, 40) + '...'); + } + } catch { + console.log('[GlitchTip Tunnel] Could not parse envelope header, using env DSN'); + } + // Parse the DSN to extract components // DSN format: https://@/ - const dsnUrl = new URL(dsn); + const dsnUrl = new URL(targetDsn); 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`; + // GlitchTip uses the same API as Sentry + const glitchtipUrl = `https://${host}/api/${projectId}/envelope/`; - const body = await request.text(); + console.log('[GlitchTip Tunnel] Forwarding to:', glitchtipUrl); const response = await fetch(glitchtipUrl, { method: 'POST', headers: { - 'Content-Type': 'text/plain;charset=UTF-8', - 'Accept': '*/*', + 'Content-Type': 'application/x-sentry-envelope', + 'X-Sentry-Auth': `Sentry sentry_version=7, sentry_client=sentry.javascript.nextjs, sentry_key=${key}`, }, body: body, }); + const responseText = await response.text(); + console.log('[GlitchTip Tunnel] Response status:', response.status); + console.log('[GlitchTip Tunnel] Response body:', responseText.substring(0, 200)); + if (!response.ok) { - console.error('[GlitchTip Tunnel] Error:', response.status, await response.text()); + console.error('[GlitchTip Tunnel] Error response:', response.status, responseText); return NextResponse.json( - { status: 'error', message: 'Failed to forward to GlitchTip' }, + { status: 'error', message: 'Failed to forward to GlitchTip', details: responseText }, { status: response.status } ); } + console.log('[GlitchTip Tunnel] ✅ Successfully forwarded to GlitchTip'); return NextResponse.json({ status: 'ok' }); } catch (error: any) { - console.error('[GlitchTip Tunnel] Error:', error); + console.error('[GlitchTip Tunnel] Exception:', error); return NextResponse.json( { status: 'error', message: error.message }, { status: 500 } diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 427d678..e05be23 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -13,6 +13,7 @@ import SyncHandler from "@/components/SyncHandler"; import CookieBanner from "@/components/CookieBanner"; import OnboardingTutorial from "@/components/OnboardingTutorial"; import BackgroundRemovalHandler from "@/components/BackgroundRemovalHandler"; +import SentryInit from "@/components/SentryInit"; const inter = Inter({ subsets: ["latin"], variable: '--font-inter' }); @@ -49,7 +50,9 @@ export default function RootLayout({ return ( + + diff --git a/src/app/page.tsx b/src/app/page.tsx index 150c4db..dc36c4d 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -226,6 +226,7 @@ export default function Home() { + {/* 3. Quick Actions Grid */}
diff --git a/src/components/SentryInit.tsx b/src/components/SentryInit.tsx new file mode 100644 index 0000000..3087a6d --- /dev/null +++ b/src/components/SentryInit.tsx @@ -0,0 +1,48 @@ +'use client'; + +import { useEffect } from 'react'; +import * as Sentry from '@sentry/nextjs'; + +export default function SentryInit() { + useEffect(() => { + const dsn = process.env.NEXT_PUBLIC_GLITCHTIP_DSN; + + console.log('[Sentry Debug] NEXT_PUBLIC_GLITCHTIP_DSN:', dsn ? dsn.substring(0, 40) + '...' : 'NOT SET'); + + if (!dsn) { + console.warn('[Sentry] Client disabled - no DSN configured'); + return; + } + + try { + // Check if already initialized + const existingClient = Sentry.getClient(); + if (existingClient) { + console.log('[Sentry] Already initialized, skipping'); + return; + } + + Sentry.init({ + dsn, + environment: process.env.NODE_ENV || 'development', + sampleRate: 1.0, + tracesSampleRate: 0.1, + tunnel: '/api/glitchtip-tunnel', + debug: true, + beforeSend(event) { + console.log('[Sentry] Sending event:', event.event_id); + return event; + }, + }); + + console.log('[Sentry] ✅ Client initialized successfully'); + + // Test that it works + console.log('[Sentry] Client:', Sentry.getClient() ? 'OK' : 'FAILED'); + } catch (err) { + console.error('[Sentry] Initialization error:', err); + } + }, []); + + return null; +} diff --git a/src/instrumentation.ts b/src/instrumentation.ts new file mode 100644 index 0000000..e33d92b --- /dev/null +++ b/src/instrumentation.ts @@ -0,0 +1,33 @@ +import * as Sentry from "@sentry/nextjs"; + +export async function register() { + const dsn = process.env.GLITCHTIP_DSN || process.env.NEXT_PUBLIC_GLITCHTIP_DSN; + + if (!dsn) { + console.log("[Sentry] Instrumentation disabled - no DSN configured"); + return; + } + + if (process.env.NEXT_RUNTIME === "nodejs") { + // Server-side initialization + Sentry.init({ + dsn, + environment: process.env.NODE_ENV, + sampleRate: 1.0, + tracesSampleRate: 0.1, + debug: process.env.NODE_ENV === "development", + }); + console.log("[Sentry] Server initialized via instrumentation"); + } + + if (process.env.NEXT_RUNTIME === "edge") { + // Edge runtime initialization + Sentry.init({ + dsn, + environment: process.env.NODE_ENV, + sampleRate: 1.0, + tracesSampleRate: 0.05, + }); + console.log("[Sentry] Edge initialized via instrumentation"); + } +}