Skip to content

Conversation

@sundayonah
Copy link
Collaborator

@sundayonah sundayonah commented Jan 6, 2026

Description

This PR integrates GlitchTip (Sentry-compatible) error tracking into the Noblocks application to enable real-time error monitoring, logging, and alerting. The implementation uses direct API calls to GlitchTip instead of the Sentry SDK, providing full control over error reporting and reducing bundle size.

Key Features:

  • Client-side error capture: Automatically captures unhandled JavaScript errors and unhandled promise rejections
  • Server-side error capture: Integrates with existing API error handling to track server-side errors
  • Tunnel route: Implements /api/sentry-tunnel to forward client-side errors, bypassing ad blockers and CORS restrictions
  • Error filtering: Filters out development-specific noise (webpack chunk errors, Next.js HMR messages)
  • Centralized configuration: Sentry configuration is managed in config.ts with DSN parsing support
  • Analytics integration: Integrates with existing trackError and trackApiError functions in analytics utilities

Implementation Details:

  • Uses GlitchTip's /api/{projectId}/store/ endpoint for error submission
  • Generates unique event_id (32-character hex UUID) for each event
  • Includes stack traces, user context, request details, and environment information
  • Error tracking fails silently to prevent breaking the application

Files Added:

  • app/lib/sentry.ts - Core Sentry utility with error capture and event creation
  • app/api/sentry-tunnel/route.ts - API route to tunnel client-side events to GlitchTip
  • app/hooks/useSentry.ts - React hook to initialize client-side error handlers
  • app/types.ts - Added SentryConfig type and SentryEvent interface

Files Modified:

  • app/lib/config.ts - Added Sentry configuration with DSN parsing
  • app/providers.tsx - Added useSentry() hook initialization
  • app/hooks/analytics/analytics-utils.ts - Updated trackError to use Sentry
  • app/lib/server-analytics.ts - Updated trackApiError to use Sentry

Environment Variables Required:

  • NEXT_PUBLIC_SENTRY_DSN
  • NEXT_PUBLIC_SENTRY_URL

Breaking Changes:
None. This is a new feature that doesn't modify existing functionality.

References

Testing

Manual Testing Steps:

  1. Client-side error capture:

    • Trigger an unhandled error in the browser console
    • Verify the error appears in GlitchTip dashboard
    • Check that stack trace, URL, and user agent are included
  2. Server-side error capture:

    • Trigger an API error (e.g., invalid request)
    • Verify the error appears in GlitchTip with server context
    • Check that request details and IP address are included
  3. Tunnel route:

    • Verify client-side errors are forwarded through /api/sentry-tunnel
    • Check network tab to confirm requests are sent
    • Verify events appear in GlitchTip dashboard
  4. Error filtering:

    • Verify development webpack/Next.js chunk errors are filtered
    • Confirm production errors are still captured

Checklist

  • All active GitHub checks for tests, formatting, and security are passing
  • The correct base branch is being used, if not main

By submitting a PR, I agree to Paycrest's Contributor Code of Conduct and Contribution Guide.

Summary by CodeRabbit

  • New Features

    • Global client/server error monitoring with automatic initialization and manual capture API.
    • Server-side relay endpoint to forward events for more reliable error reporting.
    • New analytics events for fee updates and settings changes, plus a render-time performance metric.
  • Enhancements

    • Server-side API error reporting enriched with contextual tags and sanitized request data.
    • Public configuration and types added for Sentry integration.

✏️ Tip: You can customize this high-level summary in your review settings.

… handling

- Added Sentry configuration and integration for error tracking in both client and server environments.
- Updated analytics error tracking to send errors to Sentry with additional context.
- Introduced Sentry-related types in the types file for better type safety.
@sundayonah sundayonah requested a review from chibie as a code owner January 6, 2026 20:12
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 6, 2026

📝 Walkthrough

Walkthrough

Adds full Sentry integration: DSN parsing and exported sentryConfig, a new app/lib/sentry client+server library (captureException, initErrorHandlers), types, a client tunnel API route, a useSentry hook wired into providers, analytics wiring to surface errors, and server-side API error forwarding.

Changes

