UNPKG

@sentry/node

Version:

Sentry Node SDK using OpenTelemetry for performance instrumentation

304 lines (252 loc) 9.66 kB
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); const api = require('@opentelemetry/api'); const core = require('@opentelemetry/core'); const instrumentation = require('@opentelemetry/instrumentation'); const semanticConventions = require('@opentelemetry/semantic-conventions'); const core$1 = require('@sentry/core'); const AttributeNames = require('./enums/AttributeNames.js'); const utils = require('./utils.js'); // Vendored from: https://github.com/open-telemetry/opentelemetry-js-contrib/blob/407f61591ba69a39a6908264379d4d98a48dbec4/plugins/node/opentelemetry-instrumentation-fastify/src/instrumentation.ts /* eslint-disable @typescript-eslint/no-this-alias */ /* eslint-disable jsdoc/require-jsdoc */ /* eslint-disable @typescript-eslint/explicit-function-return-type */ /* eslint-disable @typescript-eslint/no-unsafe-member-access */ /** @knipignore */ const PACKAGE_VERSION = '0.1.0'; const PACKAGE_NAME = '@sentry/instrumentation-fastify-v3'; const ANONYMOUS_NAME = 'anonymous'; // The instrumentation creates a span for invocations of lifecycle hook handlers // that take `(request, reply, ...[, done])` arguments. Currently this is all // lifecycle hooks except `onRequestAbort`. // https://fastify.dev/docs/latest/Reference/Hooks const hooksNamesToWrap = new Set([ 'onTimeout', 'onRequest', 'preParsing', 'preValidation', 'preSerialization', 'preHandler', 'onSend', 'onResponse', 'onError', ]); /** * Fastify instrumentation for OpenTelemetry */ class FastifyInstrumentationV3 extends instrumentation.InstrumentationBase { constructor(config = {}) { super(PACKAGE_NAME, PACKAGE_VERSION, config); } init() { return [ new instrumentation.InstrumentationNodeModuleDefinition('fastify', ['>=3.0.0 <4'], moduleExports => { return this._patchConstructor(moduleExports); }), ]; } _hookOnRequest() { const instrumentation = this; return function onRequest(request, reply, done) { if (!instrumentation.isEnabled()) { return done(); } instrumentation._wrap(reply, 'send', instrumentation._patchSend()); const anyRequest = request ; const rpcMetadata = core.getRPCMetadata(api.context.active()); const routeName = anyRequest.routeOptions ? anyRequest.routeOptions.url // since fastify@4.10.0 : request.routerPath; if (routeName && rpcMetadata?.type === core.RPCType.HTTP) { rpcMetadata.route = routeName; } const method = request.method || 'GET'; core$1.getIsolationScope().setTransactionName(`${method} ${routeName}`); done(); }; } _wrapHandler( pluginName, hookName, original, syncFunctionWithDone, ) { const instrumentation = this; this._diag.debug('Patching fastify route.handler function'); return function ( ...args) { if (!instrumentation.isEnabled()) { return original.apply(this, args); } const name = original.name || pluginName || ANONYMOUS_NAME; const spanName = `${AttributeNames.FastifyNames.MIDDLEWARE} - ${name}`; const reply = args[1] ; const span = utils.startSpan(reply, instrumentation.tracer, spanName, { [AttributeNames.AttributeNames.FASTIFY_TYPE]: AttributeNames.FastifyTypes.MIDDLEWARE, [AttributeNames.AttributeNames.PLUGIN_NAME]: pluginName, [AttributeNames.AttributeNames.HOOK_NAME]: hookName, }); const origDone = syncFunctionWithDone && (args[args.length - 1] ); if (origDone) { args[args.length - 1] = function (...doneArgs) { utils.endSpan(reply); origDone.apply(this, doneArgs); }; } return api.context.with(api.trace.setSpan(api.context.active(), span), () => { return utils.safeExecuteInTheMiddleMaybePromise( () => { return original.apply(this, args); }, err => { if (err instanceof Error) { span.setStatus({ code: api.SpanStatusCode.ERROR, message: err.message, }); span.recordException(err); } // async hooks should end the span as soon as the promise is resolved if (!syncFunctionWithDone) { utils.endSpan(reply); } }, ); }); }; } _wrapAddHook() { const instrumentation = this; this._diag.debug('Patching fastify server.addHook function'); // biome-ignore lint/complexity/useArrowFunction: <explanation> return function (original) { return function wrappedAddHook( ...args) { const name = args[0] ; const handler = args[1] ; const pluginName = this.pluginName; if (!hooksNamesToWrap.has(name)) { return original.apply(this, args); } const syncFunctionWithDone = typeof args[args.length - 1] === 'function' && handler.constructor.name !== 'AsyncFunction'; return original.apply(this, [ name, instrumentation._wrapHandler(pluginName, name, handler, syncFunctionWithDone), ] ); }; }; } _patchConstructor(moduleExports ) { const instrumentation = this; function fastify( ...args) { const app = moduleExports.fastify.apply(this, args); app.addHook('onRequest', instrumentation._hookOnRequest()); app.addHook('preHandler', instrumentation._hookPreHandler()); instrumentClient(); instrumentation._wrap(app, 'addHook', instrumentation._wrapAddHook()); return app; } if (moduleExports.errorCodes !== undefined) { fastify.errorCodes = moduleExports.errorCodes; } fastify.fastify = fastify; fastify.default = fastify; return fastify; } _patchSend() { const instrumentation$1 = this; this._diag.debug('Patching fastify reply.send function'); return function patchSend(original) { return function send( ...args) { const maybeError = args[0]; if (!instrumentation$1.isEnabled()) { return original.apply(this, args); } return instrumentation.safeExecuteInTheMiddle( () => { return original.apply(this, args); }, err => { if (!err && maybeError instanceof Error) { // eslint-disable-next-line no-param-reassign err = maybeError; } utils.endSpan(this, err); }, ); }; }; } _hookPreHandler() { const instrumentation$1 = this; this._diag.debug('Patching fastify preHandler function'); return function preHandler( request, reply, done) { if (!instrumentation$1.isEnabled()) { return done(); } const anyRequest = request ; const handler = anyRequest.routeOptions?.handler || anyRequest.context?.handler; const handlerName = handler?.name.startsWith('bound ') ? handler.name.substring(6) : handler?.name; const spanName = `${AttributeNames.FastifyNames.REQUEST_HANDLER} - ${handlerName || this.pluginName || ANONYMOUS_NAME}`; const spanAttributes = { [AttributeNames.AttributeNames.PLUGIN_NAME]: this.pluginName, [AttributeNames.AttributeNames.FASTIFY_TYPE]: AttributeNames.FastifyTypes.REQUEST_HANDLER, // eslint-disable-next-line deprecation/deprecation [semanticConventions.SEMATTRS_HTTP_ROUTE]: anyRequest.routeOptions ? anyRequest.routeOptions.url // since fastify@4.10.0 : request.routerPath, }; if (handlerName) { spanAttributes[AttributeNames.AttributeNames.FASTIFY_NAME] = handlerName; } const span = utils.startSpan(reply, instrumentation$1.tracer, spanName, spanAttributes); addFastifyV3SpanAttributes(span); const { requestHook } = instrumentation$1.getConfig(); if (requestHook) { instrumentation.safeExecuteInTheMiddle( () => requestHook(span, { request }), e => { if (e) { instrumentation$1._diag.error('request hook failed', e); } }, true, ); } return api.context.with(api.trace.setSpan(api.context.active(), span), () => { done(); }); }; } } function instrumentClient() { const client = core$1.getClient(); if (client) { client.on('spanStart', (span) => { addFastifyV3SpanAttributes(span); }); } } function addFastifyV3SpanAttributes(span) { const attributes = core$1.spanToJSON(span).data; // this is one of: middleware, request_handler const type = attributes['fastify.type']; // If this is already set, or we have no fastify span, no need to process again... if (attributes[core$1.SEMANTIC_ATTRIBUTE_SENTRY_OP] || !type) { return; } span.setAttributes({ [core$1.SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.http.otel.fastify', [core$1.SEMANTIC_ATTRIBUTE_SENTRY_OP]: `${type}.fastify`, }); // Also update the name, we don't need to "middleware - " prefix const name = attributes['fastify.name'] || attributes['plugin.name'] || attributes['hook.name']; if (typeof name === 'string') { // Try removing `fastify -> ` and `@fastify/otel -> ` prefixes // This is a bit of a hack, and not always working for all spans // But it's the best we can do without a proper API const updatedName = name.replace(/^fastify -> /, '').replace(/^@fastify\/otel -> /, ''); span.updateName(updatedName); } } exports.FastifyInstrumentationV3 = FastifyInstrumentationV3; //# sourceMappingURL=instrumentation.js.map