Как настроить контроллер в nestjs interceptor?

#node.js #typescript #rxjs #nestjs #datadog

#node.js #typescript #rxjs #nestjs #datadog

Вопрос:

Я хочу использовать каждый метод контроллера nestjs для целей APM. Я написал следующий перехватчик, чтобы настроить вызов контроллера.

Однако я не знаю, как правильно обернуть вызов next.handle() .
У меня нет никакого опыта использования RxJS Observables.

Вопрос: Можно ли правильно обернуть вызов, и если да, то как?

Текущий подход, похоже, измеряет время выполнения контроллера, но не устанавливает правильную область трассировки для метода контроллера. Я думаю, проблема в том, что next.handle() это тоже должно быть обернуто.

 import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from "@nestjs/common";
import { Reflector } from "@nestjs/core";
import { Observable } from "rxjs";
import { PATH_METADATA } from '@nestjs/common/constants';
import tracer from "dd-trace";

@Injectable()
export class ApmInterceptor implements NestInterceptor {
    constructor(private readonly reflector: Reflector) {}
    
    public intercept(context: ExecutionContext, next: CallHandler): Observable<unknown> {
        const request: Request = context.switchToHttp().getRequest();

        const path = this.reflector.get<string[]>(PATH_METADATA, context.getHandler()); 
        const method = request.method;

        const observable = next.handle();

        tracer.trace(`[${method}] ${path}`, () => new Promise((resolve, reject) => {
            observable.subscribe({
                complete: resolve,
            });
        }));

        return observable;
    }
}
  

Комментарии:

1. Не могли бы вы подробнее объяснить, что вы подразумеваете под инструментом?

2. Конечно, под инструментированием я подразумеваю: отслеживание выполнения функции для определения времени выполнения (как определено, например en.wikipedia.org/wiki/Instrumentation_ (computer_programming) ) и обеспечивается решениями APM, такими как, например docs.datadoghq.com/tracing/guide/instrument_custom_method /…

3. А, понятно. Ну, я никогда с этим не работал dd-trace , но у меня есть пользовательский регистратор с перехватчиком ведения журнала, который может выводить данные в удобном для DataDog формате. dd-trace Тем временем я займусь этим вопросом

4. Длинный выстрел здесь, но я думаю, что вместо observable.subscribe() этого вы захотите сделать observable.pipe(tap(resolve)) tap то, откуда импортируется rxjs/operators . Если это сработает, я напишу подробное объяснение ответа

5. Я бы не сказал, что это проблема с Nest, скорее это проблема с RxJS из-за того, как он разработан. Однако тот факт, что Nest использует его, делает его неудачным в данном случае. Я думаю, что в этом случае вам нужно было бы создать пользовательский Span объект d-trace и установить значения, как вы и ожидали. Пользовательский регистратор, о котором я упоминал в предыдущем комментарии, вроде как делает это (без моего ведома, что я это сделал), так что вы можете извлечь из него некоторые идеи. Если вы хотите обсудить это подробнее, не стесняйтесь обращаться к Discord . Имя пользователя: PerfectOrphan31#6003

Ответ №1:

Столкнулся с аналогичной проблемой при использовании OpenTelemetry-js, чтобы установить правильную область, я должен обернуть handle() Observable в Async обещание, чтобы установить контекст, а затем снова обернуть обещание, как Observable для rxjs конвейера ( Observable -> Promise -> Observable )

 import {from, Observable} from 'rxjs';
...

 async intercept(executionContext: ExecutionContext, next: CallHandler): Promise<Observable<any>> {
    const request: Request = context.switchToHttp().getRequest();

    const path = this.reflector.get<string[]>(PATH_METADATA, context.getHandler()); 
    const method = request.method;

    const observable = tracer.trace(`[${method}] ${path}`, () => new Promise((resolve, reject) => {
      return next.handle().toPromise();
    }));
    return observable.pipe(
        map(value => {
          // Here you can stop your trace manually
          return value;
        }),
        catchError(error => {
          // Here you can stop your trace manually
          throw error;
        }))
}
  

Для OpenTelemetry вы должны создать / остановить диапазон и установить правильный контекст:

 const span = trace.getTracer('default').startSpan(spanName);
const observable = from(context.with(trace.setSpan(context.active(), span), async () => {
  return next.handle().toPromise();
}));
return observable.pipe(
    map(value => {
      span.stop();
      return value;
    }),
    catchError(error => {
      span.addEvent('error', {error: error});
      span.stop();
      throw error;
    }))