@sentry/nextjs
Version:
Official Sentry SDK for Next.js
209 lines (206 loc) • 7.42 kB
JavaScript
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