Cohort / File(s) Summary
Core Sentry library & types
app/lib/sentry.ts, app/types.ts
New Sentry implementation: stack parsing, createEvent/captureException, sendEvent with server/client routing and sampling, initErrorHandlers; adds SentryConfig and SentryEvent types.
Configuration
app/lib/config.ts
Adds DSN parsing and exports sentryConfig (serverUrl, projectId, publicKey, enabled, sampleRate, environment, release).
API tunnel
app/api/sentry-tunnel/route.ts
New Next.js POST endpoint that validates payload & config, enforces 32-char publicKey, generates a 32-char hex event_id, and forwards events to Sentry store with X-Sentry-Auth; returns JSON and handles errors.
Client integration & lifecycle
app/hooks/useSentry.ts, app/providers.tsx
New useSentry hook that initializes client global error handlers on mount and is invoked in ContextProviders; re-exports captureException.
Analytics & server reporting
app/hooks/analytics/analytics-utils.ts, app/lib/server-analytics.ts
Adds analytics keys (FEE_UPDATED, SETTINGS_CHANGED, RENDER_TIME); trackError now attempts client-side Sentry reporting via dynamic import; server trackApiError forwards sanitized context to captureException.

Sequence Diagram(s)

sequenceDiagram
    participant Browser as Client Browser
    participant WindowHandler as Global Error Handler
    participant Hook as useSentry / captureException
    participant EventBuilder as createEvent
    participant TunnelAPI as /api/sentry-tunnel
    participant SentryStore as Sentry /store/
    rect rgba(63,81,181,0.06)
    Note over Browser,SentryStore: Client-side error flow
    Browser->>WindowHandler: uncaught error / unhandledRejection
    WindowHandler->>Hook: captureException(error, meta)
    Hook->>EventBuilder: createEvent(error, context)
    EventBuilder->>Hook: SentryEvent (with event_id)
    Hook->>TunnelAPI: POST event (includes event_id)
    TunnelAPI->>SentryStore: POST /api/{project}/store/ with X-Sentry-Auth
    SentryStore-->>TunnelAPI: response
    TunnelAPI-->>Hook: response status
    end
Loading
sequenceDiagram
    participant AppServer as Server
    participant APIHandler as API endpoint
    participant ServerAnalytics as trackApiError
    participant Capture as captureException
    participant SentryServer as Sentry /store/
    rect rgba(76,175,80,0.06)
    Note over AppServer,SentryServer: Server-side error reporting
    APIHandler->>ServerAnalytics: API error occurs
    ServerAnalytics->>Capture: captureException(error, {level,tags,extra,request,user})
    Capture->>SentryServer: sendEvent (server /store/ with event_id)
    SentryServer-->>Capture: response
    Capture-->>ServerAnalytics: silent result
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • chibie

Poem

🐰 I hopped through stacks and parsed each trace,
I stitched an event id — thirty-two in place,
I tunneled the whispers from client to store,
Now errors get routed and troubles no more,
Hop-skip—observability, carrot encore! 🥕✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 14.29% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description check ✅ Passed The PR description comprehensively covers all required template sections: detailed description with implementation rationale, references to relevant issues and documentation, thorough testing steps, and completed checklist items.
Title check ✅ Passed The PR title accurately summarizes the main change: integrating Sentry for error tracking using direct API calls, which aligns with the core objective.

