Open
Description
What version of OpenTelemetry are you using?
- "@opentelemetry/instrumentation": "^0.54.0"
- "@opentelemetry/instrumentation-fastify": "^0.41.0"
- "@opentelemetry/instrumentation-http": "^0.54.0"
- "@opentelemetry/instrumentation-nestjs-core": "^0.41.0"
What version of Node are you using?
20.18
What did you do?
Attempted to integrate @opentelemetry/instrumentation-nestjs-core
with a NestJS-based microservice (https://docs.nestjs.com/microservices/basics) setup to capture distributed traces across microservice interactions.
What did you expect to see?
Expected the instrumentation to support NestJS microservices, allowing automatic trace propagation and capturing relevant metadata across service calls.
What did you see instead?
The current instrumentation does not fully support NestJS microservices, resulting in incomplete trace propagation or missing metadata across microservice interactions.
Additional context
Here is an implementation workaround:
import * as api from '@opentelemetry/api';
import {
InstrumentationBase,
InstrumentationConfig,
InstrumentationNodeModuleDefinition,
InstrumentationNodeModuleFile,
isWrapped,
} from '@opentelemetry/instrumentation';
import type { NestFactory } from '@nestjs/core/nest-factory.js';
import {
PACKAGE_VERSION,
} from '@opentelemetry/instrumentation-nestjs-core/build/src/version';
import {
AttributeNames,
NestType,
} from '@opentelemetry/instrumentation-nestjs-core/build/src/enums';
const supportedVersions = ['>=4.0.0 <11'];
const PACKAGE_NAME = '@opentelemetry/instrumentation-nestjs-microservice';
export class NestMicroserviceInstrumentation extends InstrumentationBase {
static readonly COMPONENT = '@nestjs/core';
static readonly COMMON_ATTRIBUTES = {
component: NestMicroserviceInstrumentation.COMPONENT,
};
constructor(config: InstrumentationConfig = {}) {
super(PACKAGE_NAME, PACKAGE_VERSION, config);
}
init() {
const module = new InstrumentationNodeModuleDefinition(
NestMicroserviceInstrumentation.COMPONENT,
supportedVersions,
);
module.files.push(
this.getNestFactoryFileInstrumentation(supportedVersions),
);
return module;
}
getNestFactoryFileInstrumentation(versions: string[]) {
return new InstrumentationNodeModuleFile(
'@nestjs/core/nest-factory.js',
versions,
(NestFactoryStatic: any, moduleVersion?: string) => {
this.ensureWrapped(
NestFactoryStatic.NestFactoryStatic.prototype,
'createMicroservice',
createWrapNestFactoryCreate(this.tracer, moduleVersion),
);
return NestFactoryStatic;
},
(NestFactoryStatic: any) => {
this._unwrap(
NestFactoryStatic.NestFactoryStatic.prototype,
'createMicroservice',
);
},
);
}
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,
/* NestMicroserviceOptions */
...args: any[]
) {
const span = tracer.startSpan('Create Nest Microservice', {
attributes: {
...NestMicroserviceInstrumentation.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, [nestModule, ...args]);
} catch (e: any) {
throw addError(span, e);
} finally {
span.end();
}
});
};
};
}
const addError = (span: api.Span, error: Error) => {
span.recordException(error);
span.setStatus({ code: api.SpanStatusCode.ERROR, message: error.message });
return error;
};