diff --git a/packages/nestjs/package.json b/packages/nestjs/package.json index 23c37c137dd2..354488e9e71a 100644 --- a/packages/nestjs/package.json +++ b/packages/nestjs/package.json @@ -47,7 +47,7 @@ "@opentelemetry/api": "^1.9.0", "@opentelemetry/core": "^1.30.1", "@opentelemetry/instrumentation": "0.57.2", - "@opentelemetry/instrumentation-nestjs-core": "0.44.1", + "@opentelemetry/instrumentation-nestjs-core": "^0.48.0", "@opentelemetry/semantic-conventions": "^1.34.0", "@sentry/core": "9.30.0", "@sentry/node": "9.30.0" diff --git a/packages/nestjs/src/integrations/nest.ts b/packages/nestjs/src/integrations/nest.ts index 4cc68c720541..53086b7da302 100644 --- a/packages/nestjs/src/integrations/nest.ts +++ b/packages/nestjs/src/integrations/nest.ts @@ -1,13 +1,13 @@ +import { NestInstrumentation as NestInstrumentationCore } from '@opentelemetry/instrumentation-nestjs-core'; import { defineIntegration } from '@sentry/core'; import { generateInstrumentOnce } from '@sentry/node'; -import { NestInstrumentation } from './sentry-nest-core-instrumentation'; import { SentryNestEventInstrumentation } from './sentry-nest-event-instrumentation'; import { SentryNestInstrumentation } from './sentry-nest-instrumentation'; const INTEGRATION_NAME = 'Nest'; const instrumentNestCore = generateInstrumentOnce('Nest-Core', () => { - return new NestInstrumentation(); + return new NestInstrumentationCore(); }); const instrumentNestCommon = generateInstrumentOnce('Nest-Common', () => { diff --git a/packages/nestjs/src/integrations/sentry-nest-core-instrumentation.ts b/packages/nestjs/src/integrations/sentry-nest-core-instrumentation.ts deleted file mode 100644 index aec664633342..000000000000 --- a/packages/nestjs/src/integrations/sentry-nest-core-instrumentation.ts +++ /dev/null @@ -1,307 +0,0 @@ -/* - * This file is based on code from the OpenTelemetry Authors - * Source: https://github.com/open-telemetry/opentelemetry-js-contrib - * - * Modified for immediate requirements while maintaining compliance - * with the original Apache 2.0 license terms. - * - * Original License: - * 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. - */ - -import type { Controller } from '@nestjs/common/interfaces'; -import type { NestFactory } from '@nestjs/core/nest-factory.js'; -import type { RouterExecutionContext } from '@nestjs/core/router/router-execution-context.js'; -import * as api from '@opentelemetry/api'; -import type { InstrumentationConfig } from '@opentelemetry/instrumentation'; -import { - InstrumentationBase, - InstrumentationNodeModuleDefinition, - InstrumentationNodeModuleFile, - isWrapped, -} from '@opentelemetry/instrumentation'; -import { ATTR_HTTP_REQUEST_METHOD, ATTR_HTTP_ROUTE, SEMATTRS_HTTP_URL } from '@opentelemetry/semantic-conventions'; -import { SDK_VERSION } from '@sentry/core'; - -const supportedVersions = ['>=4.0.0 <12']; -const COMPONENT = '@nestjs/core'; - -enum AttributeNames { - VERSION = 'nestjs.version', - TYPE = 'nestjs.type', - MODULE = 'nestjs.module', - CONTROLLER = 'nestjs.controller', - CALLBACK = 'nestjs.callback', - PIPES = 'nestjs.pipes', - INTERCEPTORS = 'nestjs.interceptors', - GUARDS = 'nestjs.guards', -} - -export enum NestType { - APP_CREATION = 'app_creation', - REQUEST_CONTEXT = 'request_context', - REQUEST_HANDLER = 'handler', -} - -/** - * - */ -export class NestInstrumentation extends InstrumentationBase { - public constructor(config: InstrumentationConfig = {}) { - super('sentry-nestjs', SDK_VERSION, config); - } - - /** - * - */ - public init(): InstrumentationNodeModuleDefinition { - const module = new InstrumentationNodeModuleDefinition(COMPONENT, supportedVersions); - - module.files.push( - this._getNestFactoryFileInstrumentation(supportedVersions), - this._getRouterExecutionContextFileInstrumentation(supportedVersions), - ); - - return module; - } - - /** - * - */ - private _getNestFactoryFileInstrumentation(versions: string[]): InstrumentationNodeModuleFile { - return new InstrumentationNodeModuleFile( - '@nestjs/core/nest-factory.js', - versions, - // todo - // eslint-disable-next-line @typescript-eslint/no-explicit-any - (NestFactoryStatic: any, moduleVersion?: string) => { - this._ensureWrapped( - // todo - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - NestFactoryStatic.NestFactoryStatic.prototype, - 'create', - createWrapNestFactoryCreate(this.tracer, moduleVersion), - ); - return NestFactoryStatic; - }, - // todo - // eslint-disable-next-line @typescript-eslint/no-explicit-any - (NestFactoryStatic: any) => { - // todo - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - this._unwrap(NestFactoryStatic.NestFactoryStatic.prototype, 'create'); - }, - ); - } - - /** - * - */ - private _getRouterExecutionContextFileInstrumentation(versions: string[]): InstrumentationNodeModuleFile { - return new InstrumentationNodeModuleFile( - '@nestjs/core/router/router-execution-context.js', - versions, - // todo - // eslint-disable-next-line @typescript-eslint/no-explicit-any - (RouterExecutionContext: any, moduleVersion?: string) => { - this._ensureWrapped( - // todo - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - RouterExecutionContext.RouterExecutionContext.prototype, - 'create', - createWrapCreateHandler(this.tracer, moduleVersion), - ); - return RouterExecutionContext; - }, - // todo - // eslint-disable-next-line @typescript-eslint/no-explicit-any - (RouterExecutionContext: any) => { - // todo - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - this._unwrap(RouterExecutionContext.RouterExecutionContext.prototype, 'create'); - }, - ); - } - - /** - * - */ - // todo - // eslint-disable-next-line @typescript-eslint/no-explicit-any - private _ensureWrapped(obj: any, methodName: string, wrapper: (original: any) => any): void { - // todo - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - if (isWrapped(obj[methodName])) { - this._unwrap(obj, methodName); - } - this._wrap(obj, methodName, wrapper); - } -} - -function createWrapNestFactoryCreate(tracer: api.Tracer, moduleVersion?: string) { - return function wrapCreate(original: typeof NestFactory.create) { - return function createWithTrace( - this: typeof NestFactory, - // todo - // eslint-disable-next-line @typescript-eslint/no-explicit-any - nestModule: any, - /* serverOrOptions */ - ) { - const span = tracer.startSpan('Create Nest App', { - attributes: { - component: COMPONENT, - [AttributeNames.TYPE]: NestType.APP_CREATION, - [AttributeNames.VERSION]: moduleVersion, - // todo - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - [AttributeNames.MODULE]: nestModule.name, - }, - }); - const spanContext = api.trace.setSpan(api.context.active(), span); - - return api.context.with(spanContext, async () => { - try { - // todo - // eslint-disable-next-line prefer-rest-params, @typescript-eslint/no-explicit-any - return await original.apply(this, arguments as any); - // todo - // eslint-disable-next-line @typescript-eslint/no-explicit-any - } catch (e: any) { - throw addError(span, e); - } finally { - span.end(); - } - }); - }; - }; -} - -function createWrapCreateHandler(tracer: api.Tracer, moduleVersion?: string) { - return function wrapCreateHandler(original: RouterExecutionContext['create']) { - return function createHandlerWithTrace( - this: RouterExecutionContext, - instance: Controller, - // todo - // eslint-disable-next-line @typescript-eslint/no-explicit-any - callback: (...args: any[]) => unknown, - ) { - // todo - // eslint-disable-next-line prefer-rest-params - arguments[1] = createWrapHandler(tracer, moduleVersion, callback); - // todo - // eslint-disable-next-line prefer-rest-params, @typescript-eslint/no-explicit-any - const handler = original.apply(this, arguments as any); - const callbackName = callback.name; - const instanceName = - // todo - // eslint-disable-next-line @typescript-eslint/prefer-optional-chain - instance.constructor && instance.constructor.name ? instance.constructor.name : 'UnnamedInstance'; - const spanName = callbackName ? `${instanceName}.${callbackName}` : instanceName; - - // todo - // eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any - return function (this: any, req: any, res: any, next: (...args: any[]) => unknown) { - const span = tracer.startSpan(spanName, { - attributes: { - component: COMPONENT, - [AttributeNames.VERSION]: moduleVersion, - [AttributeNames.TYPE]: NestType.REQUEST_CONTEXT, - // todo - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - [ATTR_HTTP_REQUEST_METHOD]: req.method, - // todo - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, deprecation/deprecation - [SEMATTRS_HTTP_URL]: req.originalUrl || req.url, - // todo - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - [ATTR_HTTP_ROUTE]: req.route?.path || req.routeOptions?.url || req.routerPath, - [AttributeNames.CONTROLLER]: instanceName, - [AttributeNames.CALLBACK]: callbackName, - }, - }); - const spanContext = api.trace.setSpan(api.context.active(), span); - - return api.context.with(spanContext, async () => { - try { - // todo - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, prefer-rest-params - return await handler.apply(this, arguments as unknown); - // todo - // eslint-disable-next-line @typescript-eslint/no-explicit-any - } catch (e: any) { - throw addError(span, e); - } finally { - span.end(); - } - }); - }; - }; - }; -} - -function createWrapHandler( - tracer: api.Tracer, - moduleVersion: string | undefined, - // todo - // eslint-disable-next-line @typescript-eslint/ban-types - handler: Function, - // todo - // eslint-disable-next-line @typescript-eslint/no-explicit-any -): (this: RouterExecutionContext) => Promise { - const spanName = handler.name || 'anonymous nest handler'; - const options = { - attributes: { - component: COMPONENT, - [AttributeNames.VERSION]: moduleVersion, - [AttributeNames.TYPE]: NestType.REQUEST_HANDLER, - [AttributeNames.CALLBACK]: handler.name, - }, - }; - // todo - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const wrappedHandler = function (this: RouterExecutionContext): Promise { - const span = tracer.startSpan(spanName, options); - const spanContext = api.trace.setSpan(api.context.active(), span); - - return api.context.with(spanContext, async () => { - try { - // todo - // eslint-disable-next-line prefer-rest-params - return await handler.apply(this, arguments); - // todo - // eslint-disable-next-line @typescript-eslint/no-explicit-any - } catch (e: any) { - throw addError(span, e); - } finally { - span.end(); - } - }); - }; - - if (handler.name) { - Object.defineProperty(wrappedHandler, 'name', { value: handler.name }); - } - - // Get the current metadata and set onto the wrapper to ensure other decorators ( ie: NestJS EventPattern / RolesGuard ) - // won't be affected by the use of this instrumentation - Reflect.getMetadataKeys(handler).forEach(metadataKey => { - Reflect.defineMetadata(metadataKey, Reflect.getMetadata(metadataKey, handler), wrappedHandler); - }); - return wrappedHandler; -} - -const addError = (span: api.Span, error: Error): Error => { - span.recordException(error); - span.setStatus({ code: api.SpanStatusCode.ERROR, message: error.message }); - return error; -}; diff --git a/yarn.lock b/yarn.lock index ce3e50da8c41..23a570202244 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5417,6 +5417,13 @@ dependencies: "@opentelemetry/api" "^1.3.0" +"@opentelemetry/api-logs@0.202.0": + version "0.202.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/api-logs/-/api-logs-0.202.0.tgz#78ddb3b4a30232fd0916b99f27777b1936355d03" + integrity sha512-fTBjMqKCfotFWfLzaKyhjLvyEyq5vDKTTFfBmx21btv3gvy8Lq6N5Dh2OzqeuN4DjtpSvNT1uNVfg08eD2Rfxw== + dependencies: + "@opentelemetry/api" "^1.3.0" + "@opentelemetry/api-logs@0.57.2": version "0.57.2" resolved "https://registry.yarnpkg.com/@opentelemetry/api-logs/-/api-logs-0.57.2.tgz#d4001b9aa3580367b40fe889f3540014f766cc87" @@ -5625,13 +5632,13 @@ "@opentelemetry/semantic-conventions" "^1.27.0" "@types/mysql" "2.15.26" -"@opentelemetry/instrumentation-nestjs-core@0.44.1": - version "0.44.1" - resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-nestjs-core/-/instrumentation-nestjs-core-0.44.1.tgz#54ee5877080055732093c59f8a9bc2aba4fae5f0" - integrity sha512-4TXaqJK27QXoMqrt4+hcQ6rKFd8B6V4JfrTJKnqBmWR1cbaqd/uwyl9yxhNH1JEkyo8GaBfdpBC4ZE4FuUhPmg== +"@opentelemetry/instrumentation-nestjs-core@^0.48.0": + version "0.48.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-nestjs-core/-/instrumentation-nestjs-core-0.48.0.tgz#86f1d1af48a6654adc8497a8fccd31eb234a6357" + integrity sha512-ytK4ABSkWcD9vyMU8GpinvodAGaRxBFuxybP/m7sgLtEboXMJjdWnEHb7lH/CX1ICiVKRXWdYg9npdu6yBCW5Q== dependencies: - "@opentelemetry/instrumentation" "^0.57.1" - "@opentelemetry/semantic-conventions" "^1.27.0" + "@opentelemetry/instrumentation" "^0.202.0" + "@opentelemetry/semantic-conventions" "^1.30.0" "@opentelemetry/instrumentation-pg@0.51.1": version "0.51.1" @@ -5694,6 +5701,15 @@ require-in-the-middle "^7.1.1" shimmer "^1.2.1" +"@opentelemetry/instrumentation@^0.202.0": + version "0.202.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation/-/instrumentation-0.202.0.tgz#9468829b23039e1d675635c63f18c8676dce3079" + integrity sha512-Uz3BxZWPgDwgHM2+vCKEQRh0R8WKrd/q6Tus1vThRClhlPO39Dyz7mDrOr6KuqGXAlBQ1e5Tnymzri4RMZNaWA== + dependencies: + "@opentelemetry/api-logs" "0.202.0" + import-in-the-middle "^1.8.1" + require-in-the-middle "^7.1.1" + "@opentelemetry/propagation-utils@^0.30.16": version "0.30.16" resolved "https://registry.yarnpkg.com/@opentelemetry/propagation-utils/-/propagation-utils-0.30.16.tgz#6715d0225b618ea66cf34cc3800fa3452a8475fa" @@ -5743,7 +5759,7 @@ resolved "https://registry.yarnpkg.com/@opentelemetry/semantic-conventions/-/semantic-conventions-1.28.0.tgz#337fb2bca0453d0726696e745f50064411f646d6" integrity sha512-lp4qAiMTD4sNWW4DbKLBkfiMZ4jbAboJIGOQr5DvciMRI494OapieI9qiODpOt0XBr1LjIDy1xAGAnVs5supTA== -"@opentelemetry/semantic-conventions@^1.27.0", "@opentelemetry/semantic-conventions@^1.29.0", "@opentelemetry/semantic-conventions@^1.34.0": +"@opentelemetry/semantic-conventions@^1.27.0", "@opentelemetry/semantic-conventions@^1.29.0", "@opentelemetry/semantic-conventions@^1.30.0", "@opentelemetry/semantic-conventions@^1.34.0": version "1.34.0" resolved "https://registry.yarnpkg.com/@opentelemetry/semantic-conventions/-/semantic-conventions-1.34.0.tgz#8b6a46681b38a4d5947214033ac48128328c1738" integrity sha512-aKcOkyrorBGlajjRdVoJWHTxfxO1vCNHLJVlSDaRHDIdjU+pX8IYQPvPDkYiujKLbRnWU+1TBwEt0QRgSm4SGA== @@ -27148,7 +27164,6 @@ stylus@0.59.0, stylus@^0.59.0: sucrase@^3.27.0, sucrase@^3.35.0, sucrase@getsentry/sucrase#es2020-polyfills: version "3.36.0" - uid fd682f6129e507c00bb4e6319cc5d6b767e36061 resolved "https://codeload.github.com/getsentry/sucrase/tar.gz/fd682f6129e507c00bb4e6319cc5d6b767e36061" dependencies: "@jridgewell/gen-mapping" "^0.3.2"