@stoplight/moleculer
Version:
Fast & powerful microservices framework for Node.JS
327 lines (277 loc) • 6.52 kB
JavaScript
/*
* moleculer
* Copyright (c) 2020 MoleculerJS (https://github.com/moleculerjs/moleculer)
* MIT Licensed
*/
"use strict";
const _ = require("lodash");
const Exporters = require("./exporters");
//const AsyncStorage = require("../async-storage");
const RateLimiter = require("./rate-limiter");
const Span = require("./span");
const { isFunction } = require("../utils");
/**
* Moleculer Tracer class
*/
class Tracer {
/**
* Creates an instance of Tracer.
*
* @param {ServiceBroker} broker
* @param {Object} opts
* @memberof Tracer
*/
constructor(broker, opts) {
this.broker = broker;
this.logger = broker.getLogger("tracer");
if (opts === true || opts === false) opts = { enabled: opts };
this.opts = _.defaultsDeep({}, opts, {
enabled: true,
exporter: null,
sampling: {
// Constants sampling
rate: 1.0, // 0.0 - Never, 1.0 > x > 0.0 - Fix, 1.0 - Always
// Ratelimiting sampling https://opencensus.io/tracing/sampling/ratelimited/
tracesPerSecond: null, // 1: 1 trace / sec, 5: 5 traces / sec, 0.1: 1 trace / 10 secs
minPriority: null
},
actions: true,
events: false,
errorFields: ["name", "message", "code", "type", "data"],
stackTrace: false,
defaultTags: null,
tags: {
action: null,
event: null
}
});
if (this.opts.stackTrace && this.opts.errorFields.indexOf("stack") === -1)
this.opts.errorFields.push("stack");
this.sampleCounter = 0;
if (this.opts.sampling.tracesPerSecond != null && this.opts.sampling.tracesPerSecond > 0) {
this.rateLimiter = new RateLimiter({
tracesPerSecond: this.opts.sampling.tracesPerSecond
});
}
//this.scope = new AsyncStorage(this.broker);
//this.scope.enable();
//this._scopeEnabled = true;
if (this.opts.enabled) this.logger.info("Tracing: Enabled");
}
/**
* Initialize Tracer.
*/
init() {
if (this.opts.enabled) {
this.defaultTags = isFunction(this.opts.defaultTags)
? this.opts.defaultTags.call(this, this)
: this.opts.defaultTags;
// Create Exporter instances
if (this.opts.exporter) {
const exporters = Array.isArray(this.opts.exporter)
? this.opts.exporter
: [this.opts.exporter];
this.exporter = _.compact(exporters).map(r => {
const exporter = Exporters.resolve(r);
exporter.init(this);
return exporter;
});
const exporterNames = this.exporter.map(exporter =>
this.broker.getConstructorName(exporter)
);
this.logger.info(
`Tracing exporter${exporterNames.length > 1 ? "s" : ""}: ${exporterNames.join(
", "
)}`
);
}
}
}
/**
* Stop Tracer.
*/
stop() {
if (this.exporter) {
return this.broker.Promise.all(this.exporter.map(r => r.stop()));
}
return this.broker.Promise.resolve();
}
/**
* Check tracing is enabled
*
* @returns {boolean}
* @memberof MetricRegistry
*/
isEnabled() {
return this.opts.enabled;
}
/**
* Disable trace hooks and clear the store - noop if scope is already stopped
*
* @memberof Tracer
*
stopAndClearScope() {
if (this._scopeEnabled) {
this.scope.stop();
this._scopeEnabled = false;
}
}*/
/**
* Renable the trace hooks - noop if scope is already enabled
*
* @memberof Tracer
*
restartScope() {
if (!this._scopeEnabled) {
this.scope.enable();
this._scopeEnabled = true;
}
}*/
/**
* Decide that span should be sampled.
*
* @param {Span} span
* @returns {Boolean}
* @memberof Tracer
*/
shouldSample(span) {
if (this.opts.sampling.minPriority != null) {
if (span.priority < this.opts.sampling.minPriority) return false;
}
if (this.rateLimiter) {
return this.rateLimiter.check();
}
if (this.opts.sampling.rate == 0) return false;
if (this.opts.sampling.rate == 1) return true;
if (++this.sampleCounter * this.opts.sampling.rate >= 1.0) {
this.sampleCounter = 0;
return true;
}
return false;
}
/**
* Start a new Span.
*
* @param {String} name
* @param {Object?} opts
* @returns {Span}
*
* @memberof Tracer
*/
startSpan(name, opts = {}) {
let parentOpts = {};
if (opts.parentSpan) {
parentOpts.traceID = opts.parentSpan.traceID;
parentOpts.parentID = opts.parentSpan.id;
parentOpts.sampled = opts.parentSpan.sampled;
}
const span = new Span(
this,
name,
Object.assign(
{
type: "custom",
defaultTags: this.defaultTags
},
parentOpts,
opts,
{ parentSpan: undefined }
)
);
span.start();
return span;
}
/**
* Invoke Exporter method.
*
* @param {String} method
* @param {Array<any>} args
* @memberof Tracer
*/
invokeExporter(method, args) {
if (this.exporter) {
this.exporter.forEach(exporter => exporter[method].apply(exporter, args));
}
}
/**
* Set the active span
*
* @param {Span} span
* @memberof Tracer
*
setCurrentSpan(span) {
const state = this.scope.getSessionData() || {
spans: []
};
state.spans.push(span);
this.scope.setSessionData(state);
span.meta.state = state;
}*/
/**
* Remove the active span (because async block destroyed)
*
* @param {Span} span
* @memberof Tracer
*
removeCurrentSpan(span) {
const state = span.meta.state || this.scope.getSessionData();
if (state && state.spans.length > 0) {
const idx = state.spans.indexOf(span);
if (idx >= 0)
state.spans.splice(idx, 1);
}
}*/
/**
* Get the current active span
*
* @returns {Span}
* @memberof Tracer
*
getCurrentSpan() {
const state = this.scope.getSessionData();
return state ? state.spans[state.spans.length - 1] : null;
}*/
/**
* Get the current trace ID
*
* @returns
* @memberof Tracer
*/
getCurrentTraceID() {
return null;
//const span = this.getCurrentSpan();
//return span ? span.traceID : null;
}
/**
* Get the active span ID (for the next span as parent ID)
*
* @returns
* @memberof Tracer
*/
getActiveSpanID() {
return null;
//const span = this.getCurrentSpan();
//return span ? span.id : null;
}
/**
* Called when a span started. Call exporters.
*
* @param {Span} span
* @memberof Tracer
*/
spanStarted(span) {
//this.setCurrentSpan(span);
if (span.sampled) this.invokeExporter("spanStarted", [span]);
}
/**
* Called when a span finished. Call exporters.
*
* @param {Span} span
* @memberof Tracer
*/
spanFinished(span) {
//this.removeCurrentSpan(span);
if (span.sampled) this.invokeExporter("spanFinished", [span]);
}
}
module.exports = Tracer;