UNPKG

@servant573/nest-jaeger

Version:

Jaeger middleware to request tracing for nest application

199 lines (181 loc) 6.27 kB
import { Injectable, NestInterceptor, ExecutionContext, CallHandler, } from '@nestjs/common'; import { Reflector } from '@nestjs/core'; import { Observable } from 'rxjs'; import { tap } from 'rxjs/operators'; import { initTracer } from 'jaeger-client'; import { FORMAT_HTTP_HEADERS, Tags } from 'opentracing'; import axios from 'axios'; const TAGS = { ...Tags, PROTOCAL: 'protocal', TRACING_TAG: 'tracing-tag', }; const UNKNOW = 'Unknow'; const DEFAULT_SAMPLER = { type: 'const', param: 1, }; const DEFAULT_REPORTER = { collectorEndpoint: 'http://localhost:14268/api/traces', }; const DEFAULT_LOGGER = { info: (msg) => { console.log('JAEGER INFO ', msg); }, error: (msg) => { console.log('JAEGER ERROR', msg); }, }; const DEFAULT_OPTION = { logger: DEFAULT_LOGGER }; const DEFAULT_CONFIG = { serviceName: UNKNOW, reporter: DEFAULT_REPORTER, sampler: DEFAULT_SAMPLER, }; @Injectable() export class JaegerInterceptor implements NestInterceptor { // tracer instance, one request one tracer private readonly tracer: any = undefined; // master span instance, can have mutilpe chirld span inside private span: any = undefined; // tracing tag from request and will pass to remote call private tracing_tag: any = {}; // callback function form user private readonly req_cb: any = undefined; private readonly res_cb: any = undefined; constructor(cfg?: {}, opt?: {}, req_cb?: any, res_cb?: any) { // init tracer if (!this.tracer) { try { const config = { ...DEFAULT_CONFIG, ...cfg }; const options = { ...DEFAULT_OPTION, ...opt }; this.tracer = initTracer(config, options); this.req_cb = req_cb; this.res_cb = res_cb; console.log('[*]Init tracer ... [ DONE ] '); } catch (error) { console.error('[*]Init tracer ... [ FAILED ] '); } } else { console.log(`[*]Tracer already existed`); } } intercept(context: ExecutionContext, next: CallHandler): Observable<any> { ////////////////////////////////////////////////////////////////// // handle metadata const reflector = new Reflector(); const except = reflector.get<boolean>( 'ExceptJaegerInterceptor', context.getHandler(), ); if (except) return next.handle(); //////////////////////////////////////////////////////////////////// // extract parent span from headers of request if (!this.tracer) return next.handle(); const req = context.switchToHttp().getRequest(); const res = context.switchToHttp().getResponse(); const parent = this.tracer.extract(FORMAT_HTTP_HEADERS, req.headers); const parentObj = parent ? { childOf: parent } : {}; this.span = this.tracer.startSpan(req.headers.host + req.path, parentObj); /////////////////////////////////////////////////////////////////////////// // get tracing tag from headers of request if (!this.span) return next.handle(); if (req.headers && req.headers[TAGS.TRACING_TAG]) { this.tracing_tag = JSON.parse(req.headers[TAGS.TRACING_TAG]); } for (const key in this.tracing_tag) { const val = this.tracing_tag[key]; this.span.setTag(key, val); } //////////////////////////////////////////////////////////////////////////// const createJaegerInstance = () => { return { // master span instance span: this.span, // tracer instance tracer: this.tracer, // TAGS of opentracing tags: TAGS, // use for remote call axios: (opts: any = undefined) => { if (!opts) return; // inject tracing tag to remote call let options = {}; const headers = {}; headers[TAGS.TRACING_TAG] = JSON.stringify(this.tracing_tag); this.tracer.inject(this.span, FORMAT_HTTP_HEADERS, headers); opts.headers = { ...opts.headers, ...headers }; options = { ...opts }; /////////////////////////////////////////////////////////// return axios(options); }, // log log: (name, content) => { if (!this.span) return; this.span.logEvent(name, content); }, // setup tag setTag: (tag, val) => { if (!this.span) return; this.span.setTag(tag, val); }, // setup mutiple TAGS addTags: (obj) => { if (!this.span && !obj) return; this.span.addTags(obj); }, // setup tracing tag which can pass through all remote call by using Jaeger.request setTracingTag: (tag, val) => { if (!this.span) return; this.span.setTag(tag, val); this.tracing_tag[tag] = val; }, // finish span job finish: () => { if (!this.span) return; this.span.finish(); }, // create new span under master span createSpan: (name, parent) => { const parentObj = parent ? { childOf: parent } : { childOf: this.span }; if (!this.tracer) return; return this.tracer.startSpan(name, parentObj); }, }; }; req.jaeger = createJaegerInstance(); // mark default tag of request req.jaeger.setTag('request.ip', req.ip || UNKNOW); req.jaeger.setTag('request.method', req.method || UNKNOW); req.jaeger.setTag('request.headers', req.headers || UNKNOW); req.jaeger.setTag('request.path', req.path || UNKNOW); req.jaeger.setTag('request.body', req.body || UNKNOW); req.jaeger.setTag('request.query', req.query || UNKNOW); ////////////////////////////////////////////////// // handle request callback if (this.req_cb) { this.req_cb(req, res); } /////////////////////////////////////////////// return next.handle().pipe( tap(() => { // handle respose callback if (this.res_cb) { this.res_cb(req, res); } //mark default tag of response req.jaeger.setTag('response.state', res.statusCode || UNKNOW); req.jaeger.setTag('response.result', res.statusMessage || UNKNOW); req.jaeger.finish(); /////////////////////////////////////////////////// }), ); } }