nestjs-jaeger
Version:
Jaeger middleware to request tracing for nest application
175 lines (160 loc) • 6.01 kB
text/typescript
import { Injectable, NestInterceptor, ExecutionContext, CallHandler,HttpService } 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"
import debug from "debug"
const tags = {
...Tags,
"PROTOCAL":"protocal",
"TRACING_TAG":"tracing-tag",
}
const defaultSampler = {
type: "const",
param: 1
};
const defaultReporter = {
collectorEndpoint: "http://localhost:14268/api/traces",
};
const defaultLogger = {
info: msg => {
console.log("JAEGER INFO ", msg);
},
error: msg => {
console.log("JAEGER ERROR", msg);
}
};
const defaultOptions = { logger: defaultLogger };
const defaultConfig = {
serviceName:"Unknow",
reporter: defaultReporter,
sampler: defaultSampler
};
export class JaegerInterceptor implements NestInterceptor {
private tracer:any = undefined
private span:any = undefined
private tracing_tag:any = {}
private cb:any = undefined
constructor(cfg?:any,opt?:any,cb?:any){
// init tracer
if(!this.tracer){
const config = {...defaultConfig,...cfg}
const options = {...defaultOptions,...opt}
this.tracer = initTracer(config, options)
this.cb = cb
debug("init tracer ...")
}else{
debug("tracer already exsited")
}
}
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 http/https headers
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);
debug("===== headers =====")
debug(req.headers)
// handle tracing tag
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);
}
debug("===== tracing_tag =====")
debug(this.tracing_tag)
const createJaegerInstance = ()=>{
return {
// span instance
span:this.span,
// tracer instance
tracer:this.tracer,
// tags of opentracing
tags,
// use for remote call
axios : (opts:any=undefined)=>{
if(!opts) return
// handle tracing tag
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}
debug("==========request headers======")
debug(opts.headers)
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
debug("===== tracing_tag =====")
debug(this.tracing_tag)
},
// 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)
req.jaeger.setTag("request.method",req.method)
req.jaeger.setTag("request.headers",req.headers)
req.jaeger.setTag("request.path",req.path)
req.jaeger.setTag("request.body",req.body)
req.jaeger.setTag("request.query",req.query)
// handle callback
if(this.cb){
this.cb(req,res)
}
return next
.handle()
.pipe(
tap(() => {
//mark default tag of response
req.jaeger.setTag("response.state", res.statusCode);
req.jaeger.setTag("response.result", res.statusMessage);
req.jaeger.finish()
}),
);
}
}