autotel
Version:
Write Once, Observe Anywhere
232 lines (230 loc) • 6.67 kB
JavaScript
let _opentelemetry_api = require("@opentelemetry/api");
//#region src/pretty-console-exporter.ts
/**
* Export result code constants (avoid importing @opentelemetry/core)
*/
const ExportResultCode = {
SUCCESS: 0,
FAILED: 1
};
/**
* ANSI escape codes for terminal colors (zero dependencies)
*/
const ANSI = {
reset: "\x1B[0m",
bold: "\x1B[1m",
dim: "\x1B[2m",
green: "\x1B[32m",
red: "\x1B[31m",
yellow: "\x1B[33m",
blue: "\x1B[34m",
cyan: "\x1B[36m",
gray: "\x1B[90m"
};
/**
* Pretty Console Exporter - colorized, hierarchical span output for development
*
* Features:
* - Colorized status indicators (✓ green, ✗ red)
* - Duration with color coding (fast=green, medium=yellow, slow=red)
* - Hierarchical tree view showing parent-child relationships
* - Attribute display with truncation
* - Error message highlighting
*/
var PrettyConsoleExporter = class {
options;
constructor(options = {}) {
this.options = {
colors: options.colors ?? process.stdout?.isTTY ?? false,
showAttributes: options.showAttributes ?? true,
maxValueLength: options.maxValueLength ?? 50,
showScope: options.showScope ?? true,
hideAttributes: options.hideAttributes ?? [],
showTraceId: options.showTraceId ?? false
};
}
/**
* Export spans with pretty formatting
*/
export(spans, resultCallback) {
if (spans.length === 0) {
resultCallback({ code: ExportResultCode.SUCCESS });
return;
}
try {
const traceGroups = this.groupByTrace(spans);
for (const [traceId, traceSpans] of traceGroups) this.printTrace(traceId, traceSpans);
resultCallback({ code: ExportResultCode.SUCCESS });
} catch {
resultCallback({ code: ExportResultCode.SUCCESS });
}
}
/**
* Group spans by their trace ID
*/
groupByTrace(spans) {
const groups = /* @__PURE__ */ new Map();
for (const span of spans) {
const traceId = span.spanContext().traceId;
const group = groups.get(traceId) ?? [];
group.push(span);
groups.set(traceId, group);
}
return groups;
}
/**
* Print a single trace with all its spans as a tree
*/
printTrace(traceId, spans) {
const sorted = [...spans].toSorted((a, b) => {
return hrTimeToMs(a.startTime) - hrTimeToMs(b.startTime);
});
const tree = this.buildSpanTree(sorted);
if (this.options.showTraceId && tree.length > 0) console.log(this.color(`trace: ${traceId}`, "gray"));
for (const node of tree) this.printNode(node, 0, false);
console.log("");
}
/**
* Build a tree structure from flat spans using parent-child relationships
*/
buildSpanTree(spans) {
const spanMap = /* @__PURE__ */ new Map();
const roots = [];
for (const span of spans) {
const spanId = span.spanContext().spanId;
spanMap.set(spanId, {
span,
children: []
});
}
for (const span of spans) {
const spanId = span.spanContext().spanId;
const parentId = span.parentSpanContext?.spanId;
const node = spanMap.get(spanId);
if (parentId && spanMap.has(parentId)) spanMap.get(parentId).children.push(node);
else roots.push(node);
}
return roots;
}
/**
* Print a span node with indentation and tree characters
*/
printNode(node, depth, isLast) {
const { span } = node;
const prefix = depth === 0 ? "" : " ".repeat(depth - 1) + (isLast ? "└─ " : "├─ ");
const isError = span.status.code === _opentelemetry_api.SpanStatusCode.ERROR;
const statusChar = isError ? "✗" : "✓";
const statusColor = isError ? "red" : "green";
const durationMs = hrTimeToMs(span.duration);
const durationStr = formatDuration(durationMs);
const durationColor = getDurationColor(durationMs);
const scopeName = this.options.showScope ? this.color(` [${this.getScopeName(span)}]`, "gray") : "";
const line = [
prefix,
this.color(statusChar, statusColor),
" ",
span.name.padEnd(Math.max(35 - prefix.length, 10)),
this.color(durationStr.padStart(8), durationColor),
scopeName
].join("");
console.log(line);
if (this.options.showAttributes) {
const attrs = this.formatAttributes(span);
if (attrs) {
const attrIndent = " ".repeat(depth) + " ";
console.log(this.color(`${attrIndent}${attrs}`, "dim"));
}
}
if (isError && span.status.message) {
const errorIndent = " ".repeat(depth) + " ";
console.log(this.color(`${errorIndent}Error: ${span.status.message}`, "red"));
}
const childCount = node.children.length;
let index = 0;
for (const child of node.children) {
this.printNode(child, depth + 1, index === childCount - 1);
index++;
}
}
/**
* Get short scope name from instrumentation scope
*/
getScopeName(span) {
const name = span.instrumentationScope?.name ?? "unknown";
const match = name.match(/@opentelemetry\/instrumentation-(.+)/);
if (match?.[1]) return match[1];
return name.split("/").at(-1) ?? name;
}
/**
* Format span attributes as a comma-separated string
*/
formatAttributes(span) {
const attrs = span.attributes;
if (!attrs || Object.keys(attrs).length === 0) return "";
const pairs = [];
for (const [key, value] of Object.entries(attrs)) {
if (this.options.hideAttributes.includes(key)) continue;
if (value === void 0 || value === null) continue;
const strValue = this.truncate(Array.isArray(value) ? `[${value.join(", ")}]` : String(value), this.options.maxValueLength);
pairs.push(`${key}=${strValue}`);
}
return pairs.join(", ");
}
/**
* Truncate string to max length with ellipsis
*/
truncate(str, max) {
if (str.length <= max) return str;
return str.slice(0, max - 3) + "...";
}
/**
* Apply ANSI color if colors are enabled
*/
color(text, color) {
if (!this.options.colors) return text;
return `${ANSI[color]}${text}${ANSI.reset}`;
}
/**
* Shutdown (no-op for console exporter)
*/
shutdown() {
return Promise.resolve();
}
/**
* Force flush (no-op for console exporter)
*/
forceFlush() {
return Promise.resolve();
}
};
/**
* Convert HrTime [seconds, nanoseconds] to milliseconds
*/
function hrTimeToMs(hrTime) {
const [seconds, nanos] = hrTime;
return seconds * 1e3 + nanos / 1e6;
}
/**
* Format duration with appropriate units
*/
function formatDuration(ms) {
if (ms < 1) return `${(ms * 1e3).toFixed(0)}µs`;
if (ms < 1e3) return `${ms.toFixed(0)}ms`;
return `${(ms / 1e3).toFixed(2)}s`;
}
/**
* Get color based on duration (fast=green, medium=yellow, slow=red)
*/
function getDurationColor(ms) {
if (ms < 100) return "green";
if (ms < 500) return "yellow";
return "red";
}
//#endregion
Object.defineProperty(exports, 'PrettyConsoleExporter', {
enumerable: true,
get: function () {
return PrettyConsoleExporter;
}
});
//# sourceMappingURL=pretty-console-exporter-CMzlrRNg.cjs.map