@sentry/remix
Version:
Official Sentry SDK for Remix
219 lines (173 loc) • 6.17 kB
JavaScript
import { _optionalChain } from '@sentry/utils/esm/buildPolyfills';
import { captureException, getCurrentHub } from '@sentry/node';
import { getActiveTransaction } from '@sentry/tracing';
import { addExceptionMechanism, stripUrlQueryAndFragment, fill, loadModule, logger } from '@sentry/utils';
// Types vendored from @remix-run/server-runtime@1.6.0:
// https://github.com/remix-run/remix/blob/f3691d51027b93caa3fd2cdfe146d7b62a6eb8f2/packages/remix-server-runtime/server.ts
// Taken from Remix Implementation
// https://github.com/remix-run/remix/blob/7688da5c75190a2e29496c78721456d6e12e3abe/packages/remix-server-runtime/responses.ts#L54-L62
function isResponse(value) {
return (
value != null &&
typeof value.status === 'number' &&
typeof value.statusText === 'string' &&
typeof value.headers === 'object' &&
typeof value.body !== 'undefined'
);
}
// Taken from Remix Implementation
// https://github.com/remix-run/remix/blob/7688da5c75190a2e29496c78721456d6e12e3abe/packages/remix-server-runtime/data.ts#L131-L145
function extractData(response) {
var contentType = response.headers.get('Content-Type');
if (contentType && /\bapplication\/json\b/.test(contentType)) {
return response.json();
}
return response.text();
}
function captureRemixServerException(err, name) {
// Skip capturing if the thrown error is an OK Response
// https://remix.run/docs/en/v1/api/conventions#throwing-responses-in-loaders
if (isResponse(err) && err.status < 400) {
return;
}
captureException(isResponse(err) ? extractData(err) : err, scope => {
scope.addEventProcessor(event => {
addExceptionMechanism(event, {
type: 'instrument',
handled: true,
data: {
function: name,
},
});
return event;
});
return scope;
});
}
function makeWrappedDocumentRequestFunction(
origDocumentRequestFunction,
) {
return async function (
request,
responseStatusCode,
responseHeaders,
context,
) {
let res;
var activeTransaction = getActiveTransaction();
var currentScope = getCurrentHub().getScope();
if (!activeTransaction || !currentScope) {
return origDocumentRequestFunction.call(this, request, responseStatusCode, responseHeaders, context);
}
try {
var span = activeTransaction.startChild({
op: 'remix.server.documentRequest',
description: activeTransaction.name,
tags: {
method: request.method,
url: request.url,
},
});
res = await origDocumentRequestFunction.call(this, request, responseStatusCode, responseHeaders, context);
span.finish();
} catch (err) {
captureRemixServerException(err, 'documentRequest');
throw err;
}
return res;
};
}
function makeWrappedDataFunction(origFn, name) {
return async function ( args) {
let res;
var activeTransaction = getActiveTransaction();
var currentScope = getCurrentHub().getScope();
if (!activeTransaction || !currentScope) {
return origFn.call(this, args);
}
try {
var span = activeTransaction.startChild({
op: `remix.server.${name}`,
description: activeTransaction.name,
tags: {
name,
},
});
if (span) {
// Assign data function to hub to be able to see `db` transactions (if any) as children.
currentScope.setSpan(span);
}
res = await origFn.call(this, args);
currentScope.setSpan(activeTransaction);
span.finish();
} catch (err) {
captureRemixServerException(err, name);
throw err;
}
return res;
};
}
function makeWrappedAction(origAction) {
return makeWrappedDataFunction(origAction, 'action');
}
function makeWrappedLoader(origAction) {
return makeWrappedDataFunction(origAction, 'loader');
}
function wrapRequestHandler(origRequestHandler) {
return async function ( request, loadContext) {
var hub = getCurrentHub();
var currentScope = hub.getScope();
var transaction = hub.startTransaction({
name: stripUrlQueryAndFragment(request.url),
op: 'http.server',
tags: {
method: request.method,
},
metadata: {
source: 'url',
},
});
if (transaction) {
_optionalChain([currentScope, 'optionalAccess', _ => _.setSpan, 'call', _2 => _2(transaction)]);
}
var res = (await origRequestHandler.call(this, request, loadContext)) ;
_optionalChain([transaction, 'optionalAccess', _3 => _3.setHttpStatus, 'call', _4 => _4(res.status)]);
_optionalChain([transaction, 'optionalAccess', _5 => _5.finish, 'call', _6 => _6()]);
return res;
};
}
function makeWrappedCreateRequestHandler(
origCreateRequestHandler,
) {
return function ( build, mode) {
var routes = {};
var wrappedEntry = { ...build.entry, module: { ...build.entry.module } };
fill(wrappedEntry.module, 'default', makeWrappedDocumentRequestFunction);
for (const [id, route] of Object.entries(build.routes)) {
var wrappedRoute = { ...route, module: { ...route.module } };
if (wrappedRoute.module.action) {
fill(wrappedRoute.module, 'action', makeWrappedAction);
}
if (wrappedRoute.module.loader) {
fill(wrappedRoute.module, 'loader', makeWrappedLoader);
}
routes[id] = wrappedRoute;
}
var requestHandler = origCreateRequestHandler.call(this, { ...build, routes, entry: wrappedEntry }, mode);
return wrapRequestHandler(requestHandler);
};
}
/**
* Monkey-patch Remix's `createRequestHandler` from `@remix-run/server-runtime`
* which Remix Adapters (https://remix.run/docs/en/v1/api/remix) use underneath.
*/
function instrumentServer() {
var pkg = loadModule('@remix-run/server-runtime');
if (!pkg) {
(typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && logger.warn('Remix SDK was unable to require `@remix-run/server-runtime` package.');
return;
}
fill(pkg, 'createRequestHandler', makeWrappedCreateRequestHandler);
}
export { instrumentServer };
//# sourceMappingURL=instrumentServer.js.map