#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;
}))