@sentry/nextjs
Version:
Official Sentry SDK for Next.js
128 lines (114 loc) • 5.49 kB
JavaScript
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
const core = require('@sentry/core');
const responseEnd = require('../utils/responseEnd.js');
const tracingUtils = require('../utils/tracingUtils.js');
/**
* Wrap the given API route handler with error nad performance monitoring.
*
* @param apiHandler The handler exported from the user's API page route file, which may or may not already be
* wrapped with `withSentry`
* @param parameterizedRoute The page's parameterized route.
* @returns The wrapped handler which will always return a Promise.
*/
function wrapApiHandlerWithSentry(apiHandler, parameterizedRoute) {
return new Proxy(apiHandler, {
apply: (
wrappingTarget,
thisArg,
args,
) => {
tracingUtils.dropNextjsRootContext();
return tracingUtils.escapeNextjsTracing(() => {
const [req, res] = args;
if (!req) {
core.logger.debug(
`Wrapped API handler on route "${parameterizedRoute}" was not passed a request object. Will not instrument.`,
);
return wrappingTarget.apply(thisArg, args);
} else if (!res) {
core.logger.debug(
`Wrapped API handler on route "${parameterizedRoute}" was not passed a response object. Will not instrument.`,
);
return wrappingTarget.apply(thisArg, args);
}
// Prevent double wrapping of the same request.
if (req.__withSentry_applied__) {
return wrappingTarget.apply(thisArg, args);
}
req.__withSentry_applied__ = true;
return core.withIsolationScope(isolationScope => {
// Normally, there is an active span here (from Next.js OTEL) and we just use that as parent
// Else, we manually continueTrace from the incoming headers
const continueTraceIfNoActiveSpan = core.getActiveSpan()
? (_opts, callback) => callback()
: core.continueTrace;
return continueTraceIfNoActiveSpan(
{
sentryTrace:
req.headers && core.isString(req.headers['sentry-trace']) ? req.headers['sentry-trace'] : undefined,
baggage: req.headers?.baggage,
},
() => {
const reqMethod = `${(req.method || 'GET').toUpperCase()} `;
const normalizedRequest = core.httpRequestToRequestData(req);
isolationScope.setSDKProcessingMetadata({ normalizedRequest });
isolationScope.setTransactionName(`${reqMethod}${parameterizedRoute}`);
return core.startSpanManual(
{
name: `${reqMethod}${parameterizedRoute}`,
op: 'http.server',
forceTransaction: true,
attributes: {
[core.SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route',
[core.SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.http.nextjs',
},
},
async span => {
// eslint-disable-next-line @typescript-eslint/unbound-method
res.end = new Proxy(res.end, {
apply(target, thisArg, argArray) {
core.setHttpStatus(span, res.statusCode);
span.end();
core.vercelWaitUntil(responseEnd.flushSafelyWithTimeout());
return target.apply(thisArg, argArray);
},
});
try {
return await wrappingTarget.apply(thisArg, args);
} catch (e) {
// In case we have a primitive, wrap it in the equivalent wrapper class (string -> String, etc.) so that we can
// store a seen flag on it. (Because of the one-way-on-Vercel-one-way-off-of-Vercel approach we've been forced
// to take, it can happen that the same thrown object gets caught in two different ways, and flagging it is a
// way to prevent it from actually being reported twice.)
const objectifiedErr = core.objectify(e);
core.captureException(objectifiedErr, {
mechanism: {
type: 'instrument',
handled: false,
data: {
wrapped_handler: wrappingTarget.name,
function: 'withSentry',
},
},
});
core.setHttpStatus(span, 500);
span.end();
// we need to await the flush here to ensure that the error is captured
// as the runtime freezes as soon as the error is thrown below
await responseEnd.flushSafelyWithTimeout();
// We rethrow here so that nextjs can do with the error whatever it would normally do. (Sometimes "whatever it
// would normally do" is to allow the error to bubble up to the global handlers - another reason we need to mark
// the error as already having been captured.)
throw objectifiedErr;
}
},
);
},
);
});
});
},
});
}
exports.wrapApiHandlerWithSentry = wrapApiHandlerWithSentry;
//# sourceMappingURL=wrapApiHandlerWithSentry.js.map