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) {
|
||||
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://<key>@<host>/<project_id>
|
||||
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 }
|
||||
|
||||
@@ -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 (
|
||||
<html lang="de" suppressHydrationWarning={true}>
|
||||
<body className={`${inter.variable} font-sans`}>
|
||||
<SentryInit />
|
||||
<I18nProvider>
|
||||
|
||||
<AuthProvider>
|
||||
<SessionProvider>
|
||||
<ActiveSessionBanner />
|
||||
|
||||
@@ -226,6 +226,7 @@ export default function Home() {
|
||||
<HeroBanner />
|
||||
</div>
|
||||
|
||||
|
||||
{/* 3. Quick Actions Grid */}
|
||||
<div className="px-4 mb-4">
|
||||
<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