UNPKG

moleculer

Version:

Fast & powerful microservices framework for Node.JS

228 lines (194 loc) 5.05 kB
"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;