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
This commit is contained in:
@@ -9,7 +9,11 @@ import { NextRequest, NextResponse } from 'next/server';
|
|||||||
export async function POST(request: NextRequest) {
|
export async function POST(request: NextRequest) {
|
||||||
const dsn = process.env.NEXT_PUBLIC_GLITCHTIP_DSN;
|
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) {
|
if (!dsn) {
|
||||||
|
console.error('[GlitchTip Tunnel] No DSN configured');
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
{ status: 'error', message: 'GlitchTip not configured' },
|
{ status: 'error', message: 'GlitchTip not configured' },
|
||||||
{ status: 503 }
|
{ status: 503 }
|
||||||
@@ -17,37 +21,62 @@ export async function POST(request: NextRequest) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
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
|
// Parse the DSN to extract components
|
||||||
// DSN format: https://<key>@<host>/<project_id>
|
// DSN format: https://<key>@<host>/<project_id>
|
||||||
const dsnUrl = new URL(dsn);
|
const dsnUrl = new URL(targetDsn);
|
||||||
const key = dsnUrl.username;
|
const key = dsnUrl.username;
|
||||||
const host = dsnUrl.host;
|
const host = dsnUrl.host;
|
||||||
const projectId = dsnUrl.pathname.replace('/', '');
|
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, {
|
const response = await fetch(glitchtipUrl, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'text/plain;charset=UTF-8',
|
'Content-Type': 'application/x-sentry-envelope',
|
||||||
'Accept': '*/*',
|
'X-Sentry-Auth': `Sentry sentry_version=7, sentry_client=sentry.javascript.nextjs, sentry_key=${key}`,
|
||||||
},
|
},
|
||||||
body: body,
|
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) {
|
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(
|
return NextResponse.json(
|
||||||
{ status: 'error', message: 'Failed to forward to GlitchTip' },
|
{ status: 'error', message: 'Failed to forward to GlitchTip', details: responseText },
|
||||||
{ status: response.status }
|
{ status: response.status }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log('[GlitchTip Tunnel] ✅ Successfully forwarded to GlitchTip');
|
||||||
return NextResponse.json({ status: 'ok' });
|
return NextResponse.json({ status: 'ok' });
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error('[GlitchTip Tunnel] Error:', error);
|
console.error('[GlitchTip Tunnel] Exception:', error);
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
{ status: 'error', message: error.message },
|
{ status: 'error', message: error.message },
|
||||||
{ status: 500 }
|
{ status: 500 }
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import SyncHandler from "@/components/SyncHandler";
|
|||||||
import CookieBanner from "@/components/CookieBanner";
|
import CookieBanner from "@/components/CookieBanner";
|
||||||
import OnboardingTutorial from "@/components/OnboardingTutorial";
|
import OnboardingTutorial from "@/components/OnboardingTutorial";
|
||||||
import BackgroundRemovalHandler from "@/components/BackgroundRemovalHandler";
|
import BackgroundRemovalHandler from "@/components/BackgroundRemovalHandler";
|
||||||
|
import SentryInit from "@/components/SentryInit";
|
||||||
|
|
||||||
const inter = Inter({ subsets: ["latin"], variable: '--font-inter' });
|
const inter = Inter({ subsets: ["latin"], variable: '--font-inter' });
|
||||||
|
|
||||||
@@ -49,7 +50,9 @@ export default function RootLayout({
|
|||||||
return (
|
return (
|
||||||
<html lang="de" suppressHydrationWarning={true}>
|
<html lang="de" suppressHydrationWarning={true}>
|
||||||
<body className={`${inter.variable} font-sans`}>
|
<body className={`${inter.variable} font-sans`}>
|
||||||
|
<SentryInit />
|
||||||
<I18nProvider>
|
<I18nProvider>
|
||||||
|
|
||||||
<AuthProvider>
|
<AuthProvider>
|
||||||
<SessionProvider>
|
<SessionProvider>
|
||||||
<ActiveSessionBanner />
|
<ActiveSessionBanner />
|
||||||
|
|||||||
@@ -226,6 +226,7 @@ export default function Home() {
|
|||||||
<HeroBanner />
|
<HeroBanner />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
{/* 3. Quick Actions Grid */}
|
{/* 3. Quick Actions Grid */}
|
||||||
<div className="px-4 mb-4">
|
<div className="px-4 mb-4">
|
||||||
<QuickActionsGrid />
|
<QuickActionsGrid />
|
||||||
|
|||||||
48
src/components/SentryInit.tsx
Normal file
48
src/components/SentryInit.tsx
Normal file
@@ -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;
|
||||||
|
}
|
||||||
33
src/instrumentation.ts
Normal file
33
src/instrumentation.ts
Normal file
@@ -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");
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user