Last active
October 25, 2018 19:51
-
-
Save m1o1/b5264d6048083bc167d2dc5197d8a491 to your computer and use it in GitHub Desktop.
Istio child spans
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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