moleculer
Version:
Fast & powerful microservices framework for Node.JS
333 lines (286 loc) • 7.77 kB
JavaScript
/*
* moleculer
* Copyright (c) 2025 MoleculerJS (https://github.com/moleculerjs/moleculer)
* MIT Licensed
*/
"use strict";
const _ = require("lodash");
const BaseTraceExporter = require("./base");
const { isFunction } = require("../../utils");
/**
* Import types
*
* @typedef {import("./jaeger")} JaegerTraceExporterClass
* @typedef {import("./jaeger").JaegerTraceExporterOptions} JaegerTraceExporterOptions
* @typedef {import("../tracer")} Tracer
* @typedef {import("../span")} Span
* @typedef {import("../span").SpanLogEntry} SpanLogEntry
*/
let Jaeger, GuaranteedThroughputSampler, RemoteControlledSampler, UDPSender, HTTPSender;
/**
* Trace Exporter for Jaeger.
*
* http://jaeger.readthedocs.io/en/latest/getting_started/#all-in-one-docker-image
*
* @class JaegerTraceExporter
* @implements {JaegerTraceExporterClass}
*/
class JaegerTraceExporter extends BaseTraceExporter {
/**
* Creates an instance of JaegerTraceExporter.
* @param {JaegerTraceExporterOptions?} opts
* @memberof JaegerTraceExporter
*/
constructor(opts) {
super(opts);
/** @type {JaegerTraceExporterOptions} */
this.opts = _.defaultsDeep(this.opts, {
/** @type {String?} HTTP Reporter endpoint - is set, HTTP Reporter will be used. */
endpoint: null,
/** @type {String} UDP Sender host option. */
host: "127.0.0.1",
/** @type {Number?} UDP Sender port option. */
port: 6832,
/** @type {Object?} Sampler configuration. */
sampler: {
/** @type {String?} Sampler type */
type: "Const",
/** @type {Object?} Sampler specific options. */
options: {}
},
/** @type {Object?} Additional options for `Jaeger.Tracer` */
tracerOptions: {},
/** @type {Object?} Default span tags */
defaultTags: null,
/** @type {boolean?} Use legacy mode with 64 bit trace ids*/
legacyMode: true
});
this.tracers = {};
}
/**
* Initialize Trace Exporter.
*
* @param {Tracer} tracer
* @memberof JaegerTraceExporter
*/
init(tracer) {
super.init(tracer);
try {
Jaeger = require("jaeger-client");
GuaranteedThroughputSampler =
require("jaeger-client/dist/src/samplers/guaranteed_throughput_sampler").default;
RemoteControlledSampler =
require("jaeger-client/dist/src/samplers/remote_sampler").default;
UDPSender = require("jaeger-client/dist/src/reporters/udp_sender").default;
HTTPSender = require("jaeger-client/dist/src/reporters/http_sender").default;
} catch (err) {
/* istanbul ignore next */
this.tracer.broker.fatal(
"The 'jaeger-client' package is missing! Please install it with 'npm install jaeger-client --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);
}
}
/**
* Stop Trace exporter
*/
stop() {
if (this.tracers) {
return this.broker.Promise.all(
Object.values(this.tracers).map(tracer => tracer.close())
);
}
return this.broker.Promise.resolve();
}
/**
* Get reporter instance for Tracer
*
*/
getReporter() {
let reporter;
if (this.opts.endpoint) {
reporter = new HTTPSender({ endpoint: this.opts.endpoint, logger: this.logger });
} else {
reporter = new UDPSender({
host: this.opts.host,
port: this.opts.port,
logger: this.logger
});
}
return new Jaeger.RemoteReporter(reporter);
}
/**
* Get sampler instance for Tracer
*
* @param {string} serviceName
*/
getSampler(serviceName) {
if (isFunction(this.opts.sampler)) return this.opts.sampler;
if (this.opts.sampler.type == "RateLimiting")
return new Jaeger.RateLimitingSampler(
this.opts.sampler.options.maxTracesPerSecond,
this.opts.sampler.options.initBalance
);
if (this.opts.sampler.type == "Probabilistic")
return new Jaeger.ProbabilisticSampler(this.opts.sampler.options.samplingRate);
if (this.opts.sampler.type == "GuaranteedThroughput")
return new GuaranteedThroughputSampler(
this.opts.sampler.options.lowerBound,
this.opts.sampler.options.samplingRate
);
if (this.opts.sampler.type == "RemoteControlled")
return new RemoteControlledSampler(serviceName, this.opts.sampler.options);
return new Jaeger.ConstSampler(
this.opts.sampler.options && this.opts.sampler.options.decision != null
? this.opts.sampler.options.decision
: 1
);
}
/**
* Get a tracer instance by service name
*
* @param {string} serviceName
*/
getTracer(serviceName) {
if (this.tracers[serviceName]) return this.tracers[serviceName];
const sampler = this.getSampler(serviceName);
const reporter = this.getReporter();
const tracer = new Jaeger.Tracer(
serviceName,
reporter,
sampler,
Object.assign({ logger: this.logger }, this.opts.tracerOptions)
);
this.tracers[serviceName] = tracer;
return tracer;
}
/**
* Span is finished.
*
* @param {Span} span
* @memberof JaegerTraceExporter
*/
spanFinished(span) {
this.generateJaegerSpan(span);
}
/**
* Create Jaeger tracing span
*
* @param {Span} span
* @returns {Object}
*/
generateJaegerSpan(span) {
const serviceName = span.service ? span.service.fullName : "no-service";
const tracer = this.getTracer(serviceName);
let parentCtx;
if (span.parentID) {
parentCtx = new Jaeger.SpanContext(
this.convertTraceID(span.traceID), // traceId,
this.convertSpanID(span.parentID), // spanId,
null, // parentId,
null, // traceIdStr
null, // spanIdStr
null, // parentIdStr
1, // flags
{}, // baggage
"" // debugId
);
}
const jaegerSpan = tracer.startSpan(span.name, {
startTime: span.startTime,
childOf: parentCtx,
tags: this.flattenTags(
_.defaultsDeep(
{
"span.type": span.type
},
span.tags,
this.defaultTags
)
)
});
this.addLogs(jaegerSpan, span.logs);
this.addTags(jaegerSpan, "service", serviceName);
this.addTags(
jaegerSpan,
Jaeger.opentracing.Tags.SPAN_KIND,
Jaeger.opentracing.Tags.SPAN_KIND_RPC_SERVER
);
const sc = jaegerSpan.context();
sc.traceId = this.convertTraceID(span.traceID);
sc.spanId = this.convertSpanID(span.id);
if (span.error) {
this.addTags(jaegerSpan, Jaeger.opentracing.Tags.ERROR, true);
this.addTags(jaegerSpan, "error", this.errorToObject(span.error));
}
jaegerSpan.finish(span.finishTime);
return jaegerSpan;
}
/**
* Add logs to span
*
* @param {any} span
* @param {SpanLogEntry[]} logs
*/
addLogs(span, logs) {
if (Array.isArray(logs)) {
logs.forEach(log => {
span.log(
{
event: log.name,
payload: log.fields
},
log.time
);
});
}
}
/**
* Add tags to span
*
* @param {any} 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);
}
}
/**
* Convert Trace/Span ID to Jaeger format
*
* @param {String} id
* @returns {Buffer}
*/
convertSpanID(id) {
if (id) return Buffer.from(id.replace(/-/g, "").substring(0, 16), "hex");
return null;
}
/**
* Convert Trace ID to Jaeger format. Return 128 bit IDs or 64 bit IDs if legacy is set.
*
* @param {String} id
* @returns {String}
*/
convertTraceID(id) {
if (id)
return Buffer.from(
id.replace(/-/g, "").substring(0, this.opts.legacyMode ? 16 : 32),
"hex"
);
return null;
}
}
module.exports = JaegerTraceExporter;