@sentry/remix
Version:
Official Sentry SDK for Remix
147 lines (125 loc) • 5.54 kB
JavaScript
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