import { ConsoleSpanExporter, NodeTracerProvider, TraceIdRatioBasedSampler } from '@opentelemetry/sdk-trace-node' import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-grpc' import { api, NodeSDK } from '@opentelemetry/sdk-node'; import { Resource } from '@opentelemetry/resources' import { ATTR_SERVICE_NAME, SemanticResourceAttributes } from '@opentelemetry/semantic-conventions'; import { HttpInstrumentation } from '@opentelemetry/instrumentation-http' import { ExpressInstrumentation } from '@opentelemetry/instrumentation-express' import { SimpleSpanProcessor } from '@opentelemetry/sdk-trace-base' import { trace, Tracer } from '@opentelemetry/api' import { ClientRequest, IncomingMessage, ServerResponse } from 'node:http' import _ from 'lodash' import { initNest } from './manually-instrument.js' import { registerInstrumentations } from '@opentelemetry/instrumentation' import { SpanFilterSampler } from './span-filter-sampler.js' import { TraceConfig } from './trace-config.js'; import { UndiciInstrumentation } from '@opentelemetry/instrumentation-undici' import { OTLPGRPCExporterConfigNode } from '@opentelemetry/otlp-grpc-exporter-base/build/src/types.js'; import {Metadata} from '@grpc/grpc-js' import { Logger } from '@nestjs/common'; import { NestInstrumentation } from '@opentelemetry/instrumentation-nestjs-core'; import { OpenTelemetryModuleDefaultConfig } from '../nest/default-config'; import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node'; import { MySQL2Instrumentation } from '@opentelemetry/instrumentation-mysql2'; import { MongoDBInstrumentation } from '@opentelemetry/instrumentation-mongodb'; import { RedisInstrumentation } from '@opentelemetry/instrumentation-redis-4'; const TRACE_ID_HEADER = 'x-trace-id' const logger = new Logger('') let t: Tracer export function initTracing(traceConfig: TraceConfig) { const serviceName = traceConfig.serviceName if (_.toNumber(traceConfig.isTraceOn) !== 1) { logger.log('不启动调用链追踪') return; } logger.log('启动调用链追踪') const conf = { url: traceConfig.endpoint as string, } as OTLPGRPCExporterConfigNode if (traceConfig.metadata) { const metadata = new Metadata(); for (const k in traceConfig.metadata) { metadata.set(k, traceConfig.metadata[k]); } conf.metadata = metadata } const exporter = new OTLPTraceExporter(conf); const spanProcessors = [new SimpleSpanProcessor(exporter)] if (traceConfig.withConsoleExporter) { spanProcessors.push(new SimpleSpanProcessor(new ConsoleSpanExporter())) } const sdk = new NodeSDK({ ...OpenTelemetryModuleDefaultConfig, serviceName, resource: new Resource({ [ATTR_SERVICE_NAME]: serviceName, [SemanticResourceAttributes.HOST_NAME]: require("os").hostname() }), spanProcessors, instrumentations: [ new HttpInstrumentation({ ignoreIncomingRequestHook: traceConfig.ignoreIncomingRequestHook, ignoreOutgoingRequestHook: traceConfig.ignoreOutgoingRequestHook, // 添加一个自定义的responseHook responseHook: (span, response) => { // 从当前上下文中获取traceId, spanId api.trace.getActiveSpan()?.spanContext().traceId const traceId = span.spanContext().traceId; const spanId = span.spanContext().spanId; // 将traceId, spanId添加到响应标头中 if (response instanceof ServerResponse) { response.setHeader('TraceId', traceId) response.setHeader('SpanId', spanId); } // 返回响应对象 return response; }, }), new ExpressInstrumentation(), new UndiciInstrumentation(), // new NestInstrumentation(), // ...ins ...getNodeAutoInstrumentations(), // new NestInstrumentation() ], }); sdk.start() // 手动进行替换Nest参数, 我们项目因为某些原因Instrument不能正常初始化 t = trace.getTracer(serviceName); initNest(t) const shutdown = () => { sdk .shutdown() .then(() => console.log("Tracing terminated")) .catch((error) => console.log("Error terminating tracing", error)) .finally(() => process.exit(0)); } // Gracefully shutdown SDK if a SIGTERM is received process.on('SIGTERM', shutdown); // Gracefully shutdown SDK if Node.js is exiting normally process.once('beforeExit', shutdown); return sdk } export function initTracingWithProvider(traceConfig: TraceConfig) { if (_.toNumber(traceConfig.isTraceOn) !== 1) { logger.log('不启动调用链追踪') return; } logger.log('启动调用链追踪') const serviceName = traceConfig.serviceName const traceExporter = new ConsoleSpanExporter() const conf = { url: traceConfig.endpoint } as OTLPGRPCExporterConfigNode if (traceConfig.metadata) { const metadata = new Metadata(); for (const k in traceConfig.metadata) { metadata.set(k, traceConfig.metadata[k]); } conf.metadata = metadata } const exporter = new OTLPTraceExporter(conf); const spanProcessors = [new SimpleSpanProcessor(exporter)] if (traceConfig.withConsoleExporter) { spanProcessors.push(new SimpleSpanProcessor(new ConsoleSpanExporter())) } const provider = new NodeTracerProvider({ resource: new Resource({ [ATTR_SERVICE_NAME]: serviceName, }), // sampler: new SpanFilterSampler({ // // Trace ID Ratio Sampler accepts a positional argument // // which represents the percentage of traces which should // // be sampled. // root: new TraceIdRatioBasedSampler(traceConfig.sampleRatio? traceConfig.sampleRatio : 1) // }), spanProcessors, }); provider.register(); registerInstrumentations({ tracerProvider: provider, instrumentations: [ ...getNodeAutoInstrumentations(), new HttpInstrumentation({ ignoreIncomingRequestHook: traceConfig.ignoreIncomingRequestHook, ignoreOutgoingRequestHook: traceConfig.ignoreOutgoingRequestHook, requestHook: (span, request) => { // // 本地请求远程 // if (request instanceof ClientRequest) { // const traceId = span.spanContext().traceId // // const traceId = request.getHeader('x-trace-id') // if (_.isEmpty(traceId)) { // return // } // request.setHeader('x-trace-id', traceId) // } // // 远程请求本地 // if (request instanceof IncomingMessage) { // const traceId = request.headers[TRACE_ID_HEADER] // if (_.isEmpty(traceId)) { // return // } // span.spanContext().traceId = _.toString(traceId) // } }, // 添加一个自定义的responseHook responseHook: (span, response) => { // 从当前上下文中获取traceId, spanId const traceId = span.spanContext().traceId; const spanId = span.spanContext().spanId; // 将traceId, spanId添加到响应标头中 if (response instanceof ServerResponse) { response.setHeader('TraceId', traceId) response.setHeader('SpanId', spanId); } // 返回响应对象 return response; }, }), new ExpressInstrumentation(), new UndiciInstrumentation(), new NestInstrumentation(), new MySQL2Instrumentation(), new MongoDBInstrumentation(), // new RedisInstrumentation(), ], }); // Initialize the OpenTelemetry APIs to use the NodeTracerProvider bindings // const t = trace.getTracer(serviceName); // initNest(t) return t }