fix(ui): skip Sentry initialization when DSN is not configured (#9368)

This commit is contained in:
Alan Buscaglia
2025-12-01 18:05:45 +01:00
committed by GitHub
parent 56ea498cca
commit dda0a2567d
4 changed files with 231 additions and 206 deletions

View File

@@ -11,105 +11,110 @@
import { browserTracingIntegration } from "@sentry/browser"; import { browserTracingIntegration } from "@sentry/browser";
import * as Sentry from "@sentry/nextjs"; import * as Sentry from "@sentry/nextjs";
const isDevelopment = process.env.NEXT_PUBLIC_SENTRY_ENVIRONMENT === "local"; const SENTRY_DSN = process.env.NEXT_PUBLIC_SENTRY_DSN;
/** // Only initialize Sentry if DSN is configured
* Initialize Sentry error tracking and performance monitoring if (SENTRY_DSN) {
* const isDevelopment = process.env.NEXT_PUBLIC_SENTRY_ENVIRONMENT === "local";
* This setup includes:
* - Performance monitoring with Web Vitals tracking (LCP, FID, CLS, INP)
* - Long task detection for UI-blocking operations
* - beforeSend hook to filter noise
*/
Sentry.init({
// 📍 DSN - Data Source Name (identifies your Sentry project)
dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
// 🌍 Environment - Separate dev errors from production /**
environment: process.env.NEXT_PUBLIC_SENTRY_ENVIRONMENT || "local", * Initialize Sentry error tracking and performance monitoring
*
* This setup includes:
* - Performance monitoring with Web Vitals tracking (LCP, FID, CLS, INP)
* - Long task detection for UI-blocking operations
* - beforeSend hook to filter noise
*/
Sentry.init({
// 📍 DSN - Data Source Name (identifies your Sentry project)
dsn: SENTRY_DSN,
// 📦 Release - Track which version has the error // 🌍 Environment - Separate dev errors from production
release: process.env.NEXT_PUBLIC_PROWLER_RELEASE_VERSION, environment: process.env.NEXT_PUBLIC_SENTRY_ENVIRONMENT || "local",
// 🐛 Debug - Detailed logs in development console // 📦 Release - Track which version has the error
debug: isDevelopment, release: process.env.NEXT_PUBLIC_PROWLER_RELEASE_VERSION,
// 📊 Sample Rates - Performance monitoring // 🐛 Debug - Detailed logs in development console
// 100% in dev (test everything), 50% in production (balance visibility with costs) debug: isDevelopment,
tracesSampleRate: isDevelopment ? 1.0 : 0.5,
profilesSampleRate: isDevelopment ? 1.0 : 0.5,
// 🔌 Integrations // 📊 Sample Rates - Performance monitoring
integrations: [ // 100% in dev (test everything), 50% in production (balance visibility with costs)
// 📊 Performance Monitoring: Core Web Vitals + RUM tracesSampleRate: isDevelopment ? 1.0 : 0.5,
// Tracks LCP, FID, CLS, INP profilesSampleRate: isDevelopment ? 1.0 : 0.5,
// Real User Monitoring captures actual user experience, not synthetic tests
browserTracingIntegration({
enableLongTask: true, // Detect tasks that block UI (>50ms)
enableInp: true, // Interaction to Next Paint (Core Web Vital)
}),
],
// 🎣 beforeSend Hook - Filter or modify events before sending to Sentry // 🔌 Integrations
ignoreErrors: [ integrations: [
// Browser extensions // 📊 Performance Monitoring: Core Web Vitals + RUM
"top.GLOBALS", // Tracks LCP, FID, CLS, INP
// Random network errors // Real User Monitoring captures actual user experience, not synthetic tests
"Network request failed", browserTracingIntegration({
"NetworkError", enableLongTask: true, // Detect tasks that block UI (>50ms)
"Failed to fetch", enableInp: true, // Interaction to Next Paint (Core Web Vital)
// User canceled actions }),
"AbortError", ],
"Non-Error promise rejection captured",
// NextAuth expected errors
"NEXT_REDIRECT",
// ResizeObserver errors (common browser quirk, not real bugs)
"ResizeObserver",
],
beforeSend(event, hint) { // 🎣 beforeSend Hook - Filter or modify events before sending to Sentry
// Filter out noise: ResizeObserver errors (common browser quirk, not real bugs) ignoreErrors: [
if (event.message?.includes("ResizeObserver")) { // Browser extensions
return null; // Don't send to Sentry "top.GLOBALS",
} // Random network errors
"Network request failed",
"NetworkError",
"Failed to fetch",
// User canceled actions
"AbortError",
"Non-Error promise rejection captured",
// NextAuth expected errors
"NEXT_REDIRECT",
// ResizeObserver errors (common browser quirk, not real bugs)
"ResizeObserver",
],
// Filter out non-actionable errors beforeSend(event, hint) {
if (event.exception) { // Filter out noise: ResizeObserver errors (common browser quirk, not real bugs)
const error = hint.originalException; if (event.message?.includes("ResizeObserver")) {
return null; // Don't send to Sentry
// Don't send cancelled requests
if (
error &&
typeof error === "object" &&
"name" in error &&
error.name === "AbortError"
) {
return null;
} }
// Add additional context for API errors // Filter out non-actionable errors
if ( if (event.exception) {
error && const error = hint.originalException;
typeof error === "object" &&
"message" in error && // Don't send cancelled requests
typeof error.message === "string" && if (
error.message.includes("Request failed") error &&
) { typeof error === "object" &&
event.tags = { "name" in error &&
...event.tags, error.name === "AbortError"
error_type: "api_error", ) {
}; return null;
}
// Add additional context for API errors
if (
error &&
typeof error === "object" &&
"message" in error &&
typeof error.message === "string" &&
error.message.includes("Request failed")
) {
event.tags = {
...event.tags,
error_type: "api_error",
};
}
} }
}
return event; // Send to Sentry return event; // Send to Sentry
}, },
});
// 👤 Set user context (identifies who experienced the error)
// In production, this will be updated after authentication
if (isDevelopment) {
Sentry.setUser({
id: "dev-user",
}); });
// 👤 Set user context (identifies who experienced the error)
// In production, this will be updated after authentication
if (isDevelopment) {
Sentry.setUser({
id: "dev-user",
});
}
} }

