-
Notifications
You must be signed in to change notification settings - Fork 46
feat: integrate Sentry for error tracking using direct API calls. #336
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
… 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.
📝 WalkthroughWalkthroughAdds 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
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
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
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom Pre-merge Checks in the settings. ✨ Finishing touches
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. Comment |
There was a problem hiding this 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 aligningSentryConfigtype with actual usage.The
SentryConfigtype definesprojectIdandpublicKeyas requiredstring, but inapp/lib/config.ts,sentryConfigassigns these asdsnParts?.projectIdanddsnParts?.publicKey, which can beundefined. 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 frompackage.jsonor 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
messageorexception) 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
AbortControllerto 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 dynamicrequire().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
SentryConfigtype: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 incaptureException.While the current implementation is generally safe,
createEventandsendEventcould 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
📒 Files selected for processing (8)
app/api/sentry-tunnel/route.tsapp/hooks/analytics/analytics-utils.tsapp/hooks/useSentry.tsapp/lib/config.tsapp/lib/sentry.tsapp/lib/server-analytics.tsapp/providers.tsxapp/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
captureExceptionincludes useful tags and extra data for debugging.One minor observation: the
levelis hardcoded to"error", but thecaptureExceptionsignature inapp/lib/sentry.tsalready defaults to"error". This is fine as it makes the intent explicit.
46-47: New analytics event keys look good.The new
FEE_UPDATEDandSETTINGS_CHANGEDevent 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 alongsideuseMixpanel()inContextProviders, 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
captureExceptionprovides 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
errorandunhandledrejectionevents- 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 usescrypto.randomUUID()on the server-side only, and the project enforces Node.js >=18.0.0 via theenginesfield, 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.
There was a problem hiding this 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
📒 Files selected for processing (4)
app/api/sentry-tunnel/route.tsapp/lib/config.tsapp/lib/sentry.tsapp/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: truehas 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
createEventfunction comprehensively builds a Sentry event with appropriate environment metadata, source tagging, and conditional stack trace parsing for Error objects.
122-181: LGTM!The
sendEventfunction 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
captureExceptionAPI correctly applies filtering and delegates to the event creation and sending pipeline with appropriate fire-and-forget semantics.
229-276: LGTM!The
initErrorHandlersfunction 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.
app/lib/sentry.ts
Outdated
| // 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, | ||
| }); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
There was a problem hiding this 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
📒 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
createEventreview).
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.
| 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; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| 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.
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:
/api/sentry-tunnelto forward client-side errors, bypassing ad blockers and CORS restrictionsconfig.tswith DSN parsing supporttrackErrorandtrackApiErrorfunctions in analytics utilitiesImplementation Details:
/api/{projectId}/store/endpoint for error submissionevent_id(32-character hex UUID) for each eventFiles Added:
app/lib/sentry.ts- Core Sentry utility with error capture and event creationapp/api/sentry-tunnel/route.ts- API route to tunnel client-side events to GlitchTipapp/hooks/useSentry.ts- React hook to initialize client-side error handlersapp/types.ts- AddedSentryConfigtype andSentryEventinterfaceFiles Modified:
app/lib/config.ts- Added Sentry configuration with DSN parsingapp/providers.tsx- AddeduseSentry()hook initializationapp/hooks/analytics/analytics-utils.ts- UpdatedtrackErrorto use Sentryapp/lib/server-analytics.ts- UpdatedtrackApiErrorto use SentryEnvironment Variables Required:
NEXT_PUBLIC_SENTRY_DSNNEXT_PUBLIC_SENTRY_URLBreaking Changes:
None. This is a new feature that doesn't modify existing functionality.
References
Testing
Manual Testing Steps:
Client-side error capture:
Server-side error capture:
Tunnel route:
/api/sentry-tunnelError filtering:
Checklist
mainBy submitting a PR, I agree to Paycrest's Contributor Code of Conduct and Contribution Guide.
Summary by CodeRabbit
New Features
Enhancements
✏️ Tip: You can customize this high-level summary in your review settings.