Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .oxlintrc.base.json
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,8 @@
"**/integrations/fs/vendored/**/*.ts",
"**/integrations/tracing/knex/vendored/**/*.ts",
"**/integrations/tracing/mongo/vendored/**/*.ts",
"**/integrations/tracing/connect/vendored/**/*.ts"
"**/integrations/tracing/connect/vendored/**/*.ts",
"**/nestjs/src/integrations/vendored/**/*.ts"
],
"rules": {
"typescript/no-explicit-any": "off"
Expand Down
1 change: 0 additions & 1 deletion packages/nestjs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@
"@opentelemetry/api": "^1.9.1",
"@opentelemetry/core": "^2.6.1",
"@opentelemetry/instrumentation": "^0.214.0",
"@opentelemetry/instrumentation-nestjs-core": "0.60.0",
"@opentelemetry/semantic-conventions": "^1.40.0",
"@sentry/core": "10.53.1",
"@sentry/node": "10.53.1"
Expand Down
2 changes: 1 addition & 1 deletion packages/nestjs/src/integrations/nest.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { NestInstrumentation as NestInstrumentationCore } from '@opentelemetry/instrumentation-nestjs-core';
import { NestInstrumentation as NestInstrumentationCore } from './vendored/instrumentation';
import { defineIntegration } from '@sentry/core';
import { generateInstrumentOnce } from '@sentry/node';
import { SentryNestBullMQInstrumentation } from './sentry-nest-bullmq-instrumentation';
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* 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-nestjs-core
* - Upstream version: @opentelemetry/instrumentation-nestjs-core@0.64.0
*/
/* eslint-disable */

export 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',
}
26 changes: 26 additions & 0 deletions packages/nestjs/src/integrations/vendored/enums/NestType.ts
Original file line number Diff line number Diff line change
@@ -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-nestjs-core
* - Upstream version: @opentelemetry/instrumentation-nestjs-core@0.64.0
*/
/* eslint-disable */

export enum NestType {
APP_CREATION = 'app_creation',
REQUEST_CONTEXT = 'request_context',
REQUEST_HANDLER = 'handler',
}
23 changes: 23 additions & 0 deletions packages/nestjs/src/integrations/vendored/enums/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
* 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-nestjs-core
* - Upstream version: @opentelemetry/instrumentation-nestjs-core@0.64.0
*/
/* eslint-disable */

export { AttributeNames } from './AttributeNames';
export { NestType } from './NestType';
249 changes: 249 additions & 0 deletions packages/nestjs/src/integrations/vendored/instrumentation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,249 @@
/*
* 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-nestjs-core
* - Upstream version: @opentelemetry/instrumentation-nestjs-core@0.64.0
* - Some types vendored from @nestjs/core and @nestjs/common with simplifications
*/
/* eslint-disable */

import * as api from '@opentelemetry/api';
import {
InstrumentationBase,
InstrumentationConfig,
InstrumentationNodeModuleDefinition,
InstrumentationNodeModuleFile,
isWrapped,
SemconvStability,
semconvStabilityFromStr,
} from '@opentelemetry/instrumentation';
import { ATTR_HTTP_REQUEST_METHOD, ATTR_HTTP_ROUTE, ATTR_URL_FULL } from '@opentelemetry/semantic-conventions';
import { SDK_VERSION } from '@sentry/core';
import { ATTR_HTTP_METHOD, ATTR_HTTP_URL } from './semconv';
import { AttributeNames, NestType } from './enums';

const PACKAGE_NAME = '@sentry/instrumentation-nestjs-core';

type Controller = object;

declare const NestFactory: {
create(...args: any[]): Promise<any>;
};

interface RouterExecutionContext {
create(instance: Controller, callback: (...args: any[]) => unknown, ...args: any[]): any;
}

declare namespace Reflect {
function getMetadataKeys(target: any): any[];
function getMetadata(metadataKey: any, target: any): any;
function defineMetadata(metadataKey: any, metadataValue: any, target: any): void;
}

const supportedVersions = ['>=4.0.0 <12'];