View File

@@ -16,7 +16,14 @@
import * as Sentry from "@sentry/nextjs"; import * as Sentry from "@sentry/nextjs";
const SENTRY_DSN = process.env.SENTRY_DSN;
export async function register() { export async function register() {
// Skip Sentry initialization if DSN is not configured
if (!SENTRY_DSN) {
return;
}
// The Sentry SDK automatically loads the appropriate config based on runtime // The Sentry SDK automatically loads the appropriate config based on runtime
if (process.env.NEXT_RUNTIME === "nodejs") { if (process.env.NEXT_RUNTIME === "nodejs") {
await import("./sentry/sentry.server.config"); await import("./sentry/sentry.server.config");
@@ -27,4 +34,7 @@ export async function register() {
} }
} }
export const onRequestError = Sentry.captureRequestError; // Only capture request errors if Sentry is configured
export const onRequestError = SENTRY_DSN
? Sentry.captureRequestError
: undefined;

View File

@@ -1,64 +1,69 @@
import * as Sentry from "@sentry/nextjs"; import * as Sentry from "@sentry/nextjs";
const isProduction = process.env.SENTRY_ENVIRONMENT === "pro"; const SENTRY_DSN = process.env.SENTRY_DSN;
/** // Only initialize Sentry if DSN is configured
* Edge runtime Sentry configuration if (SENTRY_DSN) {
* const isProduction = process.env.SENTRY_ENVIRONMENT === "pro";
* Edge runtime has stricter constraints than Node.js:
* - Limited execution time (~10-30 seconds)
* - Lower memory availability
* - Reduced sample rates to minimize overhead
* - No complex integrations
*/
Sentry.init({
// 📍 DSN - Data Source Name (identifies your Sentry project)
dsn: process.env.SENTRY_DSN,
// 🌍 Environment configuration /**
environment: process.env.SENTRY_ENVIRONMENT || "local", * Edge runtime Sentry configuration
*
* Edge runtime has stricter constraints than Node.js:
* - Limited execution time (~10-30 seconds)
* - Lower memory availability
* - Reduced sample rates to minimize overhead
* - No complex integrations
*/
Sentry.init({
// 📍 DSN - Data Source Name (identifies your Sentry project)
dsn: SENTRY_DSN,
// 📦 Release tracking // 🌍 Environment configuration
release: process.env.SENTRY_RELEASE, environment: process.env.SENTRY_ENVIRONMENT || "local",
// 📊 Sample Rates - Reduced for edge runtime constraints // 📦 Release tracking
// 50% in dev, 25% in production (edge has lower overhead limits than server) release: process.env.SENTRY_RELEASE,
tracesSampleRate: isProduction ? 0.25 : 0.5,
// 🔌 Integrations - Edge runtime doesn't support all integrations // 📊 Sample Rates - Reduced for edge runtime constraints
integrations: [], // 50% in dev, 25% in production (edge has lower overhead limits than server)
tracesSampleRate: isProduction ? 0.25 : 0.5,
// 🎣 Filter expected errors - Don't send noise to Sentry // 🔌 Integrations - Edge runtime doesn't support all integrations
ignoreErrors: [ integrations: [],
// NextAuth redirect errors - Expected behavior in auth flow
"NEXT_REDIRECT",
"NEXT_NOT_FOUND",
// Expected HTTP errors - Expected when users lack permissions
"401", // Unauthorized - expected when token expires
"403", // Forbidden - expected when no permissions
"404", // Not Found - expected for missing resources
],
beforeSend(event, hint) { // 🎣 Filter expected errors - Don't send noise to Sentry
// Add edge runtime context for debugging ignoreErrors: [
event.tags = { // NextAuth redirect errors - Expected behavior in auth flow
...event.tags, "NEXT_REDIRECT",
runtime: "edge", "NEXT_NOT_FOUND",
}; // Expected HTTP errors - Expected when users lack permissions
"401", // Unauthorized - expected when token expires
"403", // Forbidden - expected when no permissions
"404", // Not Found - expected for missing resources
],
const error = hint.originalException; beforeSend(event, hint) {
// Add edge runtime context for debugging
event.tags = {
...event.tags,
runtime: "edge",
};
// Don't send NextAuth expected errors const error = hint.originalException;
if (
error &&
typeof error === "object" &&
"message" in error &&
typeof error.message === "string" &&
error.message.includes("NEXT_REDIRECT")
) {
return null;
}
return event; // Don't send NextAuth expected errors
}, if (
}); error &&
typeof error === "object" &&
"message" in error &&
typeof error.message === "string" &&
error.message.includes("NEXT_REDIRECT")
) {
return null;
}
return event;
},
});
}

