moleculer
Version:
Fast & powerful microservices framework for Node.JS
309 lines (268 loc) • 6.91 kB
JavaScript
"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;