✏️ Tip: You can configure your own custom Pre-merge Checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Fix all issues with AI Agents
In @app/api/sentry-tunnel/route.ts:
- Around line 67-78: The code currently returns Sentry's raw responseText to
clients in the NextResponse.json call when response.ok is false; instead, log
the full responseText server-side (console.error or processLogger) and return a
generic error payload to the client (e.g., success: false, error: "Failed to
forward to Sentry") without including responseText or other internal details;
update the block that checks response.ok and the NextResponse.json invocation
(reference responseText, response.ok, and NextResponse.json) to remove or
replace the details field and only expose non-sensitive info while retaining
server-side logging.

In @app/lib/config.ts:
- Around line 88-96: sentryConfig currently forces enabled: true even when DSN
parsing failed; change enabled to be conditional by checking parsed DSN fields
so Sentry is only enabled when a valid config exists (e.g., require
dsnParts?.publicKey and dsnParts?.projectId or a valid sentryUrl/serverUrl) —
update the sentryConfig object (symbols: sentryConfig, dsnParts, serverUrl,
projectId, publicKey, sentryUrl) so enabled is computed from those values rather
than hardcoded true.

In @app/lib/server-analytics.ts:
- Around line 292-318: The code passes all request headers to Sentry via
Object.fromEntries(request.headers.entries()) in the captureException call;
instead filter out sensitive headers (e.g., Authorization, Cookie, Set-Cookie,
Proxy-Authorization, X-Api-Key, and any custom auth headers) before attaching to
the request object. Locate the captureException usage and replace the raw
headers value with a sanitizedHeaders object created by iterating
request.headers (or using Object.fromEntries) and removing or redacting keys in
a sensitive list (you can reuse sanitizeProperties naming convention), then pass
sanitizedHeaders to captureException.request.headers so no sensitive tokens are
sent to Sentry.
🧹 Nitpick comments (8)
app/types.ts (1)

262-270: Consider aligning SentryConfig type with actual usage.

The SentryConfig type defines projectId and publicKey as required string, but in app/lib/config.ts, sentryConfig assigns these as dsnParts?.projectId and dsnParts?.publicKey, which can be undefined. This type mismatch may cause TypeScript issues if the type is used elsewhere.

🔎 Proposed fix to make fields optional
 export type SentryConfig = {
   serverUrl: string;
-  projectId: string;
-  publicKey: string;
+  projectId?: string;
+  publicKey?: string;
   enabled: boolean;
   sampleRate: number;
   environment: string;
   release: string;
 };
app/lib/config.ts (1)

95-95: Consider making the release version dynamic.

The release version is hardcoded to "2.0.0". This could become stale. Consider sourcing it from package.json or an environment variable.

release: process.env.NEXT_PUBLIC_APP_VERSION || "unknown",
app/api/sentry-tunnel/route.ts (2)

4-24: Consider adding basic event structure validation.

The tunnel forwards any valid JSON to Sentry without validating the event structure. While Sentry will reject malformed events, adding basic validation (e.g., checking for expected fields like message or exception) could prevent unnecessary API calls and provide clearer error messages.

🔎 Example validation
// After JSON.parse
if (!event.message && !event.exception) {
    return NextResponse.json(
        { success: false, error: "Event must contain message or exception" },
        { status: 400 }
    );
}

55-63: Consider adding a timeout to the Sentry fetch request.

The fetch to Sentry has no timeout, which could cause the tunnel route to hang if Sentry is slow or unresponsive. Consider using AbortController to add a timeout.

🔎 Proposed fix with timeout
+        const controller = new AbortController();
+        const timeoutId = setTimeout(() => controller.abort(), 5000);
+
         const response = await fetch(storeUrl, {
             method: "POST",
             headers: {
                 "Content-Type": "application/json",
                 "X-Sentry-Auth": authHeader,
                 Accept: "*/*",
             },
             body: JSON.stringify(eventWithId),
+            signal: controller.signal,
         });
+
+        clearTimeout(timeoutId);
app/lib/sentry.ts (4)

3-7: Consider refactoring to avoid dynamic require().

Using dynamic require() in a Next.js/ESM context can cause bundling issues, tree-shaking problems, and complications with Hot Module Replacement. This pattern is typically used to work around circular dependencies, but it's fragile and can lead to runtime errors in certain build configurations.

Consider restructuring the module dependencies to allow a standard top-level import, or use a lazy-loaded ES module import if absolutely necessary.

🔎 Alternative approach using lazy ES module import
-function getConfig(): SentryConfig {
-    // eslint-disable-next-line @typescript-eslint/no-require-imports
-    const { sentryConfig } = require("./config");
-    return sentryConfig;
-}
+let cachedConfig: SentryConfig | null = null;
+
+async function getConfig(): Promise<SentryConfig> {
+    if (!cachedConfig) {
+        const { sentryConfig } = await import("./config");
+        cachedConfig = sentryConfig;
+    }
+    return cachedConfig;
+}

Note: This would require making dependent functions async as well.


160-179: Consider making error filtering more configurable.

The current implementation hard-codes development error filters. For better flexibility, consider allowing custom ignore patterns via configuration or a callback function.

🔎 Example: Configurable error filtering

Add to SentryConfig type:

export type SentryConfig = {
  // ... existing fields
  ignoreErrors?: Array<string | RegExp>;
  beforeSend?: (event: SentryEvent) => SentryEvent | null;
};

Update shouldIgnoreError:

 function shouldIgnoreError(error: Error | string): boolean {
     const errorMessage = error instanceof Error ? error.message : String(error);
     const errorString = errorMessage.toLowerCase();
+    const config = getConfig();
+    
+    // Check custom ignore patterns
+    if (config.ignoreErrors) {
+        for (const pattern of config.ignoreErrors) {
+            if (typeof pattern === 'string' && errorString.includes(pattern.toLowerCase())) {
+                return true;
+            } else if (pattern instanceof RegExp && pattern.test(errorMessage)) {
+                return true;
+            }
+        }
+    }
 
     // Filter out development-specific webpack/Next.js chunk errors
-    const config = getConfig();
     if (config.environment === "development") {
         // ... existing logic
     }
 
     return false;
 }

181-204: Consider adding error boundaries in captureException.

While the current implementation is generally safe, createEvent and sendEvent could potentially throw unexpected errors (e.g., serialization issues, network errors in unexpected places). Consider wrapping the core logic in a try-catch block for defense in depth.

🔎 Proposed defensive error handling
 export async function captureException(
     error: Error | string,
     context: {
         level?: "fatal" | "error" | "warning" | "info" | "debug";
         tags?: Record<string, string>;
         extra?: Record<string, any>;
         user?: {
             ip_address?: string;
         };
         request?: {
             url?: string;
             method?: string;
             headers?: Record<string, string>;
             query_string?: string;
         };
     } = {}
 ): Promise<void> {
+    try {
         // Filter out HMR and other development noise
         if (shouldIgnoreError(error)) {
             return;
         }
 
         await sendEvent(createEvent(error, context));
+    } catch (err) {
+        // Prevent Sentry from breaking the application
+        console.error('[Sentry] Failed to capture exception:', err);
+    }
 }

206-214: Add comments to empty cleanup functions.

The empty function bodies on Lines 208 and 213 should include brief comments explaining why they're no-ops (server-side or disabled state).

 export function initErrorHandlers(): () => void {
     if (typeof window === "undefined") {
-        return () => { };
+        return () => { /* No cleanup needed on server */ };
     }
 
     const config = getConfig();
     if (!config.enabled) {
-        return () => { };
+        return () => { /* No handlers attached when disabled */ };
     }
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a605397 and dff862d.

📒 Files selected for processing (8)
  • app/api/sentry-tunnel/route.ts
  • app/hooks/analytics/analytics-utils.ts
  • app/hooks/useSentry.ts
  • app/lib/config.ts
  • app/lib/sentry.ts
  • app/lib/server-analytics.ts
  • app/providers.tsx
  • app/types.ts
🧰 Additional context used
🧬 Code graph analysis (6)
app/lib/server-analytics.ts (1)
app/lib/sentry.ts (1)
  • captureException (181-204)
app/api/sentry-tunnel/route.ts (1)
app/lib/config.ts (1)
  • sentryConfig (88-96)
app/lib/sentry.ts (2)
app/types.ts (2)
  • SentryConfig (262-270)
  • SentryEvent (272-306)
app/lib/config.ts (1)
  • sentryConfig (88-96)
app/hooks/useSentry.ts (1)
app/lib/sentry.ts (1)
  • initErrorHandlers (206-253)
app/hooks/analytics/analytics-utils.ts (3)
app/hooks/analytics/index.ts (6)
  • trackError (13-13)
  • APP_NAMES (9-9)
  • trackAnalyticsEvent (10-10)
  • ANALYTICS_EVENTS (7-7)
  • ANALYTICS_PROPERTIES (8-8)
  • trackPerformance (14-14)
app/hooks/useSentry.ts (1)
  • captureException (16-16)
app/lib/sentry.ts (1)
  • captureException (181-204)
app/providers.tsx (1)
app/hooks/useSentry.ts (1)
  • useSentry (9-14)
🔇 Additional comments (14)
app/hooks/analytics/analytics-utils.ts (2)

148-176: LGTM! Client-side Sentry integration looks good.

The dynamic import pattern is appropriate for code splitting, and the silent failure ensures error tracking doesn't break the app. The context passed to captureException includes useful tags and extra data for debugging.

One minor observation: the level is hardcoded to "error", but the captureException signature in app/lib/sentry.ts already defaults to "error". This is fine as it makes the intent explicit.


46-47: New analytics event keys look good.

The new FEE_UPDATED and SETTINGS_CHANGED event keys follow the existing naming conventions.

app/types.ts (1)

272-306: SentryEvent interface is well-structured.

The interface correctly models the Sentry store API payload format with appropriate optional fields for flexibility.

app/providers.tsx (2)

25-25: LGTM!

Import follows the existing pattern for analytics hooks.


86-88: LGTM! Sentry initialization follows the Mixpanel pattern.

The useSentry() hook is appropriately placed alongside useMixpanel() in ContextProviders, ensuring error handlers are set up early in the component tree.

app/lib/server-analytics.ts (1)

3-3: LGTM!

Import statement is correct.

app/hooks/useSentry.ts (2)

9-14: LGTM! Clean hook implementation.

The hook correctly initializes error handlers on mount and returns the cleanup function. The empty dependency array ensures handlers are set up once.


16-16: LGTM!

Re-exporting captureException provides a convenient API for consumers who import from this hook module.

app/lib/config.ts (1)

63-77: DSN parsing is robust.

The regex pattern correctly extracts the Sentry DSN components. The try-catch ensures malformed DSNs don't crash the application.

app/api/sentry-tunnel/route.ts (2)

43-54: LGTM! Sentry store URL and event ID generation look correct.

The store URL format and auth header follow the Sentry protocol. The event ID generation (UUID without dashes, lowercase) is correct per Sentry's requirements.


34-41: The 32-character public key validation is correct and should remain.

Both Sentry and GlitchTip enforce exactly 32-character hexadecimal public keys. The validation at lines 34-41 properly enforces this required format and will reject malformed configurations as intended.

Likely an incorrect or invalid review comment.

app/lib/sentry.ts (3)

35-97: LGTM!

The event creation logic is well-structured, properly handles both Error objects and strings, and includes appropriate context for both client and server environments. The conditional stack trace attachment for Error objects is correct.


216-244: LGTM!

The error handler implementation is well-structured:

  • Properly handles both error and unhandledrejection events
  • Applies error filtering before sending
  • Includes relevant context (error type, filename, line/column)
  • Uses silent failure (.catch()) to prevent recursive errors
  • Creates Error objects from non-Error rejection reasons

127-136: No changes needed. The code correctly uses crypto.randomUUID() on the server-side only, and the project enforces Node.js >=18.0.0 via the engines field, which far exceeds the actual requirement (v15.6.0+). The environment-aware check (isServer) ensures the function is never called in browsers, making browser compatibility irrelevant. A fallback implementation is unnecessary.

Likely an incorrect or invalid review comment.

- Removed detailed error information from Sentry response to enhance security.
- Updated Sentry configuration to enable based on project ID and public key presence.
- Enhanced stack trace parsing to support additional formats and improve error context.
- Filtered out sensitive headers before sending error data to Sentry.
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI Agents
In @app/lib/sentry.ts:
- Around line 41-51: The Firefox/Safari stack parser incorrectly maps capture
groups when the second regex (/@(.+?):(\d+):(\d+)/) matches; update the
frames.push logic in the parsing block (the line with match =
line.match(/(.+?)@(.+?):(\d+):(\d+)/) || line.match(/@(.+?):(\d+):(\d+)/)) to
branch on which pattern matched (e.g., check if match[4] is defined or
match.length) and then assign fields accordingly: for the first pattern use
function = match[1], filename = match[2], lineno = match[3], colno = match[4];
for the second pattern use function = undefined, filename = match[1], lineno =
match[2], colno = match[3]; apply parseInt only on defined captures and keep
in_app: true.
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between dff862d and 769f38f.

📒 Files selected for processing (4)
  • app/api/sentry-tunnel/route.ts
  • app/lib/config.ts
  • app/lib/sentry.ts
  • app/lib/server-analytics.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • app/lib/server-analytics.ts
🧰 Additional context used
🧬 Code graph analysis (2)
app/lib/sentry.ts (4)
app/types.ts (2)
  • SentryConfig (262-270)
  • SentryEvent (272-306)
app/lib/config.ts (1)
  • sentryConfig (88-96)
public/sw.js (2)
  • event (53-53)
  • url (54-54)
app/hooks/useSentry.ts (1)
  • captureException (16-16)
app/api/sentry-tunnel/route.ts (2)
public/sw.js (1)
  • event (53-53)
app/lib/config.ts (1)
  • sentryConfig (88-96)
🔇 Additional comments (11)
app/api/sentry-tunnel/route.ts (3)

4-24: LGTM!

The request body parsing and validation logic is well-structured with appropriate error handling for empty bodies and invalid JSON.


26-41: LGTM!

Configuration validation is thorough, including the appropriate check for 32-character public key length required by Sentry.


43-76: Good fix - internal error details no longer exposed.

The event forwarding logic is correct, and the previous issue about exposing Sentry's error response details to clients has been properly addressed. Error details are now only logged server-side.

app/lib/config.ts (2)

62-86: LGTM!

The DSN parsing logic correctly extracts Sentry configuration components using an appropriate regex pattern, with proper error handling and debugging support.


88-96: Good fix - enabled flag now correctly conditional.

The previous issue about hardcoded enabled: true has been properly addressed. Sentry is now only enabled when valid configuration exists (both projectId and publicKey are present).

app/lib/sentry.ts (6)

3-7: LGTM!

Using dynamic require() to import config is appropriate here, likely to avoid circular dependencies or support both server and client-side contexts.


58-120: LGTM!

The createEvent function comprehensively builds a Sentry event with appropriate environment metadata, source tagging, and conditional stack trace parsing for Error objects.


122-181: LGTM!

The sendEvent function correctly handles environment-aware routing, implements sampling, and properly formats requests for both server-side (direct to Sentry) and client-side (via tunnel) scenarios.


183-202: LGTM!

The development noise filtering appropriately targets webpack and Next.js HMR errors only in development environment, preventing spam without suppressing production errors.


204-227: LGTM!

The public captureException API correctly applies filtering and delegates to the event creation and sending pipeline with appropriate fire-and-forget semantics.


229-276: LGTM!

The initErrorHandlers function properly initializes global error handlers with appropriate guards, applies filtering, and returns a cleanup function for lifecycle management. Silent failure on capture errors is correct to prevent breaking the application.

Comment on lines 41 to 51
// Try Firefox/Safari format: "functionName@file:line:col" or "@file:line:col"
match = line.match(/(.+?)@(.+?):(\d+):(\d+)/) || line.match(/@(.+?):(\d+):(\d+)/);
if (match) {
frames.push({
function: match[1] || undefined,
filename: match[2] || match[1],
lineno: parseInt(match[3] || match[2], 10) || undefined,
colno: parseInt(match[4] || match[3], 10) || undefined,
in_app: true,
});
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Fix the Firefox/Safari stack trace parsing logic.

The fallback logic on lines 45-48 doesn't correctly handle the second regex pattern /@(.+?):(\d+):(\d+)/. When this pattern matches, the capture groups have different meanings:

  • First pattern /(.+?)@(.+?):(\d+):(\d+)/: match[1]=function, match[2]=filename, match[3]=lineno, match[4]=colno
  • Second pattern /@(.+?):(\d+):(\d+)/: match[1]=filename, match[2]=lineno, match[3]=colno

The current fallback logic match[2] || match[1] incorrectly assigns lineno (match[2]) to filename when the second pattern matches.

🔎 Proposed fix
             } else {
-                // Try Firefox/Safari format: "functionName@file:line:col" or "@file:line:col"
-                match = line.match(/(.+?)@(.+?):(\d+):(\d+)/) || line.match(/@(.+?):(\d+):(\d+)/);
+                // Try Firefox/Safari format with function name: "functionName@file:line:col"
+                match = line.match(/(.+?)@(.+?):(\d+):(\d+)/);
                 if (match) {
                     frames.push({
-                        function: match[1] || undefined,
-                        filename: match[2] || match[1],
-                        lineno: parseInt(match[3] || match[2], 10) || undefined,
-                        colno: parseInt(match[4] || match[3], 10) || undefined,
+                        function: match[1],
+                        filename: match[2],
+                        lineno: parseInt(match[3], 10) || undefined,
+                        colno: parseInt(match[4], 10) || undefined,
+                        in_app: true,
+                    });
+                } else {
+                    // Try Firefox/Safari format without function name: "@file:line:col"
+                    match = line.match(/@(.+?):(\d+):(\d+)/);
+                    if (match) {
+                        frames.push({
+                            function: undefined,
+                            filename: match[1],
+                            lineno: parseInt(match[2], 10) || undefined,
+                            colno: parseInt(match[3], 10) || undefined,
+                            in_app: true,
+                        });
+                    }
-                        in_app: true,
-                    });
                 }
             }
