UNPKG

@sentry/node

Version:

Sentry Node SDK using OpenTelemetry for performance instrumentation

276 lines (272 loc) 11.9 kB
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); const api = require('@opentelemetry/api'); const core$1 = require('@opentelemetry/core'); const instrumentation = require('@opentelemetry/instrumentation'); const core = require('@sentry/core'); const internalTypes = require('./internal-types.js'); const utils = require('./utils.js'); const PACKAGE_NAME = "@sentry/instrumentation-hapi"; class HapiInstrumentation extends instrumentation.InstrumentationBase { constructor(config = {}) { super(PACKAGE_NAME, core.SDK_VERSION, config); this._semconvStability = instrumentation.semconvStabilityFromStr("http", process.env.OTEL_SEMCONV_STABILITY_OPT_IN); } init() { return new instrumentation.InstrumentationNodeModuleDefinition( internalTypes.HapiComponentName, [">=17.0.0 <22"], (module) => { const moduleExports = module[Symbol.toStringTag] === "Module" ? module.default : module; if (!instrumentation.isWrapped(moduleExports.server)) { this._wrap(moduleExports, "server", this._getServerPatch.bind(this)); } if (!instrumentation.isWrapped(moduleExports.Server)) { this._wrap(moduleExports, "Server", this._getServerPatch.bind(this)); } return moduleExports; }, (module) => { const moduleExports = module[Symbol.toStringTag] === "Module" ? module.default : module; this._massUnwrap([moduleExports], ["server", "Server"]); } ); } /** * Patches the Hapi.server and Hapi.Server functions in order to instrument * the server.route, server.ext, and server.register functions via calls to the * @function _getServerRoutePatch, @function _getServerExtPatch, and * @function _getServerRegisterPatch functions * @param original - the original Hapi Server creation function */ _getServerPatch(original) { const instrumentation = this; const self = this; return function server(opts) { const newServer = original.apply(this, [opts]); self._wrap(newServer, "route", (originalRouter) => { return instrumentation._getServerRoutePatch.bind(instrumentation)(originalRouter); }); self._wrap(newServer, "ext", (originalExtHandler) => { return instrumentation._getServerExtPatch.bind(instrumentation)(originalExtHandler); }); self._wrap(newServer, "register", instrumentation._getServerRegisterPatch.bind(instrumentation)); return newServer; }; } /** * Patches the plugin register function used by the Hapi Server. This function * goes through each plugin that is being registered and adds instrumentation * via a call to the @function _wrapRegisterHandler function. * @param {RegisterFunction<T>} original - the original register function which * registers each plugin on the server */ _getServerRegisterPatch(original) { const instrumentation = this; return function register(pluginInput, options) { if (Array.isArray(pluginInput)) { for (const pluginObj of pluginInput) { const plugin = utils.getPluginFromInput(pluginObj); instrumentation._wrapRegisterHandler(plugin); } } else { const plugin = utils.getPluginFromInput(pluginInput); instrumentation._wrapRegisterHandler(plugin); } return original.apply(this, [pluginInput, options]); }; } /** * Patches the Server.ext function which adds extension methods to the specified * point along the request lifecycle. This function accepts the full range of * accepted input into the standard Hapi `server.ext` function. For each extension, * it adds instrumentation to the handler via a call to the @function _wrapExtMethods * function. * @param original - the original ext function which adds the extension method to the server * @param {string} [pluginName] - if present, represents the name of the plugin responsible * for adding this server extension. Else, signifies that the extension was added directly */ _getServerExtPatch(original, pluginName) { const instrumentation = this; return function ext(...args) { if (Array.isArray(args[0])) { const eventsList = args[0]; for (let i = 0; i < eventsList.length; i++) { const eventObj = eventsList[i]; if (utils.isLifecycleExtType(eventObj.type)) { const lifecycleEventObj = eventObj; const handler = instrumentation._wrapExtMethods(lifecycleEventObj.method, eventObj.type, pluginName); lifecycleEventObj.method = handler; eventsList[i] = lifecycleEventObj; } } return original.apply(this, args); } else if (utils.isDirectExtInput(args)) { const extInput = args; const method = extInput[1]; const handler = instrumentation._wrapExtMethods(method, extInput[0], pluginName); return original.apply(this, [extInput[0], handler, extInput[2]]); } else if (utils.isLifecycleExtEventObj(args[0])) { const lifecycleEventObj = args[0]; const handler = instrumentation._wrapExtMethods(lifecycleEventObj.method, lifecycleEventObj.type, pluginName); lifecycleEventObj.method = handler; return original.call(this, lifecycleEventObj); } return original.apply(this, args); }; } /** * Patches the Server.route function. This function accepts either one or an array * of Hapi.ServerRoute objects and adds instrumentation on each route via a call to * the @function _wrapRouteHandler function. * @param {HapiServerRouteInputMethod} original - the original route function which adds * the route to the server * @param {string} [pluginName] - if present, represents the name of the plugin responsible * for adding this server route. Else, signifies that the route was added directly */ _getServerRoutePatch(original, pluginName) { const instrumentation = this; return function route(route) { if (Array.isArray(route)) { for (let i = 0; i < route.length; i++) { const newRoute = instrumentation._wrapRouteHandler.call(instrumentation, route[i], pluginName); route[i] = newRoute; } } else { route = instrumentation._wrapRouteHandler.call(instrumentation, route, pluginName); } return original.apply(this, [route]); }; } /** * Wraps newly registered plugins to add instrumentation to the plugin's clone of * the original server. Specifically, wraps the server.route and server.ext functions * via calls to @function _getServerRoutePatch and @function _getServerExtPatch * @param {Hapi.Plugin<T>} plugin - the new plugin which is being instrumented */ _wrapRegisterHandler(plugin) { const instrumentation = this; const pluginName = utils.getPluginName(plugin); const oldRegister = plugin.register; const self = this; const newRegisterHandler = function(server, options) { self._wrap(server, "route", (original) => { return instrumentation._getServerRoutePatch.bind(instrumentation)(original, pluginName); }); self._wrap(server, "ext", (originalExtHandler) => { return instrumentation._getServerExtPatch.bind(instrumentation)(originalExtHandler, pluginName); }); return oldRegister.call(this, server, options); }; plugin.register = newRegisterHandler; } /** * Wraps request extension methods to add instrumentation to each new extension handler. * Patches each individual extension in order to create the * span and propagate context. It does not create spans when there is no parent span. * @param {PatchableExtMethod | PatchableExtMethod[]} method - the request extension * handler which is being instrumented * @param {Hapi.ServerRequestExtType} extPoint - the point in the Hapi request lifecycle * which this extension targets * @param {string} [pluginName] - if present, represents the name of the plugin responsible * for adding this server route. Else, signifies that the route was added directly */ _wrapExtMethods(method, extPoint, pluginName) { const instrumentation = this; if (method instanceof Array) { for (let i = 0; i < method.length; i++) { method[i] = instrumentation._wrapExtMethods(method[i], extPoint); } return method; } else if (utils.isPatchableExtMethod(method)) { if (method[internalTypes.handlerPatched] === true) return method; method[internalTypes.handlerPatched] = true; const newHandler = async function(...params) { if (api.trace.getSpan(api.context.active()) === void 0) { return await method.apply(this, params); } const metadata = utils.getExtMetadata(extPoint, pluginName, method.name); const span = instrumentation.tracer.startSpan(metadata.name, { attributes: metadata.attributes }); try { return await api.context.with( api.trace.setSpan(api.context.active(), span), method, void 0, ...params ); } catch (err) { span.recordException(err); span.setStatus({ code: api.SpanStatusCode.ERROR, message: err.message }); throw err; } finally { span.end(); } }; return newHandler; } return method; } /** * Patches each individual route handler method in order to create the * span and propagate context. It does not create spans when there is no parent span. * @param {PatchableServerRoute} route - the route handler which is being instrumented * @param {string} [pluginName] - if present, represents the name of the plugin responsible * for adding this server route. Else, signifies that the route was added directly */ _wrapRouteHandler(route, pluginName) { const instrumentation = this; if (route[internalTypes.handlerPatched] === true) return route; route[internalTypes.handlerPatched] = true; const wrapHandler = (oldHandler) => { return async function(...params) { if (api.trace.getSpan(api.context.active()) === void 0) { return await oldHandler.call(this, ...params); } const rpcMetadata = core$1.getRPCMetadata(api.context.active()); if (rpcMetadata?.type === core$1.RPCType.HTTP) { rpcMetadata.route = route.path; } const metadata = utils.getRouteMetadata(route, instrumentation._semconvStability, pluginName); const span = instrumentation.tracer.startSpan(metadata.name, { attributes: metadata.attributes }); try { return await api.context.with( api.trace.setSpan(api.context.active(), span), () => oldHandler.call(this, ...params) ); } catch (err) { span.recordException(err); span.setStatus({ code: api.SpanStatusCode.ERROR, message: err.message }); throw err; } finally { span.end(); } }; }; if (typeof route.handler === "function") { route.handler = wrapHandler(route.handler); } else if (typeof route.options === "function") { const oldOptions = route.options; route.options = function(server) { const options = oldOptions(server); if (typeof options.handler === "function") { options.handler = wrapHandler(options.handler); } return options; }; } else if (typeof route.options?.handler === "function") { route.options.handler = wrapHandler(route.options.handler); } return route; } } exports.HapiInstrumentation = HapiInstrumentation; //# sourceMappingURL=instrumentation.js.map