moleculer
Version:
Fast & powerful microservices framework for Node.JS
322 lines (266 loc) • 6.83 kB
JavaScript
"use strict";
const _ = require("lodash");
const r = _.repeat;
const kleur = require("kleur");
const { humanize, isFunction } = require("../../utils");
const BaseTraceExporter = require("./base");
/**
* Import types
*
* @typedef {import("./console")} ConsoleTraceExporterClass
* @typedef {import("./console").ConsoleTraceExporterOptions} ConsoleTraceExporterOptions
* @typedef {import("../tracer")} Tracer
* @typedef {import("../span")} Span
*/
/**
* Console Trace Exporter only for debugging
*
* @class ConsoleTraceExporter
* @implements {ConsoleTraceExporterClass}
*/
class ConsoleTraceExporter extends BaseTraceExporter {
/**
* Creates an instance of ConsoleTraceExporter.
* @param {ConsoleTraceExporterOptions?} opts
* @memberof ConsoleTraceExporter
*/
constructor(opts) {
super(opts);
/** @type {ConsoleTraceExporterOptions} */
this.opts = _.defaultsDeep(this.opts, {
logger: null,
colors: true,
width: 100,
gaugeWidth: 40
});
if (!this.opts.colors) kleur.enabled = false;
this.spans = {};
}
/**
* Initialize Trace Exporter.
*
* @param {Tracer} tracer
* @memberof ConsoleTraceExporter
*/
init(tracer) {
super.init(tracer);
}
/**
* Stop Trace exporter
*/
stop() {
this.spans = {};
return this.broker.Promise.resolve();
}
/**
* Span is started.
*
* @param {Span} span
* @memberof ConsoleTraceExporter
*/
spanStarted(span) {
this.spans[span.id] = {
span,
children: []
};
if (span.parentID) {
const parentItem = this.spans[span.parentID];
if (parentItem) parentItem.children.push(span.id);
}
}
/**
* Span is finished.
*
* @param {Span} span
* @memberof ConsoleTraceExporter
*/
spanFinished(span) {
//this.log(span);
if (!this.spans[span.parentID]) {
this.printRequest(span.id);
// remove old printed requests
this.removeSpanWithChildren(span.id);
}
}
/**
* Remove a finished span with children.
*
* @param {String} spanID
* @memberof ConsoleTraceExporter
*/
removeSpanWithChildren(spanID) {
const span = this.spans[spanID];
if (span) {
if (span.children && span.children.length > 0) {
span.children.forEach(child => this.removeSpanWithChildren(child));
}
delete this.spans[spanID];
}
}
drawTableTop() {
this.log(kleur.grey("┌" + r("─", this.opts.width - 2) + "┐"));
}
drawHorizonalLine() {
this.log(kleur.grey("├" + r("─", this.opts.width - 2) + "┤"));
}
drawLine(text) {
this.log(kleur.grey("│ ") + text + kleur.grey(" │"));
}
drawTableBottom() {
this.log(kleur.grey("└" + r("─", this.opts.width - 2) + "┘"));
}
getAlignedTexts(str, space) {
const len = str.length;
let left;
if (len <= space) left = str + r(" ", space - len);
else {
left = str.slice(0, Math.max(space - 3, 0));
left += r(".", Math.min(3, space));
}
return left;
}
drawGauge(gstart, gstop) {
const gw = this.opts.gaugeWidth;
const p1 = Math.floor((gw * gstart) / 100);
const p2 = Math.max(Math.floor((gw * gstop) / 100) - p1, 1);
const p3 = Math.max(gw - (p1 + p2), 0);
return [
kleur.grey("["),
kleur.grey(r(".", p1)),
r("■", p2),
kleur.grey(r(".", p3)),
kleur.grey("]")
].join("");
}
getCaption(span) {
let caption = span.name;
if (span.tags.fromCache) caption += " *";
if (span.tags.remoteCall) caption += " »";
if (span.error) caption += " ×";
return caption;
}
getColor(span) {
let c = kleur.bold;
if (span.tags.fromCache) c = c().yellow;
if (span.tags.remoteCall) c = c().cyan;
if (span.duration == null) c = c().grey;
if (span.error) c = c().red;
return c;
}
getTraceInfo(main) {
let depth = 0;
let total = 0;
let check = (item, level, parents) => {
item.level = level;
item.parents = parents || [];
total++;
if (level > depth) depth = level;
if (item.children.length > 0) {
item.children.forEach((spanID, idx) => {
const span = this.spans[spanID];
span.first = idx === 0;
span.last = idx === item.children.length - 1;
check(span, item.level + 1, [].concat(item.parents, [item]));
});
}
};
check(main, 1);
return { depth, total };
}
getSpanIndent(spanItem) {
if (spanItem.level > 1) {
let s = spanItem.parents
.map((item, idx) => {
if (idx > 0) return item.last ? " " : "│ ";
return "";
})
.join("");
s += spanItem.last ? "└─" : "├─";
return s + (spanItem.children.length > 0 ? "┬─" : "──") + " ";
}
return "";
}
/**
* Print a span row
*
* @param {Object} spanItem
* @param {Object} mainItem
* @param {number} level
*/
printSpanTime(spanItem, mainItem, level) {
const span = spanItem.span;
const mainSpan = mainItem.span;
const margin = 2 * 2;
const w = (this.opts.width || 80) - margin;
const gw = this.opts.gaugeWidth || 40;
const time = span.duration == null ? "?" : humanize(span.duration);
const indent = this.getSpanIndent(spanItem);
const caption = this.getCaption(span);
const info =
kleur.grey(indent) +
this.getAlignedTexts(caption, w - gw - 3 - time.length - 1 - indent.length) +
" " +
time;
const startTime = span.startTime || mainSpan.startTime;
const finishTime = span.finishTime || mainSpan.finishTime;
let gstart =
((startTime - mainSpan.startTime) / (mainSpan.finishTime - mainSpan.startTime)) * 100;
let gstop =
((finishTime - mainSpan.startTime) / (mainSpan.finishTime - mainSpan.startTime)) * 100;
if (Number.isNaN(gstart) && Number.isNaN(gstop)) {
gstart = 0;
gstop = 100;
}
if (gstop > 100) gstop = 100;
const c = this.getColor(span);
this.drawLine(c(info + " " + this.drawGauge(gstart, gstop)));
if (spanItem.children.length > 0)
spanItem.children.forEach(spanID =>
this.printSpanTime(this.spans[spanID], mainItem, level + 1)
);
}
/**
* Print request traces
*
* @param {String} id
*/
printRequest(id) {
const main = this.spans[id];
if (!main) return; // Async span
const margin = 2 * 2;
const w = this.opts.width - margin;
this.drawTableTop();
const { total, depth } = this.getTraceInfo(main);
const truncatedID = this.getAlignedTexts(
id,
w -
"ID: ".length -
"Depth: ".length -
("" + depth).length -
"Total: ".length -
("" + total).length -
2
);
const line =
kleur.grey("ID: ") +
kleur.bold(truncatedID) +
" " +
kleur.grey("Depth: ") +
kleur.bold(depth) +
" " +
kleur.grey("Total: ") +
kleur.bold(total);
this.drawLine(line);
this.drawHorizonalLine();
this.printSpanTime(main, main, 1);
this.drawTableBottom();
}
log(...args) {
if (isFunction(this.opts.logger)) {
return this.opts.logger(...args);
} else {
return this.logger.info(...args);
}
}
}
module.exports = ConsoleTraceExporter;