Skip to content

Instantly share code, notes, and snippets.

@m1o1
Last active October 25, 2018 19:51
Show Gist options
  • Save m1o1/b5264d6048083bc167d2dc5197d8a491 to your computer and use it in GitHub Desktop.
Save m1o1/b5264d6048083bc167d2dc5197d8a491 to your computer and use it in GitHub Desktop.
Istio child spans
import express = require("express");
import bp = require("body-parser");
import rp = require("request-promise-native");
import opentracing = require('opentracing');
import clsHooked = require("cls-hooked");
import promClient = require("prom-client");
import * as jaeger from "jaeger-client";
const initTracer = jaeger.initTracer;
const PrometheusMetricsFactory = jaeger.PrometheusMetricsFactory;
const httpContext = clsHooked.createNamespace("testexpress-http-context");
var config = {
serviceName: 'testexpress-chart',
sampler: {
type: "const",
param: 1,
},
reporter: {
logSpans: true,
// configure the tracer to send spans over HTTP directly to the collector
collectorEndpoint: "http://jaeger-collector.istio-system.svc.cluster.local:14268/api/traces",
}
};
var namespace = config.serviceName;
var options = {
tags: {
'testexpress-chart.version': '1.0.0',
},
logger: {
info(msg: any) {
console.log("INFO ", msg);
},
error(msg: any) {
console.log("ERROR", msg);
},
},
metrics: new PrometheusMetricsFactory(promClient, namespace.replace("-", "_")),
// metrics: metrics, # see https://github.com/jaegertracing/jaeger-client-node#metrics-and-logging
// logger: logger,
};
const tracer: opentracing.Tracer = initTracer(config, options)
const codec = new jaeger.ZipkinB3TextMapCodec({ urlEncoding: true });
(tracer as any).registerInjector(opentracing.FORMAT_HTTP_HEADERS, codec);
(tracer as any).registerExtractor(opentracing.FORMAT_HTTP_HEADERS, codec);
opentracing.initGlobalTracer(tracer);
const app = express();
const port = 3000;
function bindNamespace(namespace: clsHooked.Namespace): express.RequestHandler {
return (req, res, next) => {
namespace.bindEmitter(req);
namespace.bindEmitter(res);
namespace.run(() => {
next();
});
};
}
// express middleware to pull out span context from HTTP headers
function getSpanContext(namespace: clsHooked.Namespace): express.RequestHandler {
return (req, res, next) => {
const spanContext = tracer.extract(opentracing.FORMAT_HTTP_HEADERS, req.headers);
namespace.set("spanContext", spanContext || new opentracing.SpanContext());
next();
};
}
function getRequestIdHeader(namespace: clsHooked.Namespace): express.RequestHandler {
return (req, _res, next) => {
namespace.set("x-request-id", {
"x-request-id": req.header("x-request-id")
});
next();
};
}
function getOTSpanContextHeader(namespace: clsHooked.Namespace): express.RequestHandler {
return (req, _res, next) => {
namespace.set("x-ot-span-context", {
"x-ot-span-context": req.header("x-ot-span-context")
});
next();
};
}
// body-parser json middleware
app.use(bp.json());
app.use(bindNamespace(httpContext));
app.use(getSpanContext(httpContext));
app.use(getRequestIdHeader(httpContext));
app.use(getOTSpanContextHeader(httpContext));
app.get('/', async (req: express.Request, res: express.Response) => {
const headers = getTracingHeaders();
const options = {
uri: "http://time-service",
headers,
json: true,
};
let timeResponse: any;
try {
timeResponse = await rp.get(options);
} catch(err) {
timeResponse = "<unavailable>";
}
let response: any = {
message: "Hello world!",
time: timeResponse,
value: await getCalculatedValue(),
};
res.send(response);
});
app.listen(port, () => console.log(`Example app listening on port ${port}!`));
async function getCalculatedValue(): Promise<number> {
return new Promise<number>((resolve, _reject) => {
const spanContext = httpContext.get("spanContext") as opentracing.SpanContext;
const span = tracer.startSpan('testexpress_calculation', {
childOf: spanContext,
});
span.setTag("testkey", "testvalue");
setTimeout(()=>{
let calculatedValue: number;
try {
// some pretend, long-running service
calculatedValue = 10;
span.log({
"event": "format",
"value": calculatedValue,
});
} finally {
// put inside a "finally" block in case something above hypothetically throws an exception
span.finish();
}
resolve(calculatedValue);
}, 250);
});
}
function getTracingHeaders(): any {
let headers: any = {};
const spanContext = httpContext.get("spanContext") as opentracing.SpanContext;
if (spanContext) {
opentracing.globalTracer().inject(spanContext, opentracing.FORMAT_HTTP_HEADERS, headers);
}
headers = {
...httpContext.get("x-request-id"),
...headers,
...httpContext.get("x-ot-span-context"),
};
if (!headers["x-b3-parentspanid"]) { // to get around bug (https://github.com/jaegertracing/jaeger-client-node/issues/304)
delete headers["x-b3-parentspanid"];
}
return headers;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment