autotel-terminal 17.0.4 → 17.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.cjs +345 -67
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +345 -67
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +318 -62
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +23 -1
- package/dist/index.d.ts +23 -1
- package/dist/index.js +318 -62
- package/dist/index.js.map +1 -1
- package/package.json +4 -2
- package/src/ai/catalog.test.ts +18 -0
- package/src/ai/catalog.ts +16 -0
- package/src/ai/system-prompt.ts +16 -3
- package/src/ai/tools.test.ts +36 -0
- package/src/ai/tools.ts +46 -1
- package/src/ai/types.ts +3 -0
- package/src/index.tsx +177 -89
- package/src/lib/dashboard-keymap.test.ts +36 -0
- package/src/lib/dashboard-keymap.ts +22 -1
- package/src/lib/format.test.ts +70 -0
- package/src/lib/format.ts +65 -0
- package/src/lib/topology-render.test.ts +319 -0
- package/src/lib/topology-render.ts +96 -0
- package/src/otlp-http-json.test.ts +131 -0
- package/src/otlp-http-json.ts +52 -7
- package/src/span-stream.ts +57 -1
package/dist/cli.cjs
CHANGED
|
@@ -10,6 +10,9 @@ var autotel = require('autotel');
|
|
|
10
10
|
var tracerProvider = require('autotel/tracer-provider');
|
|
11
11
|
var ai = require('ai');
|
|
12
12
|
var zod = require('zod');
|
|
13
|
+
var core = require('@json-render/core');
|
|
14
|
+
var catalog = require('@json-render/ink/catalog');
|
|
15
|
+
var ink$1 = require('@json-render/ink');
|
|
13
16
|
require('autotel/exporters');
|
|
14
17
|
var jsxRuntime = require('react/jsx-runtime');
|
|
15
18
|
|
|
@@ -111,6 +114,25 @@ function createTerminalSpanStream(processor) {
|
|
|
111
114
|
const startTime = timeToMs(span.startTime);
|
|
112
115
|
const endTime = timeToMs(span.endTime);
|
|
113
116
|
const durationMs = endTime - startTime;
|
|
117
|
+
const resourceAttrs = span.resource?.attributes ?? {};
|
|
118
|
+
const spanAttrs = span.attributes;
|
|
119
|
+
const mergedAttrs = {};
|
|
120
|
+
for (const [k, v] of Object.entries(resourceAttrs)) {
|
|
121
|
+
mergedAttrs[k] = v;
|
|
122
|
+
}
|
|
123
|
+
for (const [k, v] of Object.entries(spanAttrs)) {
|
|
124
|
+
mergedAttrs[k] = v;
|
|
125
|
+
}
|
|
126
|
+
const spanEvents = span.events?.length ? span.events.map((e) => ({
|
|
127
|
+
name: e.name,
|
|
128
|
+
timeMs: timeToMs(e.time),
|
|
129
|
+
attributes: e.attributes
|
|
130
|
+
})) : void 0;
|
|
131
|
+
const spanLinks = span.links?.length ? span.links.map((l) => ({
|
|
132
|
+
traceId: l.context.traceId,
|
|
133
|
+
spanId: l.context.spanId,
|
|
134
|
+
attributes: l.attributes
|
|
135
|
+
})) : void 0;
|
|
114
136
|
const event = {
|
|
115
137
|
name: span.name,
|
|
116
138
|
spanId: spanContext.spanId,
|
|
@@ -121,7 +143,9 @@ function createTerminalSpanStream(processor) {
|
|
|
121
143
|
durationMs,
|
|
122
144
|
status: mapStatus(span.status.code),
|
|
123
145
|
kind: mapKind(span.kind),
|
|
124
|
-
attributes:
|
|
146
|
+
attributes: mergedAttrs,
|
|
147
|
+
...spanEvents ? { events: spanEvents } : {},
|
|
148
|
+
...spanLinks ? { links: spanLinks } : {}
|
|
125
149
|
};
|
|
126
150
|
callback(event);
|
|
127
151
|
});
|
|
@@ -561,6 +585,123 @@ function buildErrorSummaries(traceSummaries) {
|
|
|
561
585
|
return out;
|
|
562
586
|
}
|
|
563
587
|
|
|
588
|
+
// src/lib/topology-model.ts
|
|
589
|
+
function getServiceName2(span) {
|
|
590
|
+
const attrs = span.attributes ?? {};
|
|
591
|
+
const serviceName = attrs["service.name"] ?? attrs["resource.service.name"];
|
|
592
|
+
return serviceName || "unknown";
|
|
593
|
+
}
|
|
594
|
+
function getPeerService(span) {
|
|
595
|
+
const attrs = span.attributes ?? {};
|
|
596
|
+
const peerService = attrs["peer.service"] ?? attrs["db.system"] ?? attrs["messaging.system"] ?? attrs["http.host"];
|
|
597
|
+
return peerService ?? null;
|
|
598
|
+
}
|
|
599
|
+
function buildServiceGraph(spans) {
|
|
600
|
+
const byService = /* @__PURE__ */ new Map();
|
|
601
|
+
for (const span of spans) {
|
|
602
|
+
const svc = getServiceName2(span);
|
|
603
|
+
const entry = byService.get(svc) ?? {
|
|
604
|
+
durations: [],
|
|
605
|
+
spanCount: 0,
|
|
606
|
+
errorCount: 0
|
|
607
|
+
};
|
|
608
|
+
entry.spanCount += 1;
|
|
609
|
+
entry.durations.push(span.durationMs);
|
|
610
|
+
if (span.status === "ERROR") entry.errorCount += 1;
|
|
611
|
+
byService.set(svc, entry);
|
|
612
|
+
}
|
|
613
|
+
const services = [];
|
|
614
|
+
for (const [serviceName, { durations, spanCount, errorCount }] of byService) {
|
|
615
|
+
if (spanCount === 0) continue;
|
|
616
|
+
const avgDurationMs = durations.reduce((acc, d) => acc + d, 0) / durations.length;
|
|
617
|
+
const sorted = durations.toSorted((a, b) => a - b);
|
|
618
|
+
const p95Index = Math.floor(sorted.length * 0.95);
|
|
619
|
+
const p95DurationMs = sorted[p95Index] ?? sorted.at(-1) ?? 0;
|
|
620
|
+
services.push({
|
|
621
|
+
serviceName,
|
|
622
|
+
spanCount,
|
|
623
|
+
errorCount,
|
|
624
|
+
avgDurationMs,
|
|
625
|
+
p95DurationMs
|
|
626
|
+
});
|
|
627
|
+
}
|
|
628
|
+
const byEdge = /* @__PURE__ */ new Map();
|
|
629
|
+
for (const span of spans) {
|
|
630
|
+
const to = getPeerService(span);
|
|
631
|
+
if (!to) continue;
|
|
632
|
+
const from = getServiceName2(span);
|
|
633
|
+
const key = `${from}\u2192${to}`;
|
|
634
|
+
const entry = byEdge.get(key) ?? { spanCount: 0, errorCount: 0 };
|
|
635
|
+
entry.spanCount += 1;
|
|
636
|
+
if (span.status === "ERROR") entry.errorCount += 1;
|
|
637
|
+
byEdge.set(key, entry);
|
|
638
|
+
}
|
|
639
|
+
const edges = [];
|
|
640
|
+
for (const [key, { spanCount, errorCount }] of byEdge) {
|
|
641
|
+
const [fromService, toService] = key.split("\u2192");
|
|
642
|
+
edges.push({ fromService, toService, spanCount, errorCount });
|
|
643
|
+
}
|
|
644
|
+
services.sort((a, b) => b.spanCount - a.spanCount);
|
|
645
|
+
edges.sort((a, b) => b.spanCount - a.spanCount);
|
|
646
|
+
return { services, edges };
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
// src/lib/topology-render.ts
|
|
650
|
+
function renderTopologyAscii(graph) {
|
|
651
|
+
if (graph.services.length === 0) {
|
|
652
|
+
return [" No services detected yet."];
|
|
653
|
+
}
|
|
654
|
+
const targetServices = new Set(graph.edges.map((e) => e.toService));
|
|
655
|
+
const roots = graph.services.filter(
|
|
656
|
+
(s) => !targetServices.has(s.serviceName)
|
|
657
|
+
);
|
|
658
|
+
const rootList = roots.length > 0 ? roots : [...graph.services];
|
|
659
|
+
const lines = [];
|
|
660
|
+
for (const root of rootList) {
|
|
661
|
+
renderNode(graph, root.serviceName, "", true, lines, /* @__PURE__ */ new Set());
|
|
662
|
+
}
|
|
663
|
+
return lines;
|
|
664
|
+
}
|
|
665
|
+
function renderNode(graph, serviceName, prefix, isRoot, lines, ancestors) {
|
|
666
|
+
const svc = graph.services.find((s) => s.serviceName === serviceName);
|
|
667
|
+
const label = svc ? formatServiceLine(svc) : `[${serviceName}]`;
|
|
668
|
+
if (isRoot) {
|
|
669
|
+
lines.push(label);
|
|
670
|
+
}
|
|
671
|
+
if (ancestors.has(serviceName)) return;
|
|
672
|
+
const pathAncestors = new Set(ancestors);
|
|
673
|
+
pathAncestors.add(serviceName);
|
|
674
|
+
const outgoing = graph.edges.filter((e) => e.fromService === serviceName);
|
|
675
|
+
for (let i = 0; i < outgoing.length; i++) {
|
|
676
|
+
const edge = outgoing[i];
|
|
677
|
+
const isLast = i === outgoing.length - 1;
|
|
678
|
+
const connector = isLast ? "\u2514" : "\u251C";
|
|
679
|
+
const childPrefix = isLast ? " " : "\u2502 ";
|
|
680
|
+
const downstream = graph.services.find(
|
|
681
|
+
(s) => s.serviceName === edge.toService
|
|
682
|
+
);
|
|
683
|
+
const edgeLabel = formatEdgeLabel(edge);
|
|
684
|
+
const downstreamLabel = downstream ? formatServiceLine(downstream) : `[${edge.toService}]`;
|
|
685
|
+
lines.push(`${prefix} ${connector}\u2500\u2500${edgeLabel}\u2500\u2500\u2192 ${downstreamLabel}`);
|
|
686
|
+
renderNode(
|
|
687
|
+
graph,
|
|
688
|
+
edge.toService,
|
|
689
|
+
prefix + " " + childPrefix,
|
|
690
|
+
false,
|
|
691
|
+
lines,
|
|
692
|
+
pathAncestors
|
|
693
|
+
);
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
function formatServiceLine(svc) {
|
|
697
|
+
const errPart = svc.errorCount > 0 ? ` \xB7 ${svc.errorCount} err` : "";
|
|
698
|
+
return `[${svc.serviceName}] ${svc.spanCount} spans${errPart} \xB7 p95 ${formatDurationMs(svc.p95DurationMs)}`;
|
|
699
|
+
}
|
|
700
|
+
function formatEdgeLabel(edge) {
|
|
701
|
+
const errPart = edge.errorCount > 0 ? `, ${edge.errorCount} err` : "";
|
|
702
|
+
return `(${edge.spanCount}${errPart})`;
|
|
703
|
+
}
|
|
704
|
+
|
|
564
705
|
// src/lib/export-model.ts
|
|
565
706
|
function exportTraceToJson(trace, logs) {
|
|
566
707
|
const exported = {
|
|
@@ -690,17 +831,31 @@ You have tools to query the telemetry data precisely. Use them to answer questio
|
|
|
690
831
|
- getTraceDetail: deep dive into a specific trace
|
|
691
832
|
- searchSpans: search spans by name
|
|
692
833
|
- searchLogs: search logs by message content
|
|
834
|
+
- renderUI: display rich terminal UI (tables, charts, badges)
|
|
835
|
+
|
|
836
|
+
## Workflow
|
|
837
|
+
1. Use data tools first to gather data
|
|
838
|
+
2. Use renderUI to display structured results as tables, charts, or cards
|
|
839
|
+
3. Add a brief text explanation after the rendered UI
|
|
693
840
|
|
|
694
|
-
|
|
695
|
-
|
|
841
|
+
## When to use renderUI
|
|
842
|
+
Use renderUI for tables, comparisons, and metrics. Do NOT use it for short text answers.
|
|
843
|
+
|
|
844
|
+
renderUI spec format: { root: "id", elements: { "id": { type: "ComponentName", props: {...}, children: [] } } }
|
|
845
|
+
|
|
846
|
+
Components: Table (columns, rows), KeyValue (label, value), Badge (label, variant: success/error/warning/info), BarChart (data: [{label,value}]), Card (title, children), Heading (text), Divider, Text (text, color, bold), Box (flexDirection, children).
|
|
847
|
+
|
|
848
|
+
Table example: { type: "Table", props: { columns: [{ header: "Name", key: "name" }], rows: [{ name: "api" }] }, children: [] }
|
|
849
|
+
|
|
850
|
+
Keep text responses under 300 words.
|
|
696
851
|
Use specific span names, durations, and attribute values from the data.
|
|
697
|
-
Format for a narrow terminal column \u2014 use short paragraphs, not wide tables.
|
|
698
852
|
|
|
699
853
|
Current dashboard summary:
|
|
700
854
|
${contextJson}`;
|
|
701
855
|
}
|
|
856
|
+
var COMPONENT_NAMES = Object.keys(catalog.standardComponentDefinitions);
|
|
702
857
|
var t = ai.tool;
|
|
703
|
-
function createTelemetryTools(ctx) {
|
|
858
|
+
function createTelemetryTools(ctx, onRenderUI) {
|
|
704
859
|
return {
|
|
705
860
|
getOverviewStats: t({
|
|
706
861
|
description: "Get high-level stats: total spans, error count, average duration, p95 duration, and service count.",
|
|
@@ -868,6 +1023,33 @@ function createTelemetryTools(ctx) {
|
|
|
868
1023
|
attrs: l.attributes
|
|
869
1024
|
}));
|
|
870
1025
|
}
|
|
1026
|
+
}),
|
|
1027
|
+
renderUI: t({
|
|
1028
|
+
description: "Render rich terminal UI (tables, charts, badges) to display structured data. Use this when showing tabular data, comparisons, or metrics \u2014 not for short text answers. Available components: Table (columns + rows), KeyValue (key-value pairs), Badge (status labels: default/info/success/warning/error), BarChart (horizontal bars with labels), Card (grouped content with title), Heading (section title), Divider (separator), Text (styled text), Box (layout container).",
|
|
1029
|
+
parameters: zod.z.object({
|
|
1030
|
+
spec: zod.z.object({
|
|
1031
|
+
root: zod.z.string().describe("ID of the root element"),
|
|
1032
|
+
elements: zod.z.record(
|
|
1033
|
+
zod.z.string(),
|
|
1034
|
+
zod.z.object({
|
|
1035
|
+
type: zod.z.enum(COMPONENT_NAMES).describe("Component name"),
|
|
1036
|
+
props: zod.z.record(zod.z.string(), zod.z.unknown()).optional(),
|
|
1037
|
+
children: zod.z.array(zod.z.string()).describe("Child element keys")
|
|
1038
|
+
})
|
|
1039
|
+
).describe("Map of element ID to component definition")
|
|
1040
|
+
}).describe("json-render spec defining the UI to display")
|
|
1041
|
+
}),
|
|
1042
|
+
execute: async ({ spec }) => {
|
|
1043
|
+
const validation = core.validateSpec(spec);
|
|
1044
|
+
if (!validation.valid) {
|
|
1045
|
+
return {
|
|
1046
|
+
rendered: false,
|
|
1047
|
+
error: validation.issues.map((i) => i.message).join("; ")
|
|
1048
|
+
};
|
|
1049
|
+
}
|
|
1050
|
+
onRenderUI?.(spec);
|
|
1051
|
+
return { rendered: true };
|
|
1052
|
+
}
|
|
871
1053
|
})
|
|
872
1054
|
};
|
|
873
1055
|
}
|
|
@@ -934,11 +1116,11 @@ function Dashboard({
|
|
|
934
1116
|
const throttleRef = react.useRef(null);
|
|
935
1117
|
const pendingSpansRef = react.useRef([]);
|
|
936
1118
|
const [logs, setLogs] = react.useState([]);
|
|
937
|
-
const [aiActive, setAiActive] = react.useState(false);
|
|
938
1119
|
const [aiMessages, setAiMessages] = react.useState([]);
|
|
939
1120
|
const [aiInput, setAiInput] = react.useState("");
|
|
940
1121
|
const [aiState, setAiState] = react.useState({ status: "unconfigured" });
|
|
941
1122
|
const [aiInputMode, setAiInputMode] = react.useState(false);
|
|
1123
|
+
const [aiSpec, setAiSpec] = react.useState(null);
|
|
942
1124
|
const aiModelRef = react.useRef(null);
|
|
943
1125
|
const aiAbortRef = react.useRef(null);
|
|
944
1126
|
react.useEffect(() => {
|
|
@@ -1078,6 +1260,8 @@ function Dashboard({
|
|
|
1078
1260
|
[spansForSelectedService]
|
|
1079
1261
|
);
|
|
1080
1262
|
const selectedTraceSummary = drilldownTraceId == null ? filteredSummaries[selected] ?? null : filteredSummaries.find((t2) => t2.traceId === drilldownTraceId) ?? null;
|
|
1263
|
+
const serviceGraph = react.useMemo(() => buildServiceGraph(spans), [spans]);
|
|
1264
|
+
const topologyLines = react.useMemo(() => renderTopologyAscii(serviceGraph), [serviceGraph]);
|
|
1081
1265
|
const errorSummaries = react.useMemo(
|
|
1082
1266
|
() => buildErrorSummaries(traceSummaries),
|
|
1083
1267
|
[traceSummaries]
|
|
@@ -1149,6 +1333,7 @@ function Dashboard({
|
|
|
1149
1333
|
setAiInput("");
|
|
1150
1334
|
const abort = new AbortController();
|
|
1151
1335
|
aiAbortRef.current = abort;
|
|
1336
|
+
setAiSpec(null);
|
|
1152
1337
|
setAiState({ status: "streaming", abortController: abort });
|
|
1153
1338
|
const toolCtx = {
|
|
1154
1339
|
spans,
|
|
@@ -1158,7 +1343,7 @@ function Dashboard({
|
|
|
1158
1343
|
serviceStats,
|
|
1159
1344
|
errorSummaries
|
|
1160
1345
|
};
|
|
1161
|
-
const tools = createTelemetryTools(toolCtx);
|
|
1346
|
+
const tools = createTelemetryTools(toolCtx, (spec) => setAiSpec(spec));
|
|
1162
1347
|
const statsContext = JSON.stringify({
|
|
1163
1348
|
viewMode,
|
|
1164
1349
|
stats: {
|
|
@@ -1204,15 +1389,31 @@ Currently viewing trace ${drilldownTraceId}. This trace has ${drilldownSpans.len
|
|
|
1204
1389
|
return updated;
|
|
1205
1390
|
});
|
|
1206
1391
|
}
|
|
1392
|
+
if (!fullText.trim()) {
|
|
1393
|
+
setAiMessages((prev) => {
|
|
1394
|
+
const updated = [...prev];
|
|
1395
|
+
const lastMsg = updated.at(-1);
|
|
1396
|
+
if (lastMsg?.role === "assistant" && !lastMsg.content.trim()) {
|
|
1397
|
+
updated[updated.length - 1] = {
|
|
1398
|
+
role: "assistant",
|
|
1399
|
+
content: "(No response from model \u2014 try a simpler question or a larger model)"
|
|
1400
|
+
};
|
|
1401
|
+
}
|
|
1402
|
+
return updated;
|
|
1403
|
+
});
|
|
1404
|
+
}
|
|
1207
1405
|
setAiState({ status: "idle" });
|
|
1208
1406
|
} catch (error) {
|
|
1209
1407
|
if (abort.signal.aborted) {
|
|
1210
1408
|
setAiState({ status: "idle" });
|
|
1211
1409
|
return;
|
|
1212
1410
|
}
|
|
1411
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
1412
|
+
process.stderr.write(`[autotel-terminal] AI error: ${errorMsg}
|
|
1413
|
+
`);
|
|
1213
1414
|
setAiState({
|
|
1214
1415
|
status: "error",
|
|
1215
|
-
message:
|
|
1416
|
+
message: errorMsg
|
|
1216
1417
|
});
|
|
1217
1418
|
} finally {
|
|
1218
1419
|
aiAbortRef.current = null;
|
|
@@ -1297,7 +1498,7 @@ Currently viewing trace ${drilldownTraceId}. This trace has ${drilldownSpans.len
|
|
|
1297
1498
|
aiAbortRef.current?.abort();
|
|
1298
1499
|
} else {
|
|
1299
1500
|
setAiInputMode(false);
|
|
1300
|
-
|
|
1501
|
+
setViewMode("trace");
|
|
1301
1502
|
}
|
|
1302
1503
|
return;
|
|
1303
1504
|
}
|
|
@@ -1317,14 +1518,19 @@ Currently viewing trace ${drilldownTraceId}. This trace has ${drilldownSpans.len
|
|
|
1317
1518
|
return;
|
|
1318
1519
|
}
|
|
1319
1520
|
if (input === "a") {
|
|
1320
|
-
|
|
1321
|
-
|
|
1521
|
+
if (viewMode === "ai") {
|
|
1522
|
+
setViewMode("trace");
|
|
1322
1523
|
setAiInputMode(false);
|
|
1323
1524
|
} else {
|
|
1525
|
+
setViewMode("ai");
|
|
1324
1526
|
if (aiState.status !== "unconfigured") {
|
|
1325
1527
|
setAiInputMode(true);
|
|
1326
1528
|
}
|
|
1327
1529
|
}
|
|
1530
|
+
setSelected(0);
|
|
1531
|
+
setDrilldownTraceId(null);
|
|
1532
|
+
setDrilldownSelectedIndex(0);
|
|
1533
|
+
setDrilldownScrollOffset(0);
|
|
1328
1534
|
return;
|
|
1329
1535
|
}
|
|
1330
1536
|
if (key.escape) {
|
|
@@ -1484,6 +1690,13 @@ Currently viewing trace ${drilldownTraceId}. This trace has ${drilldownSpans.len
|
|
|
1484
1690
|
setDrilldownSelectedIndex(0);
|
|
1485
1691
|
setDrilldownScrollOffset(0);
|
|
1486
1692
|
}
|
|
1693
|
+
if (input === "G") {
|
|
1694
|
+
setViewMode((m) => m === "topology" ? "trace" : "topology");
|
|
1695
|
+
setSelected(0);
|
|
1696
|
+
setDrilldownTraceId(null);
|
|
1697
|
+
setDrilldownSelectedIndex(0);
|
|
1698
|
+
setDrilldownScrollOffset(0);
|
|
1699
|
+
}
|
|
1487
1700
|
if (input === "c") {
|
|
1488
1701
|
setSpans([]);
|
|
1489
1702
|
setLogs([]);
|
|
@@ -1625,7 +1838,7 @@ ${json}
|
|
|
1625
1838
|
{ isActive: isRawModeSupported }
|
|
1626
1839
|
);
|
|
1627
1840
|
const headerRight = recording ? "[Recording]" : paused ? "[Paused]" : "[Live]";
|
|
1628
|
-
const headerModeLabel = viewMode === "trace" ? "traces" : viewMode === "span" ? "spans" : viewMode === "log" ? "logs" : viewMode === "service-summary" ? "services" : "errors";
|
|
1841
|
+
const headerModeLabel = viewMode === "trace" ? "traces" : viewMode === "span" ? "spans" : viewMode === "log" ? "logs" : viewMode === "service-summary" ? "services" : viewMode === "topology" ? "topology" : viewMode === "ai" ? "AI" : "errors";
|
|
1629
1842
|
const showNewError = newErrorCount > 0;
|
|
1630
1843
|
function renderTreeRow(node, index) {
|
|
1631
1844
|
const isSel = drilldownTraceId != null && index === drilldownSelectedIndex;
|
|
@@ -1639,8 +1852,9 @@ ${json}
|
|
|
1639
1852
|
flexDirection: "row",
|
|
1640
1853
|
children: [
|
|
1641
1854
|
/* @__PURE__ */ jsxRuntime.jsx(ink.Text, { backgroundColor: isSel ? "blue" : void 0, color: isSel ? "white" : void 0, children: isSel ? "\u25B8 " : " " }),
|
|
1855
|
+
/* @__PURE__ */ jsxRuntime.jsx(ink.Text, { color: node.span.status === "ERROR" ? "red" : void 0, children: node.span.status === "ERROR" ? "\u2717" : " " }),
|
|
1642
1856
|
/* @__PURE__ */ jsxRuntime.jsx(ink.Text, { dimColor: true, children: prefix }),
|
|
1643
|
-
/* @__PURE__ */ jsxRuntime.jsx(ink.Text, { color: colors ? statusColor : void 0, children: truncate(node.span.name,
|
|
1857
|
+
/* @__PURE__ */ jsxRuntime.jsx(ink.Text, { color: colors ? statusColor : void 0, children: truncate(node.span.name, 23) }),
|
|
1644
1858
|
/* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { color: svcColor, children: [
|
|
1645
1859
|
" ",
|
|
1646
1860
|
truncate(svcName, 10)
|
|
@@ -1705,13 +1919,48 @@ ${json}
|
|
|
1705
1919
|
"\u2026"
|
|
1706
1920
|
] })
|
|
1707
1921
|
] }) : /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { flexDirection: "row", justifyContent: "space-between", children: [
|
|
1708
|
-
/* @__PURE__ */ jsxRuntime.jsx(ink.Text, { dimColor: true, children: drilldownTraceId == null ? "\u2191/\u2193 select \u2022 Enter open \u2022 Tab cycle tabs \u2022 Esc back \u2022
|
|
1709
|
-
/* @__PURE__ */ jsxRuntime.jsx(ink.Text, { dimColor: true, children: viewMode === "trace" ? `traces ${filteredSummaries.length}/${traceSummaries.length}` : viewMode === "span" ? `spans ${filteredSpans.length}/${spans.length}` : viewMode === "service-summary" ? `services ${serviceStats.length}` : viewMode === "errors" ? `errors ${filteredErrorSummaries.length}/${errorSummaries.length}` : `logs ${filteredLogs.length}/${logs.length}` })
|
|
1922
|
+
/* @__PURE__ */ jsxRuntime.jsx(ink.Text, { dimColor: true, children: drilldownTraceId == null ? "\u2191/\u2193 select \u2022 Enter open \u2022 Tab cycle tabs \u2022 Esc back \u2022 t trace \u2022 l logs \u2022 a AI \u2022 ? help" : "\u2191/\u2193 select \u2022 Tab cycle tabs \u2022 Esc back \u2022 t trace \u2022 l logs \u2022 a AI \u2022 ? help" }),
|
|
1923
|
+
/* @__PURE__ */ jsxRuntime.jsx(ink.Text, { dimColor: true, children: viewMode === "trace" ? `traces ${filteredSummaries.length}/${traceSummaries.length}` : viewMode === "span" ? `spans ${filteredSpans.length}/${spans.length}` : viewMode === "service-summary" ? `services ${serviceStats.length}` : viewMode === "errors" ? `errors ${filteredErrorSummaries.length}/${errorSummaries.length}` : viewMode === "topology" ? `services ${serviceGraph.services.length} \xB7 edges ${serviceGraph.edges.length}` : viewMode === "ai" ? `messages ${aiMessages.length}` : `logs ${filteredLogs.length}/${logs.length}` })
|
|
1710
1924
|
] }),
|
|
1711
|
-
showHelp && /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { dimColor: true, children: "Views: t/l/v/E \u2022 Search: / \u2022 Filters: e/S/R/H/f/x \u2022 Capture: p/r/J \u2022
|
|
1925
|
+
showHelp && /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { dimColor: true, children: "Views: t/l/v/E/G/a \u2022 Search: / \u2022 Filters: e/S/R/H/f/x \u2022 Capture: p/r/J \u2022 Clear: c" })
|
|
1712
1926
|
] }),
|
|
1713
1927
|
/* @__PURE__ */ jsxRuntime.jsx(ink.Box, { marginBottom: 0, children: /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { dimColor: true, children: spanFilters.serviceName || spanFilters.route || spanFilters.statusGroup !== "all" || spanFilters.traceId ? `filters:${spanFilters.serviceName ? ` service=${spanFilters.serviceName}` : ""}${spanFilters.route ? ` route=${spanFilters.route}` : ""}${spanFilters.statusGroup && spanFilters.statusGroup !== "all" ? ` status=${spanFilters.statusGroup}` : ""}${spanFilters.traceId ? ` trace=${spanFilters.traceId.slice(0, 8)}\u2026` : ""}` : "filters: none" }) }),
|
|
1714
|
-
|
|
1928
|
+
viewMode === "topology" && /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { flexDirection: "column", borderStyle: "round", borderColor: "gray", paddingX: 1, children: [
|
|
1929
|
+
/* @__PURE__ */ jsxRuntime.jsx(ink.Text, { bold: true, children: "Service Topology" }),
|
|
1930
|
+
/* @__PURE__ */ jsxRuntime.jsx(ink.Text, { dimColor: true, children: "Press G to toggle \xB7 Shows service dependencies from span data" }),
|
|
1931
|
+
/* @__PURE__ */ jsxRuntime.jsx(ink.Box, { flexDirection: "column", marginTop: 1, children: topologyLines.map((line, i) => {
|
|
1932
|
+
const hasErr = line.includes(" err");
|
|
1933
|
+
return /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { color: hasErr ? "red" : void 0, children: line }, `topo-${i}`);
|
|
1934
|
+
}) })
|
|
1935
|
+
] }),
|
|
1936
|
+
viewMode === "ai" && /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { flexDirection: "column", borderStyle: "round", borderColor: "gray", paddingX: 1, paddingY: 0, children: [
|
|
1937
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { marginBottom: 1, justifyContent: "space-between", children: [
|
|
1938
|
+
/* @__PURE__ */ jsxRuntime.jsx(ink.Text, { bold: true, children: "AI Assistant" }),
|
|
1939
|
+
/* @__PURE__ */ jsxRuntime.jsx(ink.Text, { dimColor: true, children: aiState.status === "streaming" ? "(streaming...)" : aiState.status === "unconfigured" ? "(no provider)" : aiState.status === "error" ? "(error)" : "" })
|
|
1940
|
+
] }),
|
|
1941
|
+
aiState.status === "unconfigured" ? /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { flexDirection: "column", children: [
|
|
1942
|
+
/* @__PURE__ */ jsxRuntime.jsx(ink.Text, { dimColor: true, children: "No AI provider configured." }),
|
|
1943
|
+
/* @__PURE__ */ jsxRuntime.jsx(ink.Text, { dimColor: true, children: "Set AI_PROVIDER and AI_MODEL env vars, or start Ollama locally." }),
|
|
1944
|
+
/* @__PURE__ */ jsxRuntime.jsx(ink.Text, { dimColor: true, children: "Press a to close this view." })
|
|
1945
|
+
] }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
1946
|
+
aiMessages.length === 0 && aiState.status !== "error" && /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { dimColor: true, children: "Ask a question about your telemetry data. Press Enter to send." }),
|
|
1947
|
+
aiMessages.slice(-10).map((msg, i) => /* @__PURE__ */ jsxRuntime.jsx(ink.Box, { flexDirection: "column", marginBottom: msg.role === "assistant" ? 1 : 0, children: /* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { color: msg.role === "user" ? "cyan" : void 0, children: [
|
|
1948
|
+
msg.role === "user" ? "> " : "",
|
|
1949
|
+
msg.content.slice(0, 1e3),
|
|
1950
|
+
msg.content.length > 1e3 ? "..." : ""
|
|
1951
|
+
] }) }, `ai-msg-${i}`)),
|
|
1952
|
+
aiSpec && /* @__PURE__ */ jsxRuntime.jsx(ink.Box, { flexDirection: "column", marginBottom: 1, borderStyle: "single", borderColor: "gray", paddingX: 1, children: /* @__PURE__ */ jsxRuntime.jsx(ink$1.Renderer, { spec: aiSpec }) }),
|
|
1953
|
+
aiState.status === "error" && /* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { color: "red", children: [
|
|
1954
|
+
"Error: ",
|
|
1955
|
+
aiState.message
|
|
1956
|
+
] }),
|
|
1957
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { marginTop: 1, borderStyle: "single", borderColor: "cyan", paddingX: 1, children: [
|
|
1958
|
+
/* @__PURE__ */ jsxRuntime.jsx(ink.Text, { color: "cyan", children: "> " }),
|
|
1959
|
+
/* @__PURE__ */ jsxRuntime.jsx(ink.Text, { children: aiInput || (aiInputMode ? "(type your question)" : "(press a to focus)") })
|
|
1960
|
+
] })
|
|
1961
|
+
] })
|
|
1962
|
+
] }),
|
|
1963
|
+
viewMode !== "topology" && viewMode !== "ai" && (drilldownTraceId != null ? /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { flexDirection: "column", borderStyle: "round", borderColor: "gray", paddingX: 1, paddingY: 0, children: [
|
|
1715
1964
|
/* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { marginBottom: 0, flexDirection: "column", children: [
|
|
1716
1965
|
/* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { bold: true, children: [
|
|
1717
1966
|
"Trace ",
|
|
@@ -1810,7 +2059,8 @@ ${json}
|
|
|
1810
2059
|
const svcColor = getServiceColor(svcName);
|
|
1811
2060
|
const kindStr = (s.kind ?? "").padEnd(KIND_COL);
|
|
1812
2061
|
const svcStr = truncate(svcName, SERVICE_COL - 2).padEnd(SERVICE_COL);
|
|
1813
|
-
const
|
|
2062
|
+
const errorMark = s.status === "ERROR" ? "\u2717" : " ";
|
|
2063
|
+
const namePart = `${indent}${truncate(s.name, nameWidth - 1)}`.padEnd(NAME_COL - 1);
|
|
1814
2064
|
const bar = buildWaterfallBar(
|
|
1815
2065
|
s.startTime,
|
|
1816
2066
|
s.durationMs,
|
|
@@ -1821,6 +2071,7 @@ ${json}
|
|
|
1821
2071
|
return /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { flexDirection: "row", children: [
|
|
1822
2072
|
/* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { backgroundColor: isSel ? "blue" : void 0, color: isSel ? "white" : void 0, children: [
|
|
1823
2073
|
isSel ? "\u25B8" : " ",
|
|
2074
|
+
/* @__PURE__ */ jsxRuntime.jsx(ink.Text, { color: s.status === "ERROR" ? "red" : void 0, children: errorMark }),
|
|
1824
2075
|
namePart
|
|
1825
2076
|
] }),
|
|
1826
2077
|
/* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { color: svcColor, children: [
|
|
@@ -1927,6 +2178,53 @@ ${json}
|
|
|
1927
2178
|
": ",
|
|
1928
2179
|
truncate(String(v), 28)
|
|
1929
2180
|
] }, k))
|
|
2181
|
+
] }),
|
|
2182
|
+
span.events && span.events.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { flexDirection: "column", marginTop: 0, children: [
|
|
2183
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { bold: true, dimColor: true, children: [
|
|
2184
|
+
"Events (",
|
|
2185
|
+
span.events.length,
|
|
2186
|
+
")"
|
|
2187
|
+
] }),
|
|
2188
|
+
span.events.slice(0, 5).map((ev, i) => /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { flexDirection: "row", children: [
|
|
2189
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { color: ev.name === "exception" ? "red" : "yellow", children: [
|
|
2190
|
+
" ",
|
|
2191
|
+
"\u25C6",
|
|
2192
|
+
" ",
|
|
2193
|
+
truncate(ev.name, 20)
|
|
2194
|
+
] }),
|
|
2195
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { dimColor: true, children: [
|
|
2196
|
+
" ",
|
|
2197
|
+
"+",
|
|
2198
|
+
formatDurationMs(ev.timeMs - span.startTime)
|
|
2199
|
+
] }),
|
|
2200
|
+
ev.attributes && Object.keys(ev.attributes).length > 0 && /* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { dimColor: true, children: [
|
|
2201
|
+
" ",
|
|
2202
|
+
Object.entries(ev.attributes).slice(0, 2).map(([k, v]) => `${k}=${String(v)}`).join(" ")
|
|
2203
|
+
] })
|
|
2204
|
+
] }, `ev-${i}`))
|
|
2205
|
+
] }),
|
|
2206
|
+
span.links && span.links.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { flexDirection: "column", marginTop: 0, children: [
|
|
2207
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { bold: true, dimColor: true, children: [
|
|
2208
|
+
"Links (",
|
|
2209
|
+
span.links.length,
|
|
2210
|
+
")"
|
|
2211
|
+
] }),
|
|
2212
|
+
span.links.slice(0, 5).map((lnk, i) => /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { flexDirection: "row", children: [
|
|
2213
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { color: "cyan", children: [
|
|
2214
|
+
" ",
|
|
2215
|
+
"\u2192",
|
|
2216
|
+
" trace:",
|
|
2217
|
+
lnk.traceId.slice(0, 8),
|
|
2218
|
+
"\u2026",
|
|
2219
|
+
" span:",
|
|
2220
|
+
lnk.spanId.slice(0, 8),
|
|
2221
|
+
"\u2026"
|
|
2222
|
+
] }),
|
|
2223
|
+
lnk.attributes && Object.keys(lnk.attributes).length > 0 && /* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { dimColor: true, children: [
|
|
2224
|
+
" ",
|
|
2225
|
+
Object.entries(lnk.attributes).slice(0, 2).map(([k, v]) => `${k}=${String(v)}`).join(" ")
|
|
2226
|
+
] })
|
|
2227
|
+
] }, `lnk-${i}`))
|
|
1930
2228
|
] })
|
|
1931
2229
|
] });
|
|
1932
2230
|
})(),
|
|
@@ -2141,49 +2439,7 @@ ${json}
|
|
|
2141
2439
|
borderColor: "gray",
|
|
2142
2440
|
paddingX: 1,
|
|
2143
2441
|
paddingY: 0,
|
|
2144
|
-
children:
|
|
2145
|
-
/* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { marginBottom: 1, justifyContent: "space-between", children: [
|
|
2146
|
-
/* @__PURE__ */ jsxRuntime.jsx(ink.Text, { bold: true, children: "AI Assistant" }),
|
|
2147
|
-
/* @__PURE__ */ jsxRuntime.jsx(ink.Text, { dimColor: true, children: aiState.status === "streaming" ? "(streaming...)" : aiState.status === "unconfigured" ? "(no provider)" : aiState.status === "error" ? "(error)" : "" })
|
|
2148
|
-
] }),
|
|
2149
|
-
aiState.status === "unconfigured" ? /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { flexDirection: "column", children: [
|
|
2150
|
-
/* @__PURE__ */ jsxRuntime.jsx(ink.Text, { dimColor: true, children: "No AI provider configured." }),
|
|
2151
|
-
/* @__PURE__ */ jsxRuntime.jsx(ink.Text, { dimColor: true, children: "Set AI_PROVIDER and AI_MODEL env vars, or start Ollama locally." }),
|
|
2152
|
-
/* @__PURE__ */ jsxRuntime.jsx(ink.Text, { dimColor: true, children: "Press 'a' to close this panel." })
|
|
2153
|
-
] }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
2154
|
-
aiMessages.length === 0 && aiState.status !== "error" && /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { dimColor: true, children: "Ask a question about your telemetry data. Press Enter to send." }),
|
|
2155
|
-
aiMessages.slice(-10).map((msg, i) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
2156
|
-
ink.Box,
|
|
2157
|
-
{
|
|
2158
|
-
flexDirection: "column",
|
|
2159
|
-
marginBottom: msg.role === "assistant" ? 1 : 0,
|
|
2160
|
-
children: /* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { color: msg.role === "user" ? "cyan" : void 0, children: [
|
|
2161
|
-
msg.role === "user" ? "> " : "",
|
|
2162
|
-
msg.content.slice(0, 500),
|
|
2163
|
-
msg.content.length > 500 ? "..." : ""
|
|
2164
|
-
] })
|
|
2165
|
-
},
|
|
2166
|
-
i
|
|
2167
|
-
)),
|
|
2168
|
-
aiState.status === "error" && /* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { color: "red", children: [
|
|
2169
|
-
"Error: ",
|
|
2170
|
-
aiState.message
|
|
2171
|
-
] }),
|
|
2172
|
-
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
2173
|
-
ink.Box,
|
|
2174
|
-
{
|
|
2175
|
-
marginTop: 1,
|
|
2176
|
-
borderStyle: "single",
|
|
2177
|
-
borderColor: "cyan",
|
|
2178
|
-
paddingX: 1,
|
|
2179
|
-
children: [
|
|
2180
|
-
/* @__PURE__ */ jsxRuntime.jsx(ink.Text, { color: "cyan", children: "> " }),
|
|
2181
|
-
/* @__PURE__ */ jsxRuntime.jsx(ink.Text, { children: aiInput || (aiInputMode ? "(type your question)" : "(press a to focus)") })
|
|
2182
|
-
]
|
|
2183
|
-
}
|
|
2184
|
-
)
|
|
2185
|
-
] })
|
|
2186
|
-
] }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
2442
|
+
children: /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
2187
2443
|
/* @__PURE__ */ jsxRuntime.jsx(ink.Box, { marginBottom: 1, children: /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { bold: true, children: "Details" }) }),
|
|
2188
2444
|
viewMode === "errors" ? (() => {
|
|
2189
2445
|
const e = filteredErrorSummaries[selected] ?? null;
|
|
@@ -2407,7 +2663,7 @@ ${json}
|
|
|
2407
2663
|
] })
|
|
2408
2664
|
}
|
|
2409
2665
|
)
|
|
2410
|
-
] }),
|
|
2666
|
+
] })),
|
|
2411
2667
|
showStats && /* @__PURE__ */ jsxRuntime.jsx(ink.Box, { marginTop: 1, borderStyle: "round", borderColor: "gray", paddingX: 1, children: /* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { dimColor: true, children: [
|
|
2412
2668
|
"Spans: ",
|
|
2413
2669
|
stats.total,
|
|
@@ -2603,6 +2859,8 @@ function* extractSpans(payload) {
|
|
|
2603
2859
|
if (!Array.isArray(resourceSpans)) return;
|
|
2604
2860
|
for (const resourceSpan of resourceSpans) {
|
|
2605
2861
|
if (!resourceSpan || typeof resourceSpan !== "object") continue;
|
|
2862
|
+
const resource = resourceSpan.resource;
|
|
2863
|
+
const resourceAttrs = attrsToRecord(resource?.attributes);
|
|
2606
2864
|
const scopeSpans = resourceSpan.scopeSpans;
|
|
2607
2865
|
if (!Array.isArray(scopeSpans)) continue;
|
|
2608
2866
|
for (const scopeSpan of scopeSpans) {
|
|
@@ -2611,15 +2869,33 @@ function* extractSpans(payload) {
|
|
|
2611
2869
|
if (!Array.isArray(spans)) continue;
|
|
2612
2870
|
for (const span of spans) {
|
|
2613
2871
|
if (span && typeof span === "object") {
|
|
2614
|
-
yield span;
|
|
2872
|
+
yield { span, resourceAttrs };
|
|
2615
2873
|
}
|
|
2616
2874
|
}
|
|
2617
2875
|
}
|
|
2618
2876
|
}
|
|
2619
2877
|
}
|
|
2620
|
-
function otlpSpanToTerminalEvent(span) {
|
|
2878
|
+
function otlpSpanToTerminalEvent(span, resourceAttrs = {}) {
|
|
2621
2879
|
const startTime = toMs(span.startTimeUnixNano);
|
|
2622
2880
|
const endTime = toMs(span.endTimeUnixNano);
|
|
2881
|
+
const spanAttrs = attrsToRecord(span.attributes);
|
|
2882
|
+
const mergedAttrs = {};
|
|
2883
|
+
for (const [k, v] of Object.entries(resourceAttrs)) {
|
|
2884
|
+
mergedAttrs[k] = v;
|
|
2885
|
+
}
|
|
2886
|
+
for (const [k, v] of Object.entries(spanAttrs)) {
|
|
2887
|
+
mergedAttrs[k] = v;
|
|
2888
|
+
}
|
|
2889
|
+
const parsedEvents = span.events?.length ? span.events.map((e) => ({
|
|
2890
|
+
name: e.name || "",
|
|
2891
|
+
timeMs: toMs(e.timeUnixNano),
|
|
2892
|
+
attributes: attrsToRecord(e.attributes)
|
|
2893
|
+
})) : void 0;
|
|
2894
|
+
const parsedLinks = span.links?.length ? span.links.map((l) => ({
|
|
2895
|
+
traceId: normalizeHexId(l.traceId, 32),
|
|
2896
|
+
spanId: normalizeHexId(l.spanId, 16),
|
|
2897
|
+
attributes: attrsToRecord(l.attributes)
|
|
2898
|
+
})) : void 0;
|
|
2623
2899
|
return {
|
|
2624
2900
|
name: span.name || "unnamed",
|
|
2625
2901
|
spanId: normalizeHexId(span.spanId, 16),
|
|
@@ -2630,7 +2906,9 @@ function otlpSpanToTerminalEvent(span) {
|
|
|
2630
2906
|
durationMs: Math.max(0, endTime - startTime),
|
|
2631
2907
|
status: mapStatus2(span.status?.code),
|
|
2632
2908
|
kind: mapKind2(span.kind),
|
|
2633
|
-
attributes:
|
|
2909
|
+
attributes: mergedAttrs,
|
|
2910
|
+
...parsedEvents ? { events: parsedEvents } : {},
|
|
2911
|
+
...parsedLinks ? { links: parsedLinks } : {}
|
|
2634
2912
|
};
|
|
2635
2913
|
}
|
|
2636
2914
|
async function readJsonBody(req) {
|
|
@@ -2655,8 +2933,8 @@ function sendJson(res, status, data) {
|
|
|
2655
2933
|
}
|
|
2656
2934
|
function parseOtlpEvents(payload) {
|
|
2657
2935
|
const events = [];
|
|
2658
|
-
for (const span of extractSpans(payload)) {
|
|
2659
|
-
events.push(otlpSpanToTerminalEvent(span));
|
|
2936
|
+
for (const { span, resourceAttrs } of extractSpans(payload)) {
|
|
2937
|
+
events.push(otlpSpanToTerminalEvent(span, resourceAttrs));
|
|
2660
2938
|
}
|
|
2661
2939
|
return events;
|
|
2662
2940
|
}
|