🤖 Prompt for AI Agents
In @app/lib/sentry.ts around lines 41 - 51, The Firefox/Safari stack parser
incorrectly maps capture groups when the second regex (/@(.+?):(\d+):(\d+)/)
matches; update the frames.push logic in the parsing block (the line with match
= line.match(/(.+?)@(.+?):(\d+):(\d+)/) || line.match(/@(.+?):(\d+):(\d+)/)) to
branch on which pattern matched (e.g., check if match[4] is defined or
match.length) and then assign fields accordingly: for the first pattern use
function = match[1], filename = match[2], lineno = match[3], colno = match[4];
for the second pattern use function = undefined, filename = match[1], lineno =
match[2], colno = match[3]; apply parseInt only on defined captures and keep
in_app: true.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In @app/lib/sentry.ts:
- Around line 47-109: createEvent currently doesn't accept or set user context
so IP/user data passed into captureException is never included; update
createEvent signature to accept an optional user parameter (matching the user
shape used by captureException, e.g., { ip_address?: string, id?: string,
email?: string, username?: string }) and populate event.user = { ...user } when
provided (ensure ip_address is set if present); then update captureException to
pass its user argument through to createEvent so Sentry events include the user
context.
🧹 Nitpick comments (1)
app/lib/sentry.ts (1)

