UNPKG

@sentry/remix

Version:
327 lines (279 loc) 11 kB
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); const opentelemetry = require('@opentelemetry/api'); const instrumentation = require('@opentelemetry/instrumentation'); const semanticConventions = require('@opentelemetry/semantic-conventions'); const core = require('@sentry/core'); const RemixSemanticAttributes = { MATCH_PARAMS: 'match.params', MATCH_ROUTE_ID: 'match.route.id', }; const VERSION = core.SDK_VERSION; const DEFAULT_CONFIG = { actionFormDataAttributes: { _action: 'actionType', }, }; class RemixInstrumentation extends instrumentation.InstrumentationBase { constructor(config = {}) { super('RemixInstrumentation', VERSION, Object.assign({}, DEFAULT_CONFIG, config)); } getConfig() { return this._config; } setConfig(config = {}) { this._config = Object.assign({}, DEFAULT_CONFIG, config); } // eslint-disable-next-line @typescript-eslint/naming-convention init() { const remixRunServerRuntimeRouteMatchingFile = new instrumentation.InstrumentationNodeModuleFile( '@remix-run/server-runtime/dist/routeMatching.js', ['2.x'], (moduleExports) => { // createRequestHandler if (instrumentation.isWrapped(moduleExports['matchServerRoutes'])) { this._unwrap(moduleExports, 'matchServerRoutes'); } this._wrap(moduleExports, 'matchServerRoutes', this._patchMatchServerRoutes()); return moduleExports; }, (moduleExports) => { this._unwrap(moduleExports, 'matchServerRoutes'); }, ); const remixRunServerRuntimeData_File = new instrumentation.InstrumentationNodeModuleFile( '@remix-run/server-runtime/dist/data.js', ['2.9.0 - 2.x'], (moduleExports) => { // callRouteLoader if (instrumentation.isWrapped(moduleExports['callRouteLoader'])) { this._unwrap(moduleExports, 'callRouteLoader'); } this._wrap(moduleExports, 'callRouteLoader', this._patchCallRouteLoader()); // callRouteAction if (instrumentation.isWrapped(moduleExports['callRouteAction'])) { this._unwrap(moduleExports, 'callRouteAction'); } this._wrap(moduleExports, 'callRouteAction', this._patchCallRouteAction()); return moduleExports; }, (moduleExports) => { this._unwrap(moduleExports, 'callRouteLoader'); this._unwrap(moduleExports, 'callRouteAction'); }, ); /* * In Remix 2.9.0, the `callXXLoaderRR` functions were renamed to `callXXLoader`. */ const remixRunServerRuntimeDataPre_2_9_File = new instrumentation.InstrumentationNodeModuleFile( '@remix-run/server-runtime/dist/data.js', ['2.0.0 - 2.8.x'], ( moduleExports , ) => { // callRouteLoader if (instrumentation.isWrapped(moduleExports['callRouteLoaderRR'])) { this._unwrap(moduleExports, 'callRouteLoaderRR'); } this._wrap(moduleExports, 'callRouteLoaderRR', this._patchCallRouteLoader()); // callRouteAction if (instrumentation.isWrapped(moduleExports['callRouteActionRR'])) { this._unwrap(moduleExports, 'callRouteActionRR'); } this._wrap(moduleExports, 'callRouteActionRR', this._patchCallRouteAction()); return moduleExports; }, ( moduleExports , ) => { this._unwrap(moduleExports, 'callRouteLoaderRR'); this._unwrap(moduleExports, 'callRouteActionRR'); }, ); const remixRunServerRuntimeModule = new instrumentation.InstrumentationNodeModuleDefinition( '@remix-run/server-runtime', ['2.x'], (moduleExports) => { // createRequestHandler if (instrumentation.isWrapped(moduleExports['createRequestHandler'])) { this._unwrap(moduleExports, 'createRequestHandler'); } this._wrap(moduleExports, 'createRequestHandler', this._patchCreateRequestHandler()); return moduleExports; }, (moduleExports) => { this._unwrap(moduleExports, 'createRequestHandler'); }, [remixRunServerRuntimeRouteMatchingFile, remixRunServerRuntimeData_File, remixRunServerRuntimeDataPre_2_9_File], ); return remixRunServerRuntimeModule; } _patchMatchServerRoutes() { return function matchServerRoutes(original) { return function patchMatchServerRoutes( ...args ) { const result = original.apply(this, args) ; const span = opentelemetry.default.trace.getSpan(opentelemetry.default.context.active()); const route = (result || []).slice(-1)[0]?.route; const routePath = route?.path; if (span && routePath) { span.setAttribute(semanticConventions.SemanticAttributes.HTTP_ROUTE, routePath); span.updateName(`remix.request ${routePath}`); } const routeId = route?.id; if (span && routeId) { span.setAttribute(RemixSemanticAttributes.MATCH_ROUTE_ID, routeId); } return result; }; }; } _patchCreateRequestHandler() { // eslint-disable-next-line @typescript-eslint/no-this-alias const plugin = this; return function createRequestHandler(original) { return function patchCreateRequestHandler( ...args ) { const originalRequestHandler = original.apply(this, args); return (request, loadContext) => { const span = plugin.tracer.startSpan( 'remix.request', { attributes: { [semanticConventions.SemanticAttributes.CODE_FUNCTION]: 'requestHandler' }, }, opentelemetry.default.context.active(), ); addRequestAttributesToSpan(span, request); const originalResponsePromise = opentelemetry.default.context.with( opentelemetry.default.trace.setSpan(opentelemetry.default.context.active(), span), () => originalRequestHandler(request, loadContext), ); return originalResponsePromise .then(response => { addResponseAttributesToSpan(span, response); return response; }) .catch(error => { plugin._addErrorToSpan(span, error); throw error; }) .finally(() => { span.end(); }); }; }; }; } _patchCallRouteLoader() { // eslint-disable-next-line @typescript-eslint/no-this-alias const plugin = this; return function callRouteLoader(original) { return function patchCallRouteLoader( ...args) { const [params] = args; const span = plugin.tracer.startSpan( `LOADER ${params.routeId}`, { attributes: { [semanticConventions.SemanticAttributes.CODE_FUNCTION]: 'loader' } }, opentelemetry.default.context.active(), ); addRequestAttributesToSpan(span, params.request); addMatchAttributesToSpan(span, { routeId: params.routeId, params: params.params }); return opentelemetry.default.context.with(opentelemetry.default.trace.setSpan(opentelemetry.default.context.active(), span), () => { const originalResponsePromise = original.apply(this, args); return originalResponsePromise .then(response => { addResponseAttributesToSpan(span, response); return response; }) .catch(error => { plugin._addErrorToSpan(span, error); throw error; }) .finally(() => { span.end(); }); }); }; }; } _patchCallRouteAction() { // eslint-disable-next-line @typescript-eslint/no-this-alias const plugin = this; return function callRouteAction(original) { return async function patchCallRouteAction( ...args) { const [params] = args; const clonedRequest = params.request.clone(); const span = plugin.tracer.startSpan( `ACTION ${params.routeId}`, { attributes: { [semanticConventions.SemanticAttributes.CODE_FUNCTION]: 'action' } }, opentelemetry.default.context.active(), ); addRequestAttributesToSpan(span, clonedRequest); addMatchAttributesToSpan(span, { routeId: params.routeId, params: params.params }); return opentelemetry.default.context.with( opentelemetry.default.trace.setSpan(opentelemetry.default.context.active(), span), async () => { const originalResponsePromise = original.apply(this, args); return originalResponsePromise .then(async response => { addResponseAttributesToSpan(span, response); try { const formData = await clonedRequest.formData(); const { actionFormDataAttributes: actionFormAttributes } = plugin.getConfig(); formData.forEach((value, key) => { if (actionFormAttributes?.[key] && typeof value === 'string') { const keyName = actionFormAttributes[key] === true ? key : actionFormAttributes[key]; span.setAttribute(`formData.${keyName}`, value.toString()); } }); } catch { // Silently continue on any error. Typically happens because the action body cannot be processed // into FormData, in which case we should just continue. } return response; }) .catch(async error => { plugin._addErrorToSpan(span, error); throw error; }) .finally(() => { span.end(); }); }, ); }; }; } _addErrorToSpan(span, error) { addErrorEventToSpan(span, error); } } const addRequestAttributesToSpan = (span, request) => { span.setAttributes({ [semanticConventions.SemanticAttributes.HTTP_METHOD]: request.method, [semanticConventions.SemanticAttributes.HTTP_URL]: request.url, }); }; const addMatchAttributesToSpan = (span, match) => { span.setAttributes({ [RemixSemanticAttributes.MATCH_ROUTE_ID]: match.routeId, }); Object.keys(match.params).forEach(paramName => { span.setAttribute(`${RemixSemanticAttributes.MATCH_PARAMS}.${paramName}`, match.params[paramName] || '(undefined)'); }); }; const addResponseAttributesToSpan = (span, response) => { if (response) { span.setAttributes({ [semanticConventions.SemanticAttributes.HTTP_STATUS_CODE]: response.status, }); } }; const addErrorEventToSpan = (span, error) => { span.recordException(error); span.setStatus({ code: opentelemetry.SpanStatusCode.ERROR, message: error.message }); }; exports.RemixInstrumentation = RemixInstrumentation; //# sourceMappingURL=instrumentation.js.map