UNPKG

@sentry/nextjs

Version:
209 lines (206 loc) 7.42 kB
import { GLOBAL_OBJ, parseSemver, debug, suppressTracing } from '@sentry/core'; import * as stackTraceParser from 'stacktrace-parser'; import { DEBUG_BUILD } from './debug-build.js'; const globalWithInjectedValues = GLOBAL_OBJ; function getDevServerBaseUrl() { let basePath = process.env._sentryBasePath ?? globalWithInjectedValues._sentryBasePath ?? ""; if (basePath !== "" && !basePath.match(/^\//)) { basePath = `/${basePath}`; } if (typeof window !== "undefined") { return basePath; } const devServerPort = process.env.PORT || "3000"; return `http://localhost:${devServerPort}${basePath}`; } async function fetchWithTimeout(url, options = {}) { const controller = new AbortController(); const timer = setTimeout(() => controller.abort(), 3e3); return suppressTracing( () => fetch(url, { ...options, signal: controller.signal }).finally(() => { clearTimeout(timer); }) ); } async function devErrorSymbolicationEventProcessor(event, hint) { if (event.type === "transaction") { event.spans = event.spans?.filter((span) => { const httpUrlAttribute = span.data?.["http.url"]; if (typeof httpUrlAttribute === "string") { return !httpUrlAttribute.includes("__nextjs_original-stack-frame"); } return true; }); } try { if (hint.originalException && hint.originalException instanceof Error && hint.originalException.stack) { const frames = stackTraceParser.parse(hint.originalException.stack); const nextJsVersion = globalWithInjectedValues._sentryNextJsVersion; if (!nextJsVersion) { return event; } const parsedNextjsVersion = parseSemver(nextJsVersion); let resolvedFrames; if (parsedNextjsVersion.major > 15 || parsedNextjsVersion.major === 15 && parsedNextjsVersion.minor >= 2) { const r = await resolveStackFrames(frames); if (r === null) { return event; } resolvedFrames = r; } else { resolvedFrames = await Promise.all( frames.map((frame) => resolveStackFrame(frame, hint.originalException)) ); } if (event.exception?.values?.[0]?.stacktrace?.frames) { event.exception.values[0].stacktrace.frames = event.exception.values[0].stacktrace.frames.map( (frame, i, frames2) => { const resolvedFrame = resolvedFrames[frames2.length - 1 - i]; if (!resolvedFrame?.originalStackFrame || !resolvedFrame.originalCodeFrame) { return { ...frame, platform: frame.filename?.startsWith("node:internal") ? "nodejs" : void 0, // simple hack that will prevent a source mapping error from showing up in_app: false }; } const { contextLine, preContextLines, postContextLines } = parseOriginalCodeFrame( resolvedFrame.originalCodeFrame ); return { ...frame, pre_context: preContextLines, context_line: contextLine, post_context: postContextLines, function: resolvedFrame.originalStackFrame.methodName, filename: resolvedFrame.originalStackFrame.file ? stripWebpackInternalPrefix(resolvedFrame.originalStackFrame.file) : void 0, lineno: resolvedFrame.originalStackFrame.lineNumber || resolvedFrame.originalStackFrame.line1 || void 0, colno: resolvedFrame.originalStackFrame.column || resolvedFrame.originalStackFrame.column1 || void 0 }; } ); } } } catch { return event; } return event; } async function resolveStackFrame(frame, error) { try { if (!(frame.file?.startsWith("webpack-internal:") || frame.file?.startsWith("file:"))) { return null; } const params = new URLSearchParams(); params.append("isServer", String(false)); params.append("isEdgeServer", String(false)); params.append("isAppDirectory", String(true)); params.append("errorMessage", error.toString()); Object.keys(frame).forEach((key) => { params.append(key, (frame[key] ?? "").toString()); }); const baseUrl = getDevServerBaseUrl(); const res = await fetchWithTimeout(`${baseUrl}/__nextjs_original-stack-frame?${params.toString()}`); if (!res.ok || res.status === 204) { return null; } const body = await res.json(); return { originalCodeFrame: body.originalCodeFrame, originalStackFrame: body.originalStackFrame }; } catch (e) { DEBUG_BUILD && debug.error("Failed to symbolicate event with Next.js dev server", e); return null; } } async function resolveStackFrames(frames) { try { const postBody = { frames: frames.filter((frame) => { return !!frame.file; }).map((frame) => { frame.file = frame.file.replace(/^rsc:\/\/React\/[^/]+\//, "").replace(/\?\d+$/, ""); return { file: frame.file, methodName: frame.methodName ?? "<unknown>", arguments: [], lineNumber: frame.lineNumber ?? 0, column: frame.column ?? 0, line1: frame.lineNumber ?? 0, column1: frame.column ?? 0 }; }), isServer: false, isEdgeServer: false, isAppDirectory: true }; const baseUrl = getDevServerBaseUrl(); const res = await fetchWithTimeout(`${baseUrl}/__nextjs_original-stack-frames`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(postBody) }); if (!res.ok || res.status === 204) { return null; } const body = await res.json(); return body.map((frame) => { return { originalCodeFrame: frame.value.originalCodeFrame, originalStackFrame: frame.value.originalStackFrame }; }); } catch (e) { DEBUG_BUILD && debug.error("Failed to symbolicate event with Next.js dev server", e); return null; } } function parseOriginalCodeFrame(codeFrame) { const preProcessedLines = codeFrame.replace( // eslint-disable-next-line no-control-regex /[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g, // https://stackoverflow.com/a/29497680 "" ).split("\n").filter((line) => !line.match(/^\s*\|/)).map((line) => ({ line, isErrorLine: !!line.match(/^>/) })).map((lineObj) => ({ ...lineObj, line: lineObj.line.replace(/^.*\|/, "") })); const preContextLines = []; let contextLine = void 0; const postContextLines = []; let reachedContextLine = false; for (const preProcessedLine of preProcessedLines) { if (preProcessedLine.isErrorLine) { contextLine = preProcessedLine.line; reachedContextLine = true; } else if (reachedContextLine) { postContextLines.push(preProcessedLine.line); } else { preContextLines.push(preProcessedLine.line); } } return { contextLine, preContextLines, postContextLines }; } function stripWebpackInternalPrefix(filename) { if (!filename) { return filename; } const webpackInternalRegex = /^webpack-internal:(?:\/+)?(?:\([^)]*\)\/)?(.+)$/; const match = filename.match(webpackInternalRegex); return match ? match[1] : filename; } export { devErrorSymbolicationEventProcessor }; //# sourceMappingURL=devErrorSymbolicationEventProcessor.js.map