3-7: Consider defensive error handling in config loading.

Since this function is called during error reporting, a failure here could crash the error reporting flow. Consider wrapping the require in try-catch and returning a safe default config to ensure error reporting fails gracefully.

🔎 Suggested defensive approach
 function getConfig(): SentryConfig {
-    // eslint-disable-next-line @typescript-eslint/no-require-imports
-    const { sentryConfig } = require("./config");
-    return sentryConfig;
+    try {
+        // eslint-disable-next-line @typescript-eslint/no-require-imports
+        const { sentryConfig } = require("./config");
+        return sentryConfig;
+    } catch (error) {
+        console.error("[Sentry] Failed to load config:", error);
+        return { enabled: false } as SentryConfig;
+    }
 }
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 769f38f and 9c47159.

📒 Files selected for processing (1)
  • app/lib/sentry.ts
🔇 Additional comments (4)
app/lib/sentry.ts (4)

172-191: LGTM: Development noise filtering is appropriate.

The function correctly filters webpack and Next.js chunk loading errors in development environment to prevent noise in error tracking.


193-216: LGTM: Clean API design for exception capture.

The function provides a clean public API with appropriate filtering and delegation. Note that the user context parameter is not currently forwarded to events (flagged separately in the createEvent review).


218-265: LGTM: Robust error handler initialization.

