UNPKG

@chankamlam/nest-jaeger

Version:

Jaeger middleware to request tracing for nest application

199 lines (181 loc) 6.24 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 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 req_cb: any = undefined; private 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 var options = {}; var 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(); /////////////////////////////////////////////////// }) ); } }