UNPKG

autotel

Version:
222 lines (220 loc) 6.45 kB
import { SpanStatusCode } from '@opentelemetry/api'; // src/pretty-console-exporter.ts var ExportResultCode = { SUCCESS: 0, FAILED: 1 }; var 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" }; 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) => { const aTime = hrTimeToMs(a.startTime); const bTime = hrTimeToMs(b.startTime); return aTime - bTime; }); 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 ? "\u2514\u2500 " : "\u251C\u2500 "); const isError = span.status.code === SpanStatusCode.ERROR; const statusChar = isError ? "\u2717" : "\u2713"; 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]; const lastPart = name.split("/").at(-1); return lastPart ?? 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(); } }; function hrTimeToMs(hrTime) { const [seconds, nanos] = hrTime; return seconds * 1e3 + nanos / 1e6; } function formatDuration(ms) { if (ms < 1) { return `${(ms * 1e3).toFixed(0)}\xB5s`; } if (ms < 1e3) { return `${ms.toFixed(0)}ms`; } return `${(ms / 1e3).toFixed(2)}s`; } function getDurationColor(ms) { if (ms < 100) return "green"; if (ms < 500) return "yellow"; return "red"; } export { PrettyConsoleExporter }; //# sourceMappingURL=chunk-6UQRVUN3.js.map //# sourceMappingURL=chunk-6UQRVUN3.js.map