export class NestInstrumentation extends InstrumentationBase {
static readonly COMPONENT = '@nestjs/core';
static readonly COMMON_ATTRIBUTES = {
component: NestInstrumentation.COMPONENT,
};

private _semconvStability: SemconvStability;

constructor(config: InstrumentationConfig = {}) {
super(PACKAGE_NAME, SDK_VERSION, config);
this._semconvStability = semconvStabilityFromStr('http', process.env.OTEL_SEMCONV_STABILITY_OPT_IN);
}

init() {
const module = new InstrumentationNodeModuleDefinition(NestInstrumentation.COMPONENT, supportedVersions);

module.files.push(
this.getNestFactoryFileInstrumentation(supportedVersions),
this.getRouterExecutionContextFileInstrumentation(supportedVersions),
);

return module;
}

getNestFactoryFileInstrumentation(versions: string[]) {
return new InstrumentationNodeModuleFile(
'@nestjs/core/nest-factory.js',
versions,
(NestFactoryStatic: any, moduleVersion?: string) => {
this.ensureWrapped(
NestFactoryStatic.NestFactoryStatic.prototype,
'create',
createWrapNestFactoryCreate(this.tracer, moduleVersion),
);
return NestFactoryStatic;
},
(NestFactoryStatic: any) => {
this._unwrap(NestFactoryStatic.NestFactoryStatic.prototype, 'create');
},
);
}

getRouterExecutionContextFileInstrumentation(versions: string[]) {
return new InstrumentationNodeModuleFile(
'@nestjs/core/router/router-execution-context.js',
versions,
(RouterExecutionContext: any, moduleVersion?: string) => {
this.ensureWrapped(
RouterExecutionContext.RouterExecutionContext.prototype,
'create',
createWrapCreateHandler(this.tracer, moduleVersion, this._semconvStability),
);
return RouterExecutionContext;
},
(RouterExecutionContext: any) => {
this._unwrap(RouterExecutionContext.RouterExecutionContext.prototype, 'create');
},
);
}

private ensureWrapped(obj: any, methodName: string, wrapper: (original: any) => any) {
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,
nestModule: any,
/* serverOrOptions */
) {
const span = tracer.startSpan('Create Nest App', {
attributes: {
...NestInstrumentation.COMMON_ATTRIBUTES,
[AttributeNames.TYPE]: NestType.APP_CREATION,
[AttributeNames.VERSION]: moduleVersion,
[AttributeNames.MODULE]: nestModule.name,
},
});
const spanContext = api.trace.setSpan(api.context.active(), span);

return api.context.with(spanContext, async () => {
try {
return await original.apply(this, arguments as any);
} catch (e: any) {
throw addError(span, e);
} finally {
span.end();
}
});
};
};
}

function createWrapCreateHandler(
tracer: api.Tracer,
moduleVersion: string | undefined,
semconvStability: SemconvStability,
) {
return function wrapCreateHandler(original: RouterExecutionContext['create']) {
return function createHandlerWithTrace(
this: RouterExecutionContext,
instance: Controller,
callback: (...args: any[]) => unknown,
) {
arguments[1] = createWrapHandler(tracer, moduleVersion, callback);
const handler = original.apply(this, arguments as any);
const callbackName = callback.name;
const instanceName =
instance.constructor && instance.constructor.name ? instance.constructor.name : 'UnnamedInstance';
const spanName = callbackName ? `${instanceName}.${callbackName}` : instanceName;

return function (this: any, req: any, res: any, next: (...args: any[]) => unknown) {
const attributes: api.Attributes = {
...NestInstrumentation.COMMON_ATTRIBUTES,
[AttributeNames.VERSION]: moduleVersion,
[AttributeNames.TYPE]: NestType.REQUEST_CONTEXT,
[ATTR_HTTP_ROUTE]: req.route?.path || req.routeOptions?.url || req.routerPath,
[AttributeNames.CONTROLLER]: instanceName,
[AttributeNames.CALLBACK]: callbackName,
};
if (semconvStability & SemconvStability.OLD) {
attributes[ATTR_HTTP_METHOD] = req.method;
attributes[ATTR_HTTP_URL] = req.originalUrl || req.url;
}
if (semconvStability & SemconvStability.STABLE) {
attributes[ATTR_HTTP_REQUEST_METHOD] = req.method;
attributes[ATTR_URL_FULL] = req.originalUrl || req.url;
}
const span = tracer.startSpan(spanName, { attributes });
const spanContext = api.trace.setSpan(api.context.active(), span);

return api.context.with(spanContext, async () => {
try {
return await handler.apply(this, arguments as any);
} catch (e: any) {
throw addError(span, e);
} finally {
span.end();
}
});
};
};
};
}

function createWrapHandler(tracer: api.Tracer, moduleVersion: string | undefined, handler: Function) {
const spanName = handler.name || 'anonymous nest handler';
const options = {
attributes: {
...NestInstrumentation.COMMON_ATTRIBUTES,
[AttributeNames.VERSION]: moduleVersion,
[AttributeNames.TYPE]: NestType.REQUEST_HANDLER,
[AttributeNames.CALLBACK]: handler.name,
},
};
const wrappedHandler = function (this: RouterExecutionContext) {
const span = tracer.startSpan(spanName, options);
const spanContext = api.trace.setSpan(api.context.active(), span);

return api.context.with(spanContext, async () => {
try {
return await handler.apply(this, arguments);
} 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) => {
span.recordException(error);
span.setStatus({ code: api.SpanStatusCode.ERROR, message: error.message });
return error;
};
Loading
Loading