zipkin-javascript-opentracing
Version:
An opentracing implementation for zipkin
351 lines (289 loc) • 11 kB
JavaScript
;
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
var _require = require("zipkin"),
Annotation = _require.Annotation,
BatchRecorder = _require.BatchRecorder,
ExplicitContext = _require.ExplicitContext,
TraceId = _require.TraceId,
_require$option = _require.option,
Some = _require$option.Some,
None = _require$option.None,
Tracer = _require.Tracer,
InetAddress = _require.InetAddress,
sampler = _require.sampler,
jsonEncoder = _require.jsonEncoder;
var _require2 = require("zipkin-transport-http"),
HttpLogger = _require2.HttpLogger;
var availableTags = require("opentracing").Tags;
var _require3 = require("opentracing"),
FORMAT_BINARY = _require3.FORMAT_BINARY,
FORMAT_TEXT_MAP = _require3.FORMAT_TEXT_MAP,
FORMAT_HTTP_HEADERS = _require3.FORMAT_HTTP_HEADERS;
var JSON_V2 = jsonEncoder.JSON_V2;
var HttpHeaders = {
TraceId: "x-b3-traceid",
ParentSpanId: "x-b3-parentspanid",
SpanId: "x-b3-spanid",
Sampled: "x-b3-sampled"
};
var startSpanAnnotation = {
client: Annotation.ClientSend,
local: Annotation.ClientSend, // waiting for local PR in zipkin to get merged
server: Annotation.ServerRecv
};
var addressAnnotation = {
client: Annotation.ClientAddr,
local: Annotation.ClientAddr, // waiting for local PR in zipkin to get merged
server: Annotation.ServerAddr
};
var finishSpanAnnotation = {
client: Annotation.ClientRecv,
local: Annotation.ClientRecv, // waiting for local PR in zipkin to get merged
server: Annotation.ServerSend
};
// copied from https://github.com/openzipkin/zipkin-js/blob/08f86b63a5fd7ded60762f537be1845ede588ffa/packages/zipkin/src/tracer/randomTraceId.js
function randomTraceId() {
// === Generate a random 64-bit number in fixed-length hex format
var digits = "0123456789abcdef";
var n = "";
for (var i = 0; i < 16; i++) {
var rand = Math.floor(Math.random() * 16);
n += digits[rand];
}
return n;
}
function makeOptional(val) {
if (val && typeof val.toString === "function" && (val.toString().indexOf("Some") !== -1 || val.toString().indexOf("None") !== -1)) {
return val;
}
if (val != null) {
return new Some(val);
} else {
return None;
}
}
function SpanCreator(_ref) {
var tracer = _ref.tracer,
serviceName = _ref.serviceName,
kind = _ref.kind;
return function () {
_createClass(Span, [{
key: "_constructedFromOutside",
value: function _constructedFromOutside(options) {
return typeof options.traceId === "object" && typeof options.traceId.spanId === "string";
}
}, {
key: "_getTraceId",
value: function _getTraceId(options) {
// construct from give traceId
if (this._constructedFromOutside(options)) {
var _options$traceId = options.traceId,
traceId = _options$traceId.traceId,
parentId = _options$traceId.parentId,
spanId = _options$traceId.spanId,
sampled = _options$traceId.sampled;
return new TraceId({
traceId: makeOptional(traceId),
parentId: makeOptional(parentId),
spanId: spanId,
sampled: makeOptional(sampled)
});
}
// construct with parent
if (options.childOf !== null && typeof options.childOf === "object") {
var parent = options.childOf;
return new TraceId({
traceId: makeOptional(parent.id.traceId),
parentId: makeOptional(parent.id.spanId),
spanId: randomTraceId(),
sampled: parent.id.sampled
});
}
// construct from give traceId
return tracer.createRootId();
}
}]);
function Span(spanName, options) {
_classCallCheck(this, Span);
var id = this._getTraceId(options);
this.id = id;
if (!this._constructedFromOutside(options)) {
tracer.scoped(function () {
tracer.setId(id);
if (spanName) {
tracer.recordAnnotation(new Annotation.Rpc(spanName));
}
tracer.recordServiceName(serviceName);
tracer.recordAnnotation(new startSpanAnnotation[kind]());
});
}
}
_createClass(Span, [{
key: "log",
value: function log(obj) {
var _this = this;
tracer.scoped(function () {
// make sure correct id is set
if (obj) {
tracer.setId(_this.id);
tracer.recordMessage(obj);
}
});
}
}, {
key: "setTag",
value: function setTag(key, value) {
var _this2 = this;
tracer.scoped(function () {
// make sure correct id is set
tracer.setId(_this2.id);
// some tags are treated specially by Zipkin
switch (key) {
case availableTags.PEER_ADDRESS:
if (typeof value !== "string") {
throw new Error("Tag " + availableTags.PEER_ADDRESS + " needs a string");
}
var host = new InetAddress(value.split(":")[0]);
var port = value.split(":")[1] ? parseInt(value.split(":")[1], 10) : 80;
var address = {
serviceName: serviceName,
host: host,
port: port
};
tracer.recordAnnotation(new addressAnnotation[kind](address));
break;
// Otherwise, set arbitrary key/value tags using Zipkin binary annotations
default:
tracer.recordAnnotation(new Annotation.BinaryAnnotation(key, value));
}
});
}
}, {
key: "context",
value: function context() {
var _id = this.id,
spanId = _id.spanId,
traceId = _id.traceId;
return {
toSpanId: function toSpanId() {
return spanId;
},
toTraceId: function toTraceId() {
return traceId;
}
};
}
}, {
key: "finish",
value: function finish() {
var _this3 = this;
tracer.scoped(function () {
// make sure correct id is set
tracer.setId(_this3.id);
tracer.recordAnnotation(new finishSpanAnnotation[kind]());
});
}
}]);
return Span;
}();
}
var Tracing = function () {
function Tracing() {
var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
_classCallCheck(this, Tracing);
// serviceName: the name of the service monitored with this tracer
if (typeof options.serviceName !== "string") {
throw new Error("serviceName option needs to be provided");
}
if (typeof options.recorder !== "object") {
if (typeof options.endpoint !== "string") {
throw new Error("recorder or endpoint option needs to be provided");
}
if (options.endpoint.indexOf("http") === -1) {
throw new Error("endpoint value needs to start with http:// or https://");
}
options.recorder = new BatchRecorder({
logger: new HttpLogger({
endpoint: options.endpoint + "/api/v2/spans",
jsonEncoder: JSON_V2
})
});
}
if (options.kind !== "client" && options.kind !== "server" && options.kind !== "local") {
throw new Error('kind option needs to be provided as either "local", "client" or "server"');
}
options.sampler = options.sampler || new sampler.Sampler(sampler.alwaysSample);
this._serviceName = options.serviceName;
this._zipkinTracer = new Tracer({
ctxImpl: new ExplicitContext(),
recorder: options.recorder,
sampler: options.sampler
});
this._Span = SpanCreator({
tracer: this._zipkinTracer,
serviceName: this._serviceName,
kind: options.kind
});
}
_createClass(Tracing, [{
key: "startSpan",
value: function startSpan(name) {
var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
if (typeof name !== "string") {
throw new Error("startSpan needs an operation name as string as first argument.\n For more details, please see https://github.com/opentracing/specification/blob/master/specification.md#start-a-new-span");
}
return new this._Span(name, options);
}
}, {
key: "inject",
value: function inject(span, format, carrier) {
if (typeof span !== "object") {
throw new Error("inject called without a span");
}
if (format !== Tracing.FORMAT_HTTP_HEADERS) {
throw new Error("inject called with unsupported format");
}
if (typeof carrier !== "object") {
throw new Error("inject called without a carrier object");
}
carrier[HttpHeaders.TraceId] = span.id.traceId;
carrier[HttpHeaders.SpanId] = span.id.spanId;
carrier[HttpHeaders.ParentSpanId] = span.id.parentId;
carrier[HttpHeaders.Sampled] = span.id.sampled.getOrElse("0");
}
}, {
key: "extract",
value: function extract(format, carrier) {
if (format !== Tracing.FORMAT_HTTP_HEADERS) {
throw new Error("extract called with unsupported format");
}
if (typeof carrier !== "object") {
throw new Error("extract called without a carrier");
}
if (!carrier[HttpHeaders.TraceId]) {
return null;
}
// XXX: no empty string here v
// We should send the span name too
// TODO: take a look for span name here: https://github.com/openzipkin/zipkin-go-opentracing/blob/594640b9ef7e5c994e8d9499359d693c032d738c/propagation_ot.go#L26
var span = new this._Span("", {
traceId: {
traceId: carrier[HttpHeaders.TraceId],
parentId: carrier[HttpHeaders.ParentSpanId],
spanId: carrier[HttpHeaders.SpanId],
sampled: carrier[HttpHeaders.Sampled]
}
});
return span;
}
}]);
return Tracing;
}();
// These values should match https://github.com/opentracing/opentracing-javascript/blob/master/src/constants.ts
Tracing.FORMAT_TEXT_MAP = FORMAT_TEXT_MAP;
Tracing.FORMAT_HTTP_HEADERS = FORMAT_HTTP_HEADERS;
Tracing.FORMAT_BINARY = FORMAT_BINARY;
// For testing purposes
Tracing.makeOptional = makeOptional;
module.exports = Tracing;