UNPKG

moleculer

Version:

Fast & powerful microservices framework for Node.JS

309 lines (268 loc) 6.91 kB
"use strict"; const _ = require("lodash"); const BaseTraceExporter = require("./base"); const asyncHooks = require("async_hooks"); const { isFunction } = require("../../utils"); /** * Import types * * @typedef {import("./datadog")} DatadogTraceExporterClass * @typedef {import("./datadog").DatadogTraceExporterOptions} DatadogTraceExporterOptions * @typedef {import("../tracer")} Tracer * @typedef {import("../span")} Span * @typedef {import("../span").SpanLogEntry} SpanLogEntry */ let DatadogSpanContext; let DatadogID; /** * Datadog Trace Exporter with 'dd-trace'. * * @class DatadogTraceExporter * @implements {DatadogTraceExporterClass} */ class DatadogTraceExporter extends BaseTraceExporter { /** * Creates an instance of DatadogTraceExporter. * @param {DatadogTraceExporterOptions?} opts * @memberof DatadogTraceExporter */ constructor(opts) { super(opts); /** @type {DatadogTraceExporterOptions} */ this.opts = _.defaultsDeep(this.opts, { agentUrl: process.env.DD_AGENT_URL || "http://localhost:8126", env: process.env.DD_ENVIRONMENT || null, samplingPriority: "AUTO_KEEP", defaultTags: null, tracerOptions: null }); this.ddTracer = this.opts.tracer; } /** * Initialize Trace Exporter. * * @param {Tracer} tracer * @memberof DatadogTraceExporter */ init(tracer) { super.init(tracer); try { const ddTrace = require("dd-trace").tracer; DatadogSpanContext = require("dd-trace/packages/dd-trace/src/opentracing/span_context"); DatadogID = require("dd-trace/packages/dd-trace/src/id"); if (!this.ddTracer) { this.ddTracer = ddTrace.init( _.defaultsDeep(this.opts.tracerOptions, { url: this.opts.agentUrl }) ); } } catch (err) { /* istanbul ignore next */ this.tracer.broker.fatal( "The 'dd-trace' package is missing! Please install it with 'npm install dd-trace --save' command!", err, true ); } 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); } this.ddScope = this.ddTracer.scope(); const oldGetCurrentTraceID = this.tracer.getCurrentTraceID.bind(this.tracer); this.tracer.getCurrentTraceID = () => { const traceID = oldGetCurrentTraceID(); if (traceID) return traceID; if (this.ddScope) { const span = this.ddScope.active(); if (span) { const spanContext = span.context(); if (spanContext) return spanContext.toTraceId(); } } return null; }; const oldGetActiveSpanID = this.tracer.getActiveSpanID.bind(this.tracer); this.tracer.getActiveSpanID = () => { const spanID = oldGetActiveSpanID(); if (spanID) return spanID; if (this.ddScope) { const span = this.ddScope.active(); if (span) { const spanContext = span.context(); if (spanContext) return spanContext.toSpanId(); } } return null; }; } /** * Stop Trace exporter */ stop() { return this.broker.Promise.resolve(); } /** * Span is started. * * @param {Span} span * @memberof BaseTraceExporter */ spanStarted(span) { if (!this.ddTracer) return null; const serviceName = span.service ? span.service.fullName : null; let parentCtx; if (span.parentID) { parentCtx = new DatadogSpanContext({ traceId: this.convertID(span.traceID), spanId: this.convertID(span.parentID), parentId: this.convertID(span.parentID) }); } const ddSpan = this.ddTracer.startSpan(span.name, { startTime: span.startTime, childOf: parentCtx, tags: this.flattenTags( _.defaultsDeep( {}, span.tags, { span: { kind: "server", type: span.type }, type: span.type, resource: span.tags.action ? span.tags.action.name : undefined, "sampling.priority": this.opts.samplingPriority }, this.defaultTags ) ) }); if (this.opts.env) this.addTags(ddSpan, "env", this.opts.env); this.addTags(ddSpan, "service.name", serviceName); const sc = ddSpan.context(); sc._traceId = this.convertID(span.traceID); sc._spanId = this.convertID(span.id); // Activate span in Datadog tracer const asyncId = asyncHooks.executionAsyncId(); this.ddScope._spans = this.ddScope._spans || {}; const oldSpan = this.ddScope._spans[asyncId]; this.ddScope._spans[asyncId] = ddSpan; span.meta.datadog = { span: ddSpan, asyncId, oldSpan }; } /** * Span is finished. * * @param {Span} span * @memberof DatadogTraceExporter */ spanFinished(span) { if (!this.ddTracer) return null; const item = span.meta.datadog; if (!item) return null; const ddSpan = item.span; if (span.error) { this.addTags(ddSpan, "error", this.errorToObject(span.error)); } this.addLogs(ddSpan, span.logs); ddSpan.finish(span.finishTime); if (item.oldSpan) { this.ddScope._spans[item.asyncId] = item.oldSpan; } else { delete this.ddScope._spans[item.asyncId]; } } /** * Activate the current span inside `dd-trace` library. * * @param {Span} span * @param {Promise} promise * @returns {Promise} * * @memberof DatadogTraceExporter * activatePromise(span, promise) { const asyncId = asyncHooks.executionAsyncId(); const oldSpan = this.ddScope._spans[asyncId]; this.ddScope._spans[asyncId] = span; const finish = (err, data) => { if (oldSpan) { this.ddScope._spans[asyncId] = oldSpan; } else { this.ddScope._destroy(asyncId); } if (err) { if (span && typeof span.addTags === "function") { span.addTags({ "error.type": err.name, "error.msg": err.message, "error.stack": err.stack }); } throw err; } return data; }; return promise .then(res => finish(null, res)) .catch(err => finish(err)); } */ /** * Add tags to span * * @param {Object} span * @param {String} key * @param {any} value * @param {String=} prefix */ addTags(span, key, value, prefix) { const name = prefix ? `${prefix}.${key}` : key; if (value != null && typeof value == "object") { Object.keys(value).forEach(k => this.addTags(span, k, value[k], name)); } else if (value !== undefined) { span.setTag(name, value); } } /** * Add logs to span * * @param {Object} span * @param {SpanLogEntry[]} logs */ addLogs(span, logs) { if (Array.isArray(logs)) { logs.forEach(log => { span.log( { event: log.name, payload: log.fields }, log.time ); }); } } /** * Convert Trace/Span ID to Jaeger format * * @param {String} id * @returns {String} */ convertID(id) { if (id) { if (id.indexOf("-") !== -1) return DatadogID(id.replace(/-/g, "").substring(0, 16)); return DatadogID(id); } return null; } } module.exports = DatadogTraceExporter;