Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

@opentelemetry/instrumentation-nestjs-core - does not work with Microservice #2510

Open
neilime opened this issue Oct 30, 2024 · 1 comment
Labels
feature-request up-for-grabs Good for taking. Extra help will be provided by maintainers

Comments

@neilime
Copy link

neilime commented Oct 30, 2024

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;
};
@neilime neilime added the bug Something isn't working label Oct 30, 2024
@dyladan
Copy link
Member

dyladan commented Oct 30, 2024

Hello @neilime it sounds to me like this is a feature request to support a particular feature of nest js? Unfortunately the nestJS instrumentation is currently unmaintained. You can see component owners here: https://github.com/open-telemetry/opentelemetry-js-contrib/blob/main/.github/component_owners.yml

I'm going to reclassify this as a feature request. If you would like to make a PR to add support that would be appreciated.

@dyladan dyladan added feature-request up-for-grabs Good for taking. Extra help will be provided by maintainers and removed bug Something isn't working labels Oct 30, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature-request up-for-grabs Good for taking. Extra help will be provided by maintainers
Projects
None yet
Development

No branches or pull requests

2 participants