Distributed tracing toolset for expresso. Traces requests using Jaeger.
The tracer file will hook in require('http')
to auto-instrument
every request that is either coming in or going out of the service. This is done by @opentelemetry/node and @opentelemetry/plugin-http.
Meanwhile, the middleware will add some extra info to the traced requests (request/response body, headers and query). This is done using express-mung
Installing:
$ npm install @expresso/tracing
The tracer module is responsible for adding the required headers to all outgoing requests, plus reading headers from and logging incoming requests.
IMPORTANT: This should be intialized on the entrypoint of your application, before you require
@expresso/app
or any other module thatrequire
s node's nativehttp
module.Modules required before the initializing the tracer will not be instrumented
import tracer, { IExpressoTracerConfig } from '@expresso/tracer'
const tracerConfig: IExpressoTracerConfig = {
jaeger: {
serviceName: process.env.JAEGER_SERVICE_NAME,
host: process.env.JAEGER.HOST
}
}
tracer.init(config)
To know more about the config object, see Configuration Object
The middleware is used to gather some more information from the request and response objects (body, headers and query)
and add it to the jaeger span. This will also add a Jaeger-Trace-Id
header to all of your responses.
import expresso from '@expresso/app'
import tracing from '@expresso/tracing'
export const app = expresso((app, config) => {
app.use(tracing.factory({
before: (body, req, res, span, tracer) => { /* do something with the parameters here */ },
after: (body, req, res, span, tracer) => { /* do something with the parameters here */ }
}))
/**
* All of your enpoints must come after the middleware
* Endpoints registered before the middleware will not have
* info about their request and response bodies, headers and queries
*/
app.get('/endpoint', routes.endpoint.factory(config))
})
The middleware factory receives an object with two properties. Both properties are functions that receive the response body,current request, response, span an tracer.
-
before
: Will be ran before the middleware does anything to the span. Usefor for removing fields you don't want to be logged to jaeger. -
after
: Will be ran after the span is processed by the middleware, but before it is sent to Jeager.
The configuration object has the following type:
export type IExpressoTracerConfig = {
jaeger: {
// Functions to use for logging
logger?: types.Logger;
// Name of the service (this appears on jaeger)
serviceName: string;
// Tags to refer to when searching
tags?: Tag[];
// Jaeeger host
host?: string;
// Jaeger port
port?: number;
maxPacketSize?: number;
/** Force a flush on shutdown */
forceFlush?: boolean;
/** Time to wait for an onShutdown flush to finish before closing the sender */
flushTimeout?: number;
},
tracer?: {
/**
* Binary formatter which can serialize/deserialize Spans.
*/
binaryFormat?: BinaryFormat;
/**
* Attributed that will be applied on every span created by Tracer.
* Useful to add infrastructure and environment information to your spans.
*/
defaultAttributes?: Attributes;
/**
* HTTP text formatter which can inject/extract Spans.
*/
httpTextFormat?: HttpTextFormat;
/**
* User provided logger.
*/
logger?: Logger;
/** level of logger. */
logLevel?: LogLevel;
/**
* Sampler determines if a span should be recorded or should be a NoopSpan.
*/
sampler?: Sampler;
/**
* Scope manager keeps context across in-process operations.
*/
scopeManager?: ScopeManager;
/** Trace Parameters */
traceParams?: TraceParams;
}
}
Because this uses opentracing's module, you are able to get the tracer and, consequently, the curren span:
import * as opentracing
() => { // suppose this is yout route
const tracer = opentracing.getTracer() // returns the global tracer
const span = tracer.getCurrentSpan() // This might return undefined. Be careful!
span?.addEvent('findUser', { userId: 'some-user-id' })
}
More than that, you can create child spans:
import * as opentracing
import database from './database' // supose this is your db layer
() => { // suppose this is yout route
const tracer = opentracing.getTracer() // returns the global tracer
const parent = tracer.getCurrentSpan() // This might return undefined. Be careful!
const span = tracer.startSpan('find user', { parent })
span.setAttribute('userId', 'some-user-id')
await database.findUser('some-user-id')
span.end()
}
Easy, right? That way you can track any sort of event or operation you want.