View File

@@ -1,80 +1,85 @@
import * as Sentry from "@sentry/nextjs"; import * as Sentry from "@sentry/nextjs";
const isProduction = process.env.SENTRY_ENVIRONMENT === "pro"; const SENTRY_DSN = process.env.SENTRY_DSN;
/** // Only initialize Sentry if DSN is configured
* Server-side Sentry configuration if (SENTRY_DSN) {
* const isProduction = process.env.SENTRY_ENVIRONMENT === "pro";
* This setup includes:
* - Performance monitoring for server-side operations
* - Error tracking for API routes and server actions
* - beforeSend hook to filter noise and add context
*/
Sentry.init({
// 📍 DSN - Data Source Name (identifies your Sentry project)
dsn: process.env.SENTRY_DSN,
// 🌍 Environment configuration /**
environment: process.env.SENTRY_ENVIRONMENT || "local", * Server-side Sentry configuration
*
* This setup includes:
* - Performance monitoring for server-side operations
* - Error tracking for API routes and server actions
* - beforeSend hook to filter noise and add context
*/
Sentry.init({
// 📍 DSN - Data Source Name (identifies your Sentry project)
dsn: SENTRY_DSN,
// 📦 Release tracking // 🌍 Environment configuration
release: process.env.SENTRY_RELEASE, environment: process.env.SENTRY_ENVIRONMENT || "local",
// 📊 Sample Rates - Performance monitoring // 📦 Release tracking
// 100% in dev (test everything), 50% in production (balance visibility with costs) release: process.env.SENTRY_RELEASE,
tracesSampleRate: isProduction ? 0.5 : 1.0,
profilesSampleRate: isProduction ? 0.5 : 1.0,
// 🔌 Integrations // 📊 Sample Rates - Performance monitoring
integrations: [ // 100% in dev (test everything), 50% in production (balance visibility with costs)
Sentry.extraErrorDataIntegration({ tracesSampleRate: isProduction ? 0.5 : 1.0,
depth: 5, // Include up to 5 levels of nested objects profilesSampleRate: isProduction ? 0.5 : 1.0,
}),
],
// 🎣 Filter expected errors - Don't send noise to Sentry // 🔌 Integrations
ignoreErrors: [ integrations: [
// NextAuth redirect errors - Expected behavior Sentry.extraErrorDataIntegration({
"NEXT_REDIRECT", depth: 5, // Include up to 5 levels of nested objects
"NEXT_NOT_FOUND", }),
// Expected HTTP errors - Expected when users lack permissions ],
"401", // Unauthorized
"403", // Forbidden
"404", // Not Found
],
beforeSend(event, hint) { // 🎣 Filter expected errors - Don't send noise to Sentry
// Add server context and tag errors appropriately ignoreErrors: [
if (event.exception) { // NextAuth redirect errors - Expected behavior
const error = hint.originalException; "NEXT_REDIRECT",
"NEXT_NOT_FOUND",
// Expected HTTP errors - Expected when users lack permissions
"401", // Unauthorized
"403", // Forbidden
"404", // Not Found
],
// Tag API errors for better filtering in Sentry dashboard beforeSend(event, hint) {
if ( // Add server context and tag errors appropriately
error && if (event.exception) {
typeof error === "object" && const error = hint.originalException;
"message" in error &&
typeof error.message === "string"
) {
if (error.message.includes("Server error")) {
event.tags = {
...event.tags,
error_type: "server_error",
severity: "high",
};
} else if (error.message.includes("Request failed")) {
event.tags = {
...event.tags,
error_type: "api_error",
};
}
// Don't send NextAuth expected errors // Tag API errors for better filtering in Sentry dashboard
if (error.message.includes("NEXT_REDIRECT")) { if (
return null; error &&
typeof error === "object" &&
"message" in error &&
typeof error.message === "string"
) {
if (error.message.includes("Server error")) {
event.tags = {
...event.tags,
error_type: "server_error",
severity: "high",
};
} else if (error.message.includes("Request failed")) {
event.tags = {
...event.tags,
error_type: "api_error",
};
}
// Don't send NextAuth expected errors
if (error.message.includes("NEXT_REDIRECT")) {
return null;
}
} }
} }
}
return event; return event;
}, },
}); });
}