UNPKG

@stoplight/moleculer

Version:

Fast & powerful microservices framework for Node.JS

226 lines (191 loc) 4.84 kB
"use strict"; const _ = require("lodash"); const fetch = require("node-fetch"); const BaseTraceExporter = require("./base"); const { isFunction } = require("../../utils"); /** * Trace Exporter for Zipkin. * * API v2: https://zipkin.io/zipkin-api/#/ * API v1: https://zipkin.io/pages/data_model.html * * Running Zipkin in Docker: * * docker run -d -p 9411:9411 --name=zipkin openzipkin/zipkin * * @class ZipkinTraceExporter */ class ZipkinTraceExporter extends BaseTraceExporter { /** * Creates an instance of ZipkinTraceExporter. * @param {Object?} opts * @memberof ZipkinTraceExporter */ constructor(opts) { super(opts); this.opts = _.defaultsDeep(this.opts, { /** @type {String} Base URL for Zipkin server. */ baseURL: process.env.ZIPKIN_URL || "http://localhost:9411", /** @type {String} Zipkin REST API version. */ //version: "v2", /** @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); fetch.Promise = this.broker.Promise; 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 {Array<Object>} * @memberof ZipkinTraceExporter */ generateTracingData() { return this.queue.map(span => this.makePayload(span)); } /** * Create Zipkin v2 payload from metric event * * @param {Span} span * @returns {Object} */ 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) { payload.tags["error"] = span.error.message; 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;