The implementation correctly:

  • Guards against server-side execution
  • Respects config.enabled flag
  • Filters development noise
  • Captures relevant error metadata
  • Returns cleanup function for proper lifecycle management
  • Fails silently to avoid cascading errors

9-45: Remove this review comment — it's based on unsupported assumptions.

The review assumes "previous review comments indicated Firefox/Safari format support was expected," but the sentry.ts file is newly created in this commit (not a refactor of existing code) with no prior history showing Firefox/Safari patterns or prior review expectations. The commit message "simplify stack trace parsing" describes the initial implementation design, not a reduction in features. No evidence of prior Firefox/Safari support exists in the codebase, tests, or documentation.

Likely an incorrect or invalid review comment.

Comment on lines 47 to 109
function createEvent(
error: Error | string,
context: {
level?: "fatal" | "error" | "warning" | "info" | "debug";
tags?: Record<string, string>;
extra?: Record<string, any>;
request?: {
url?: string;
method?: string;
headers?: Record<string, string>;
query_string?: string;
};
} = {}
): SentryEvent {
const isError = error instanceof Error;
const message = isError ? error.message : String(error);
const errorObj = isError ? error : new Error(message);
const config = getConfig();

const event: SentryEvent = {
message,
level: context.level || "error",
timestamp: Math.floor(Date.now() / 1000),
environment: config.environment,
release: config.release,
platform: "javascript",
sdk: { name: "sentry-api", version: "1.0.0" },
tags: {
...context.tags,
source: typeof window === "undefined" ? "server" : "client",
},
extra: {
...context.extra,
page_url:
typeof window !== "undefined" ? window.location.href : undefined,
user_agent:
typeof window !== "undefined" ? navigator.userAgent : undefined,
},
request:
context.request ||
(typeof window !== "undefined"
? {
url: window.location.href,
method: "GET",
query_string: window.location.search,
}
: undefined),
};

if (isError && errorObj.stack) {
event.exception = {
values: [
{
type: errorObj.name || "Error",
value: errorObj.message,
stacktrace: { frames: parseStackTrace(errorObj) },
},
],
};
}

return event;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Implement missing user context in event creation.

The captureException function accepts a user parameter (with ip_address field), but createEvent doesn't accept or use this parameter. This means user identification data is never included in Sentry events, even though the API suggests it's supported.

🔎 Proposed fix to include user context
 function createEvent(
     error: Error | string,
     context: {
         level?: "fatal" | "error" | "warning" | "info" | "debug";
         tags?: Record<string, string>;
         extra?: Record<string, any>;
+        user?: {
+            ip_address?: string;
+        };
         request?: {
             url?: string;
             method?: string;
             headers?: Record<string, string>;
             query_string?: string;
         };
     } = {}
 ): SentryEvent {
     const isError = error instanceof Error;
     const message = isError ? error.message : String(error);
     const errorObj = isError ? error : new Error(message);
     const config = getConfig();

     const event: SentryEvent = {
         message,
         level: context.level || "error",
         timestamp: Math.floor(Date.now() / 1000),
         environment: config.environment,
         release: config.release,
         platform: "javascript",
         sdk: { name: "sentry-api", version: "1.0.0" },
+        user: context.user,
         tags: {
             ...context.tags,
             source: typeof window === "undefined" ? "server" : "client",
         },
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function createEvent(
error: Error | string,
context: {
level?: "fatal" | "error" | "warning" | "info" | "debug";
tags?: Record<string, string>;
extra?: Record<string, any>;
request?: {
url?: string;
method?: string;
headers?: Record<string, string>;
query_string?: string;
};
} = {}
): SentryEvent {
const isError = error instanceof Error;
const message = isError ? error.message : String(error);
const errorObj = isError ? error : new Error(message);
const config = getConfig();
const event: SentryEvent = {
message,
level: context.level || "error",
timestamp: Math.floor(Date.now() / 1000),
environment: config.environment,
release: config.release,
platform: "javascript",
sdk: { name: "sentry-api", version: "1.0.0" },
tags: {
...context.tags,
source: typeof window === "undefined" ? "server" : "client",
},
extra: {
...context.extra,
page_url:
typeof window !== "undefined" ? window.location.href : undefined,
user_agent:
typeof window !== "undefined" ? navigator.userAgent : undefined,
},
request:
context.request ||
(typeof window !== "undefined"
? {
url: window.location.href,
method: "GET",
query_string: window.location.search,
}
: undefined),
};
if (isError && errorObj.stack) {
event.exception = {
values: [
{
type: errorObj.name || "Error",
value: errorObj.message,
stacktrace: { frames: parseStackTrace(errorObj) },
},
],
};
}
return event;
}
function createEvent(
error: Error | string,
context: {
level?: "fatal" | "error" | "warning" | "info" | "debug";
tags?: Record<string, string>;
extra?: Record<string, any>;
user?: {
ip_address?: string;
};
request?: {
url?: string;
method?: string;
headers?: Record<string, string>;
query_string?: string;
};
} = {}
): SentryEvent {
const isError = error instanceof Error;
const message = isError ? error.message : String(error);
const errorObj = isError ? error : new Error(message);
const config = getConfig();
const event: SentryEvent = {
message,
level: context.level || "error",
timestamp: Math.floor(Date.now() / 1000),
environment: config.environment,
release: config.release,
platform: "javascript",
sdk: { name: "sentry-api", version: "1.0.0" },
user: context.user,
tags: {
...context.tags,
source: typeof window === "undefined" ? "server" : "client",
},
extra: {
...context.extra,
page_url:
typeof window !== "undefined" ? window.location.href : undefined,
user_agent:
typeof window !== "undefined" ? navigator.userAgent : undefined,
},
request:
context.request ||
(typeof window !== "undefined"
? {
url: window.location.href,
method: "GET",
query_string: window.location.search,
}
: undefined),
};
if (isError && errorObj.stack) {
event.exception = {
values: [
{
type: errorObj.name || "Error",
value: errorObj.message,
stacktrace: { frames: parseStackTrace(errorObj) },
},
],
};
}
return event;
}
🤖 Prompt for AI Agents
In @app/lib/sentry.ts around lines 47 - 109, createEvent currently doesn't
accept or set user context so IP/user data passed into captureException is never
included; update createEvent signature to accept an optional user parameter
(matching the user shape used by captureException, e.g., { ip_address?: string,
id?: string, email?: string, username?: string }) and populate event.user = {
...user } when provided (ensure ip_address is set if present); then update
captureException to pass its user argument through to createEvent so Sentry
events include the user context.

- Introduced a new optional user property in SentryEvent interface to capture user IP address.
- Updated createEvent function to include user IP address in the Sentry event context.
@sundayonah sundayonah changed the title feat: integrate Sentry for error tracking and enhance analytics error… feat: integrate Sentry for error tracking using direct API calls. Jan 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants