moleculer
Version:
Fast & powerful microservices framework for Node.JS
228 lines (194 loc) • 5.05 kB
JavaScript
"use strict";
const _ = require("lodash");
const BaseTraceExporter = require("./base");
const { isFunction, isObject } = require("../../utils");
/**
* Import types
*
* @typedef {import("./zipkin")} ZipkinTraceExporterClass
* @typedef {import("./zipkin").ZipkinTraceExporterOptions} ZipkinTraceExporterOptions
* @typedef {import("../tracer")} Tracer
* @typedef {import("../span")} Span
*/
/**
* Trace Exporter for Zipkin.
*
* API v2: https://zipkin.io/zipkin-api/#/
* API v1: https://zipkin.io/pages/data_model.html
*
* @class ZipkinTraceExporter
* @implements {ZipkinTraceExporterClass}
*/
class ZipkinTraceExporter extends BaseTraceExporter {
/**
* Creates an instance of ZipkinTraceExporter.
* @param {ZipkinTraceExporterOptions?} opts
* @memberof ZipkinTraceExporter
*/
constructor(opts) {
super(opts);
/** @type {ZipkinTraceExporterOptions} */
this.opts = _.defaultsDeep(this.opts, {
/** @type {String} Base URL for Zipkin server. */
baseURL: process.env.ZIPKIN_URL || "http://localhost:9411",
/** @type {Number} Batch send time interval in seconds. */
interval: 5,
/** @type {Object} Additional payload options. */
payloadOptions: {
/** @type {Boolean} Set `debug` property in v2 payload. */
debug: false,
/** @type {Boolean} Set `shared` property in v2 payload. */
shared: false
},
/** @type {Object?} Default span tags */
defaultTags: null,
/** @type {Object} Default headers */
headers: {
"Content-Type": "application/json"
}
});
this.queue = [];
}
/**
* Initialize Trace Exporter.
*
* @param {Tracer} tracer
* @memberof ZipkinTraceExporter
*/
init(tracer) {
super.init(tracer);
if (this.opts.interval > 0) {
this.timer = setInterval(() => this.flush(), this.opts.interval * 1000);
this.timer.unref();
}
this.defaultTags = isFunction(this.opts.defaultTags)
? this.opts.defaultTags.call(this, tracer)
: this.opts.defaultTags;
if (this.defaultTags) {
this.defaultTags = this.flattenTags(this.defaultTags, true);
}
}
/**
* Stop Trace exporter
*/
stop() {
if (this.timer) {
clearInterval(this.timer);
this.timer = null;
}
return this.broker.Promise.resolve();
}
/**
* Span is finished.
*
* @param {Span} span
* @memberof ZipkinTraceExporter
*/
spanFinished(span) {
this.queue.push(span);
}
/**
* Flush tracing data to Datadog server
*
* @memberof ZipkinTraceExporter
*/
flush() {
if (this.queue.length === 0) return;
const data = this.generateTracingData();
this.queue.length = 0;
fetch(`${this.opts.baseURL}/api/v2/spans`, {
method: "post",
body: JSON.stringify(data),
headers: this.opts.headers
})
.then(res => {
if (res.status >= 400) {
this.logger.warn(
`Unable to upload tracing spans to Zipkin. Status: ${res.status} ${res.statusText}`
);
} else {
this.logger.debug(
`Tracing spans (${data.length} spans) are uploaded to Zipkin. Status: ${res.statusText}`
);
}
})
.catch(err => {
this.logger.warn(
"Unable to upload tracing spans to Zipkin. Error:" + err.message,
err
);
});
}
/**
* Generate tracing data for Zipkin
*
* @returns {Record<string, any>[]}
* @memberof ZipkinTraceExporter
*/
generateTracingData() {
return this.queue.map(span => this.makePayload(span));
}
/**
* Create Zipkin v2 payload from metric event
*
* @param {Span} span
* @returns {Record<string, any>}
*/
makePayload(span) {
const serviceName = span.service ? span.service.fullName : null;
const payload = {
name: span.name,
kind: "SERVER",
// Trace & span IDs
traceId: this.convertID(span.traceID),
id: this.convertID(span.id),
parentId: this.convertID(span.parentID),
localEndpoint: { serviceName },
remoteEndpoint: { serviceName },
annotations: [],
timestamp: this.convertTime(span.startTime),
duration: this.convertTime(span.duration),
tags: {
service: serviceName,
"span.type": span.type
},
debug: this.opts.payloadOptions.debug,
shared: this.opts.payloadOptions.shared
};
if (span.error) {
if (isObject(span.error)) payload.tags["error"] = span.error.message;
else payload.tags["error"] = "Unknown error";
payload.annotations.push({
value: "error",
endpoint: { serviceName: serviceName, ipv4: "", port: 0 },
timestamp: this.convertTime(span.finishTime)
});
}
Object.assign(
payload.tags,
this.defaultTags || {},
this.flattenTags(span.tags, true),
this.flattenTags(this.errorToObject(span.error), true, "error") || {}
);
return payload;
}
/**
* Convert Context ID to Zipkin format
*
* @param {String} id
* @returns {String}
*/
convertID(id) {
return id ? id.replace(/-/g, "").substring(0, 16) : null;
}
/**
* Convert JS timestamp to microseconds
*
* @param {Number} ts
* @returns {Number}
*/
convertTime(ts) {
return ts != null ? Math.round(ts * 1000) : null;
}
}
module.exports = ZipkinTraceExporter;