import type { DynamicModule, OnModuleInit } from '@nestjs/common'; import { Module } from '@nestjs/common'; import { APP_INTERCEPTOR } from '@nestjs/core'; import type { ContextAPI, TraceAPI } from '@opentelemetry/api'; import { text } from '@rsdk/common'; import { ILogger, LoggerFactory } from '@rsdk/logging'; import { InjectLogger } from '../logging'; import type { PlatformOptions } from '../types'; import { DEFAULT_TRACING_HEADERS, DEFAULT_TRACING_OPTIONS, HEADER_MAPPINGS, } from './constants'; import { TracingHeaders } from './tracing.actx'; import { TracingInterceptor } from './tracing.interceptor'; import type { TracingHeaderMappings } from './types'; export interface TracingOptions { includeDefault: boolean; additionalMappings?: TracingHeaderMappings[]; } @Module({}) export class TracingModule implements OnModuleInit { private trace: TraceAPI | null = null; private context: ContextAPI | null = null; constructor(@InjectLogger(TracingModule) private readonly logger: ILogger) {} static forRoot({ tracing = DEFAULT_TRACING_OPTIONS, plugins = [], }: PlatformOptions): DynamicModule { const mappings: TracingHeaderMappings[] = []; if (tracing?.includeDefault) { mappings.push(...DEFAULT_TRACING_HEADERS); } for (const plugin of plugins) { mappings.push(...(plugin.additionalTracingHeaders?.() ?? [])); } mappings.push(...(tracing.additionalMappings ?? [])); return { global: true, module: TracingModule, providers: [ { provide: APP_INTERCEPTOR, useClass: TracingInterceptor }, { provide: HEADER_MAPPINGS, useValue: mappings }, ], }; } // Можно добавить включение и выключение при изменении конфига. async onModuleInit(): Promise { if (!this.context || !this.trace) { try { const { context, trace } = await import('@opentelemetry/api'); this.context = context; this.trace = trace; } catch { this.logger.debug(text` @opentelemetry/api is not installed. Opentelemetry metadata will not appear in log messages. `); } } LoggerFactory.applyInstrumentations((record) => { Object.assign(record, TracingHeaders.get() || {}); if (this.trace && this.context) { const { traceId, spanId } = this.trace.getSpan(this.context.active())?.spanContext() ?? {}; record['trace_id'] = traceId; record['span_id'] = spanId; } }); this.logger.info('Tracing is enabled'); this.logger.debug('Attaching log messages to spans...'); LoggerFactory.onMessage((level, msg) => { if (this.trace && this.context) { const span = this.trace.getSpan(this.context.active()); span?.addEvent('log', { ...msg, level }); } }); } }