From b9769a1b5671f65fb8fd11d7325a17491d2cfa9a Mon Sep 17 00:00:00 2001 From: Nicolas Hrubec Date: Wed, 20 May 2026 12:05:11 +0200 Subject: [PATCH] ref(node): Vendor hapi instrumentation Co-Authored-By: Claude Opus 4.6 (1M context) --- .oxlintrc.base.json | 3 +- packages/node/package.json | 1 - .../src/integrations/tracing/hapi/index.ts | 2 +- .../hapi/vendored/enums/AttributeNames.ts | 26 ++ .../tracing/hapi/vendored/hapi-types.ts | 86 +++++ .../tracing/hapi/vendored/instrumentation.ts | 353 ++++++++++++++++++ .../tracing/hapi/vendored/internal-types.ts | 73 ++++ .../tracing/hapi/vendored/semconv.ts | 33 ++ .../tracing/hapi/vendored/utils.ts | 147 ++++++++ yarn.lock | 11 +- 10 files changed, 722 insertions(+), 13 deletions(-) create mode 100644 packages/node/src/integrations/tracing/hapi/vendored/enums/AttributeNames.ts create mode 100644 packages/node/src/integrations/tracing/hapi/vendored/hapi-types.ts create mode 100644 packages/node/src/integrations/tracing/hapi/vendored/instrumentation.ts create mode 100644 packages/node/src/integrations/tracing/hapi/vendored/internal-types.ts create mode 100644 packages/node/src/integrations/tracing/hapi/vendored/semconv.ts create mode 100644 packages/node/src/integrations/tracing/hapi/vendored/utils.ts diff --git a/.oxlintrc.base.json b/.oxlintrc.base.json index d28c7a2746d7..b7acab53b5b8 100644 --- a/.oxlintrc.base.json +++ b/.oxlintrc.base.json @@ -153,7 +153,8 @@ "**/integration/aws/vendored/**/*.ts", "**/nestjs/src/integrations/vendored/**/*.ts", "**/integrations/tracing/kafka/vendored/**/*.ts", - "**/integrations/tracing/tedious/vendored/**/*.ts" + "**/integrations/tracing/tedious/vendored/**/*.ts", + "**/integrations/tracing/hapi/vendored/**/*.ts" ], "rules": { "typescript/no-explicit-any": "off" diff --git a/packages/node/package.json b/packages/node/package.json index ef3d88644336..a2159cb585ea 100644 --- a/packages/node/package.json +++ b/packages/node/package.json @@ -70,7 +70,6 @@ "@opentelemetry/instrumentation": "^0.214.0", "@opentelemetry/instrumentation-amqplib": "0.61.0", "@opentelemetry/instrumentation-graphql": "0.62.0", - "@opentelemetry/instrumentation-hapi": "0.60.0", "@opentelemetry/instrumentation-http": "0.214.0", "@opentelemetry/instrumentation-mongoose": "0.60.0", "@opentelemetry/sql-common": "^0.41.2", diff --git a/packages/node/src/integrations/tracing/hapi/index.ts b/packages/node/src/integrations/tracing/hapi/index.ts index 876f130c65b4..ae3b870d5128 100644 --- a/packages/node/src/integrations/tracing/hapi/index.ts +++ b/packages/node/src/integrations/tracing/hapi/index.ts @@ -1,4 +1,4 @@ -import { HapiInstrumentation } from '@opentelemetry/instrumentation-hapi'; +import { HapiInstrumentation } from './vendored/instrumentation'; import type { IntegrationFn, Span } from '@sentry/core'; import { captureException, diff --git a/packages/node/src/integrations/tracing/hapi/vendored/enums/AttributeNames.ts b/packages/node/src/integrations/tracing/hapi/vendored/enums/AttributeNames.ts new file mode 100644 index 000000000000..d29cb8967ed6 --- /dev/null +++ b/packages/node/src/integrations/tracing/hapi/vendored/enums/AttributeNames.ts @@ -0,0 +1,26 @@ +/* + * Copyright The OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * NOTICE from the Sentry authors: + * - Vendored from: https://github.com/open-telemetry/opentelemetry-js-contrib/tree/15ef7506553f631ea4181391e0c5725a56f0d082/packages/instrumentation-hapi + * - Upstream version: @opentelemetry/instrumentation-hapi@0.64.0 + */ +/* eslint-disable */ + +export enum AttributeNames { + HAPI_TYPE = 'hapi.type', + PLUGIN_NAME = 'hapi.plugin.name', + EXT_TYPE = 'server.ext.type', +} diff --git a/packages/node/src/integrations/tracing/hapi/vendored/hapi-types.ts b/packages/node/src/integrations/tracing/hapi/vendored/hapi-types.ts new file mode 100644 index 000000000000..1c1419285fd0 --- /dev/null +++ b/packages/node/src/integrations/tracing/hapi/vendored/hapi-types.ts @@ -0,0 +1,86 @@ +/* + * Simplified type definitions vendored from @types/hapi__hapi. + * Only includes the types actually accessed by the instrumentation. + */ +/* eslint-disable */ + +export type ServerOptions = Record; + +export declare function server(options?: ServerOptions): Server; +export declare function Server(options?: ServerOptions): Server; + +export type ServerRequestExtType = + | 'onPreAuth' + | 'onCredentials' + | 'onPostAuth' + | 'onPreHandler' + | 'onPostHandler' + | 'onPreResponse' + | 'onRequest'; + +export namespace Lifecycle { + export type Method = (request: any, h: any, err?: Error) => ReturnValue; + export type ReturnValue = any; + export type FailAction = 'error' | 'log' | 'ignore' | Method; +} + +export interface ServerRoute { + path: string; + method: string; + handler?: Lifecycle.Method | T; + options?: ((server: Server) => ServerRouteOptions) | ServerRouteOptions; + [key: string]: any; +} + +interface ServerRouteOptions { + handler?: Lifecycle.Method | any; + [key: string]: any; +} + +export interface Server { + route: (...args: any[]) => any; + ext: (...args: any[]) => any; + register: (...args: any[]) => any; + [key: string]: any; +} + +export interface Plugin { + register: (server: Server, options: T) => void | Promise; + name?: string; + pkg?: { name: string; [key: string]: any }; + [key: string]: any; +} + +export interface PluginNameVersion { + name: string; + [key: string]: any; +} + +export interface PluginPackage { + pkg: { name: string; [key: string]: any }; + [key: string]: any; +} + +export interface ServerRegisterPluginObject { + plugin: Plugin | { plugin: Plugin; [key: string]: any }; + [key: string]: any; +} + +export interface ServerRegisterOptions { + [key: string]: any; +} + +export interface ServerExtEventsObject { + type: string; + [key: string]: any; +} + +export interface ServerExtEventsRequestObject { + type: ServerRequestExtType; + method: Lifecycle.Method; + [key: string]: any; +} + +export interface ServerExtOptions { + [key: string]: any; +} diff --git a/packages/node/src/integrations/tracing/hapi/vendored/instrumentation.ts b/packages/node/src/integrations/tracing/hapi/vendored/instrumentation.ts new file mode 100644 index 000000000000..0fe2c0f14756 --- /dev/null +++ b/packages/node/src/integrations/tracing/hapi/vendored/instrumentation.ts @@ -0,0 +1,353 @@ +/* + * Copyright The OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * NOTICE from the Sentry authors: + * - Vendored from: https://github.com/open-telemetry/opentelemetry-js-contrib/tree/15ef7506553f631ea4181391e0c5725a56f0d082/packages/instrumentation-hapi + * - Upstream version: @opentelemetry/instrumentation-hapi@0.64.0 + * - Types vendored from @hapi/hapi as simplified interfaces + * - Minor TypeScript strictness adjustments for this repository's compiler settings + */ +/* eslint-disable */ + +import * as api from '@opentelemetry/api'; +import { getRPCMetadata, RPCType } from '@opentelemetry/core'; +import { + InstrumentationBase, + InstrumentationConfig, + InstrumentationNodeModuleDefinition, + isWrapped, + SemconvStability, + semconvStabilityFromStr, +} from '@opentelemetry/instrumentation'; + +import type * as Hapi from './hapi-types'; +import { SDK_VERSION } from '@sentry/core'; +import { + HapiComponentName, + HapiServerRouteInput, + handlerPatched, + PatchableServerRoute, + HapiServerRouteInputMethod, + HapiPluginInput, + RegisterFunction, + PatchableExtMethod, + ServerExtDirectInput, +} from './internal-types'; +import { + getRouteMetadata, + getPluginName, + isLifecycleExtType, + isLifecycleExtEventObj, + getExtMetadata, + isDirectExtInput, + isPatchableExtMethod, + getPluginFromInput, +} from './utils'; + +const PACKAGE_NAME = '@sentry/instrumentation-hapi'; + +/** Hapi instrumentation for OpenTelemetry */ +export class HapiInstrumentation extends InstrumentationBase { + private _semconvStability: SemconvStability; + + constructor(config: InstrumentationConfig = {}) { + super(PACKAGE_NAME, SDK_VERSION, config); + this._semconvStability = semconvStabilityFromStr('http', process.env.OTEL_SEMCONV_STABILITY_OPT_IN); + } + + protected init() { + return new InstrumentationNodeModuleDefinition( + HapiComponentName, + ['>=17.0.0 <22'], + (module: any) => { + const moduleExports: typeof Hapi = module[Symbol.toStringTag] === 'Module' ? module.default : module; + if (!isWrapped(moduleExports.server)) { + this._wrap(moduleExports, 'server', this._getServerPatch.bind(this) as any); + } + + if (!isWrapped(moduleExports.Server)) { + this._wrap(moduleExports, 'Server', this._getServerPatch.bind(this) as any); + } + return moduleExports; + }, + (module: any) => { + const moduleExports: typeof Hapi = 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 + */ + private _getServerPatch(original: (options?: Hapi.ServerOptions) => Hapi.Server) { + const instrumentation: HapiInstrumentation = this; + const self = this; + return function server(this: Hapi.Server, opts?: Hapi.ServerOptions) { + const newServer: Hapi.Server = original.apply(this, [opts]); + + self._wrap(newServer, 'route', originalRouter => { + return instrumentation._getServerRoutePatch.bind(instrumentation)(originalRouter); + }); + + // Casting as any is necessary here due to multiple overloads on the Hapi.ext + // function, which requires supporting a variety of different parameters + // as extension inputs + self._wrap(newServer, 'ext', originalExtHandler => { + return instrumentation._getServerExtPatch.bind(instrumentation)(originalExtHandler as any); + }); + + // Casting as any is necessary here due to multiple overloads on the Hapi.Server.register + // function, which requires supporting a variety of different types of Plugin inputs + self._wrap(newServer, 'register', instrumentation._getServerRegisterPatch.bind(instrumentation) as any); + 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} original - the original register function which + * registers each plugin on the server + */ + private _getServerRegisterPatch(original: RegisterFunction): RegisterFunction { + const instrumentation: HapiInstrumentation = this; + return function register(this: Hapi.Server, pluginInput: HapiPluginInput, options?: Hapi.ServerRegisterOptions) { + if (Array.isArray(pluginInput)) { + for (const pluginObj of pluginInput) { + const plugin = getPluginFromInput(pluginObj); + instrumentation._wrapRegisterHandler(plugin); + } + } else { + const plugin = 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 + */ + private _getServerExtPatch(original: (...args: unknown[]) => unknown, pluginName?: string) { + const instrumentation: HapiInstrumentation = this; + + return function ext(this: ThisParameterType, ...args: Parameters) { + if (Array.isArray(args[0])) { + const eventsList: Hapi.ServerExtEventsObject[] | Hapi.ServerExtEventsRequestObject[] = args[0]; + for (let i = 0; i < eventsList.length; i++) { + const eventObj = eventsList[i]!; + if (isLifecycleExtType(eventObj.type)) { + const lifecycleEventObj = eventObj as Hapi.ServerExtEventsRequestObject; + const handler = instrumentation._wrapExtMethods(lifecycleEventObj.method, eventObj.type, pluginName); + lifecycleEventObj.method = handler; + eventsList[i] = lifecycleEventObj; + } + } + return original.apply(this, args); + } else if (isDirectExtInput(args)) { + const extInput: ServerExtDirectInput = args; + const method: PatchableExtMethod = extInput[1]; + const handler = instrumentation._wrapExtMethods(method, extInput[0], pluginName); + return original.apply(this, [extInput[0], handler, extInput[2]]); + } else if (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 + */ + private _getServerRoutePatch(original: HapiServerRouteInputMethod, pluginName?: string) { + const instrumentation: HapiInstrumentation = this; + return function route(this: Hapi.Server, route: HapiServerRouteInput): void { + 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} plugin - the new plugin which is being instrumented + */ + private _wrapRegisterHandler(plugin: Hapi.Plugin): void { + const instrumentation: HapiInstrumentation = this; + const pluginName = getPluginName(plugin); + const oldRegister = plugin.register; + const self = this; + const newRegisterHandler = function (this: typeof plugin, server: Hapi.Server, options: T) { + self._wrap(server, 'route', original => { + return instrumentation._getServerRoutePatch.bind(instrumentation)(original, pluginName); + }); + + // Casting as any is necessary here due to multiple overloads on the Hapi.ext + // function, which requires supporting a variety of different parameters + // as extension inputs + self._wrap(server, 'ext', originalExtHandler => { + return instrumentation._getServerExtPatch.bind(instrumentation)(originalExtHandler as any, 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 + */ + private _wrapExtMethods( + method: T, + extPoint: Hapi.ServerRequestExtType, + pluginName?: string, + ): T { + const instrumentation: HapiInstrumentation = this; + if (method instanceof Array) { + for (let i = 0; i < method.length; i++) { + method[i] = instrumentation._wrapExtMethods(method[i]!, extPoint) as PatchableExtMethod; + } + return method; + } else if (isPatchableExtMethod(method)) { + if (method[handlerPatched] === true) return method; + method[handlerPatched] = true; + + const newHandler: PatchableExtMethod = async function (this: any, ...params: Parameters) { + if (api.trace.getSpan(api.context.active()) === undefined) { + return await method.apply(this, params); + } + const metadata = getExtMetadata(extPoint, pluginName, method.name); + const span = instrumentation.tracer.startSpan(metadata.name, { + attributes: metadata.attributes, + }); + try { + return await api.context.with, Hapi.Lifecycle.Method>( + api.trace.setSpan(api.context.active(), span), + method, + undefined, + ...params, + ); + } catch (err: any) { + span.recordException(err); + span.setStatus({ + code: api.SpanStatusCode.ERROR, + message: err.message, + }); + throw err; + } finally { + span.end(); + } + }; + return newHandler as T; + } + 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 + */ + private _wrapRouteHandler(route: PatchableServerRoute, pluginName?: string): PatchableServerRoute { + const instrumentation: HapiInstrumentation = this; + if (route[handlerPatched] === true) return route; + route[handlerPatched] = true; + + const wrapHandler: (oldHandler: Hapi.Lifecycle.Method) => Hapi.Lifecycle.Method = oldHandler => { + return async function (this: any, ...params: Parameters) { + if (api.trace.getSpan(api.context.active()) === undefined) { + return await oldHandler.call(this, ...params); + } + const rpcMetadata = getRPCMetadata(api.context.active()); + if (rpcMetadata?.type === RPCType.HTTP) { + rpcMetadata.route = route.path; + } + const metadata = 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: any) { + 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 as Hapi.Lifecycle.Method); + } 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 as Hapi.Lifecycle.Method); + } + return options; + }; + } else if (typeof route.options?.handler === 'function') { + route.options.handler = wrapHandler(route.options.handler as Hapi.Lifecycle.Method); + } + return route; + } +} diff --git a/packages/node/src/integrations/tracing/hapi/vendored/internal-types.ts b/packages/node/src/integrations/tracing/hapi/vendored/internal-types.ts new file mode 100644 index 000000000000..2d7329254be2 --- /dev/null +++ b/packages/node/src/integrations/tracing/hapi/vendored/internal-types.ts @@ -0,0 +1,73 @@ +/* + * Copyright The OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * NOTICE from the Sentry authors: + * - Vendored from: https://github.com/open-telemetry/opentelemetry-js-contrib/tree/15ef7506553f631ea4181391e0c5725a56f0d082/packages/instrumentation-hapi + * - Upstream version: @opentelemetry/instrumentation-hapi@0.64.0 + * - Types vendored from @hapi/hapi as simplified interfaces + */ +/* eslint-disable */ + +import type * as Hapi from './hapi-types'; + +export const HapiComponentName = '@hapi/hapi'; + +/** + * This symbol is used to mark a Hapi route handler or server extension handler as + * already patched, since its possible to use these handlers multiple times + * i.e. when allowing multiple versions of one plugin, or when registering a plugin + * multiple times on different servers. + */ +export const handlerPatched: unique symbol = Symbol('hapi-handler-patched'); + +export type HapiServerRouteInputMethod = (route: HapiServerRouteInput) => void; + +export type HapiServerRouteInput = PatchableServerRoute | PatchableServerRoute[]; + +export type PatchableServerRoute = Hapi.ServerRoute & { + [handlerPatched]?: boolean; +}; + +export type HapiPluginObject = Hapi.ServerRegisterPluginObject; + +export type HapiPluginInput = HapiPluginObject | Array>; + +export type RegisterFunction = (plugin: HapiPluginInput, options?: Hapi.ServerRegisterOptions) => Promise; + +export type PatchableExtMethod = Hapi.Lifecycle.Method & { + [handlerPatched]?: boolean; +}; + +export type ServerExtDirectInput = [ + Hapi.ServerRequestExtType, + Hapi.Lifecycle.Method, + (Hapi.ServerExtOptions | undefined)?, +]; + +export const HapiLayerType = { + ROUTER: 'router', + PLUGIN: 'plugin', + EXT: 'server.ext', +}; + +export const HapiLifecycleMethodNames = new Set([ + 'onPreAuth', + 'onCredentials', + 'onPostAuth', + 'onPreHandler', + 'onPostHandler', + 'onPreResponse', + 'onRequest', +]); diff --git a/packages/node/src/integrations/tracing/hapi/vendored/semconv.ts b/packages/node/src/integrations/tracing/hapi/vendored/semconv.ts new file mode 100644 index 000000000000..fbe36b62cc97 --- /dev/null +++ b/packages/node/src/integrations/tracing/hapi/vendored/semconv.ts @@ -0,0 +1,33 @@ +/* + * Copyright The OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * NOTICE from the Sentry authors: + * - Vendored from: https://github.com/open-telemetry/opentelemetry-js-contrib/tree/15ef7506553f631ea4181391e0c5725a56f0d082/packages/instrumentation-hapi + * - Upstream version: @opentelemetry/instrumentation-hapi@0.64.0 + */ +/* eslint-disable */ + +/** + * Deprecated, use `http.request.method` instead. + * + * @example GET + * @example POST + * @example HEAD + * + * @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`. + * + * @deprecated Replaced by `http.request.method`. + */ +export const ATTR_HTTP_METHOD = 'http.method' as const; diff --git a/packages/node/src/integrations/tracing/hapi/vendored/utils.ts b/packages/node/src/integrations/tracing/hapi/vendored/utils.ts new file mode 100644 index 000000000000..94d7ed613cc9 --- /dev/null +++ b/packages/node/src/integrations/tracing/hapi/vendored/utils.ts @@ -0,0 +1,147 @@ +/* + * Copyright The OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * NOTICE from the Sentry authors: + * - Vendored from: https://github.com/open-telemetry/opentelemetry-js-contrib/tree/15ef7506553f631ea4181391e0c5725a56f0d082/packages/instrumentation-hapi + * - Upstream version: @opentelemetry/instrumentation-hapi@0.64.0 + * - Types vendored from @hapi/hapi as simplified interfaces + */ +/* eslint-disable */ + +import { Attributes } from '@opentelemetry/api'; +import { ATTR_HTTP_ROUTE, ATTR_HTTP_REQUEST_METHOD } from '@opentelemetry/semantic-conventions'; +import { ATTR_HTTP_METHOD } from './semconv'; +import type * as Hapi from './hapi-types'; +import { + HapiLayerType, + HapiLifecycleMethodNames, + HapiPluginObject, + PatchableExtMethod, + ServerExtDirectInput, +} from './internal-types'; +import { AttributeNames } from './enums/AttributeNames'; +import { SemconvStability } from '@opentelemetry/instrumentation'; + +export function getPluginName(plugin: Hapi.Plugin): string { + if ((plugin as Hapi.PluginNameVersion).name) { + return (plugin as Hapi.PluginNameVersion).name; + } else { + return (plugin as Hapi.PluginPackage).pkg.name; + } +} + +export const isLifecycleExtType = (variableToCheck: unknown): variableToCheck is Hapi.ServerRequestExtType => { + return typeof variableToCheck === 'string' && HapiLifecycleMethodNames.has(variableToCheck); +}; + +export const isLifecycleExtEventObj = ( + variableToCheck: unknown, +): variableToCheck is Hapi.ServerExtEventsRequestObject => { + const event = (variableToCheck as Hapi.ServerExtEventsRequestObject)?.type; + return event !== undefined && isLifecycleExtType(event); +}; + +export const isDirectExtInput = (variableToCheck: unknown): variableToCheck is ServerExtDirectInput => { + return ( + Array.isArray(variableToCheck) && + variableToCheck.length <= 3 && + isLifecycleExtType(variableToCheck[0]) && + typeof variableToCheck[1] === 'function' + ); +}; + +export const isPatchableExtMethod = ( + variableToCheck: PatchableExtMethod | PatchableExtMethod[], +): variableToCheck is PatchableExtMethod => { + return !Array.isArray(variableToCheck); +}; + +export const getRouteMetadata = ( + route: Hapi.ServerRoute, + semconvStability: SemconvStability, + pluginName?: string, +): { + attributes: Attributes; + name: string; +} => { + const attributes: Attributes = { + [ATTR_HTTP_ROUTE]: route.path, + }; + if (semconvStability & SemconvStability.OLD) { + attributes[ATTR_HTTP_METHOD] = route.method; + } + if (semconvStability & SemconvStability.STABLE) { + // Note: This currently does *not* normalize the method name to uppercase + // and conditionally include `http.request.method.original` as described + // at https://opentelemetry.io/docs/specs/semconv/http/http-spans/ + // These attributes are for a *hapi* span, and not the parent HTTP span, + // so the HTTP span guidance doesn't strictly apply. + attributes[ATTR_HTTP_REQUEST_METHOD] = route.method; + } + + let name; + if (pluginName) { + attributes[AttributeNames.HAPI_TYPE] = HapiLayerType.PLUGIN; + attributes[AttributeNames.PLUGIN_NAME] = pluginName; + name = `${pluginName}: route - ${route.path}`; + } else { + attributes[AttributeNames.HAPI_TYPE] = HapiLayerType.ROUTER; + name = `route - ${route.path}`; + } + + return { attributes, name }; +}; + +export const getExtMetadata = ( + extPoint: Hapi.ServerRequestExtType, + pluginName?: string, + methodName?: string, +): { + attributes: Attributes; + name: string; +} => { + let baseName = `ext - ${extPoint}`; + if (methodName && methodName !== 'method') { + // method is the default name for the extension in the ServerExtEventsObject format. + baseName = `ext - ${extPoint} - ${methodName}`; + } + if (pluginName) { + return { + attributes: { + [AttributeNames.EXT_TYPE]: extPoint, + [AttributeNames.HAPI_TYPE]: HapiLayerType.EXT, + [AttributeNames.PLUGIN_NAME]: pluginName, + }, + name: `${pluginName}: ${baseName}`, + }; + } + return { + attributes: { + [AttributeNames.EXT_TYPE]: extPoint, + [AttributeNames.HAPI_TYPE]: HapiLayerType.EXT, + }, + name: baseName, + }; +}; + +export const getPluginFromInput = (pluginObj: HapiPluginObject): Hapi.Plugin => { + if ('plugin' in pluginObj) { + if ('plugin' in pluginObj.plugin) { + return pluginObj.plugin.plugin; + } + return pluginObj.plugin; + } + return pluginObj; +}; diff --git a/yarn.lock b/yarn.lock index e281eb67c324..20c7b72091af 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6074,15 +6074,6 @@ dependencies: "@opentelemetry/instrumentation" "^0.214.0" -"@opentelemetry/instrumentation-hapi@0.60.0": - version "0.60.0" - resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-hapi/-/instrumentation-hapi-0.60.0.tgz#ad1ba65a32347351c310ac0f194fe66b8e9d9e7d" - integrity sha512-aNljZKYrEa7obLAxd1bCEDxF7kzCLGXTuTJZ8lMR9rIVEjmuKBXN1gfqpm/OB//Zc2zP4iIve1jBp7sr3mQV6w== - dependencies: - "@opentelemetry/core" "^2.0.0" - "@opentelemetry/instrumentation" "^0.214.0" - "@opentelemetry/semantic-conventions" "^1.27.0" - "@opentelemetry/instrumentation-http@0.214.0": version "0.214.0" resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-http/-/instrumentation-http-0.214.0.tgz#d4a31a638b798e191f4f556c257a4d3c97d65ba0" @@ -6197,7 +6188,7 @@ "@opentelemetry/resources" "2.6.1" "@opentelemetry/semantic-conventions" "^1.29.0" -"@opentelemetry/semantic-conventions@^1.27.0", "@opentelemetry/semantic-conventions@^1.28.0", "@opentelemetry/semantic-conventions@^1.29.0", "@opentelemetry/semantic-conventions@^1.33.0", "@opentelemetry/semantic-conventions@^1.34.0", "@opentelemetry/semantic-conventions@^1.40.0": +"@opentelemetry/semantic-conventions@^1.28.0", "@opentelemetry/semantic-conventions@^1.29.0", "@opentelemetry/semantic-conventions@^1.33.0", "@opentelemetry/semantic-conventions@^1.34.0", "@opentelemetry/semantic-conventions@^1.40.0": version "1.40.0" resolved "https://registry.yarnpkg.com/@opentelemetry/semantic-conventions/-/semantic-conventions-1.40.0.tgz#10b2944ca559386590683392022a897eefd011d3" integrity sha512-cifvXDhcqMwwTlTK04GBNeIe7yyo28Mfby85QXFe1Yk8nmi36Ab/5UQwptOx84SsoGNRg+EVSjwzfSZMy6pmlw==