UNPKG

@sentry/remix

Version:
147 lines (125 loc) • 5.54 kB
var { _optionalChain } = require('@sentry/utils/cjs/buildPolyfills'); Object.defineProperty(exports, '__esModule', { value: true }); const core = require('@sentry/core'); const node = require('@sentry/node'); const utils = require('@sentry/utils'); const process = require('process'); const instrumentServer = require('../instrumentServer.js'); let pkg; function wrapExpressRequestHandler( origRequestHandler, build, ) { const routes = instrumentServer.createRoutes(build.routes); // If the core request handler is already wrapped, don't wrap Express handler which uses it. if (instrumentServer.isRequestHandlerWrapped) { return origRequestHandler; } return async function ( req, res, next, ) { if (!pkg) { try { pkg = await Promise.resolve().then(() => require('react-router-dom')); } catch (e) { pkg = await Promise.resolve().then(() => require(`${process.cwd()}/node_modules/react-router-dom`)); } finally { if (!pkg) { (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && utils.logger.error('Could not find `react-router-dom` package.'); } } } // eslint-disable-next-line @typescript-eslint/unbound-method res.end = wrapEndMethod(res.end); const request = utils.extractRequestData(req); const hub = core.getCurrentHub(); const options = _optionalChain([hub, 'access', _ => _.getClient, 'call', _2 => _2(), 'optionalAccess', _3 => _3.getOptions, 'call', _4 => _4()]); const scope = hub.getScope(); if (scope) { scope.setSDKProcessingMetadata({ request }); } if (!options || !core.hasTracingEnabled(options) || !request.url || !request.method) { return origRequestHandler.call(this, req, res, next); } const url = new URL(request.url); const [name, source] = instrumentServer.getTransactionName(routes, url, pkg); const transaction = instrumentServer.startRequestHandlerTransaction(hub, name, source, { headers: { 'sentry-trace': (req.headers && utils.isString(req.headers['sentry-trace']) && req.headers['sentry-trace']) || '', baggage: (req.headers && utils.isString(req.headers.baggage) && req.headers.baggage) || '', }, method: request.method, }); // save a link to the transaction on the response, so that even if there's an error (landing us outside of // the domain), we can still finish it (albeit possibly missing some scope data) (res ).__sentryTransaction = transaction; return origRequestHandler.call(this, req, res, next); }; } /** * Instruments `createRequestHandler` from `@remix-run/express` */ function wrapExpressCreateRequestHandler( origCreateRequestHandler, // eslint-disable-next-line @typescript-eslint/no-explicit-any ) { // eslint-disable-next-line @typescript-eslint/no-explicit-any return function ( options) { const newBuild = instrumentServer.instrumentBuild((options ).build); const requestHandler = origCreateRequestHandler.call(this, { ...options, build: newBuild }); return wrapExpressRequestHandler(requestHandler, newBuild); }; } /** * Wrap `res.end()` so that it closes the transaction and flushes events before letting the request finish. * * Note: This wraps a sync method with an async method. While in general that's not a great idea in terms of keeping * things in the right order, in this case it's safe, because the native `.end()` actually *is* async, and its run * actually *is* awaited, just manually so (which reflects the fact that the core of the request/response code in Node * by far predates the introduction of `async`/`await`). When `.end()` is done, it emits the `prefinish` event, and * only once that fires does request processing continue. See * https://github.com/nodejs/node/commit/7c9b607048f13741173d397795bac37707405ba7. * * @param origEnd The original `res.end()` method * @returns The wrapped version */ function wrapEndMethod(origEnd) { return async function newEnd( ...args) { await finishSentryProcessing(this); return origEnd.call(this, ...args); }; } /** * Close the open transaction (if any) and flush events to Sentry. * * @param res The outgoing response for this request, on which the transaction is stored */ async function finishSentryProcessing(res) { const { __sentryTransaction: transaction } = res; if (transaction) { transaction.setHttpStatus(res.statusCode); // Push `transaction.finish` to the next event loop so open spans have a better chance of finishing before the // transaction closes, and make sure to wait until that's done before flushing events await new Promise(resolve => { setImmediate(() => { transaction.finish(); resolve(); }); }); } // Flush the event queue to ensure that events get sent to Sentry before the response is finished and the lambda // ends. If there was an error, rethrow it so that the normal exception-handling mechanisms can apply. try { (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && utils.logger.log('Flushing events...'); await node.flush(2000); (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && utils.logger.log('Done flushing events'); } catch (e) { (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && utils.logger.log('Error while flushing events:\n', e); } } exports.wrapExpressCreateRequestHandler = wrapExpressCreateRequestHandler; //# sourceMappingURL=express.js.map