UNPKG

autotel

Version:
232 lines (230 loc) 6.67 kB
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