Skip to content

Commit 3fbb457

Browse files
committed
Implement tracing visualisation with CLI
1 parent 5fdcd64 commit 3fbb457

File tree

9 files changed

+1176
-72
lines changed

9 files changed

+1176
-72
lines changed

package-lock.json

Lines changed: 795 additions & 11 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,12 @@
5555
"prettier": "^3.0.0",
5656
"shx": "^0.3.4",
5757
"systeminformation": "^5.18.5",
58+
"ts-node": "^10.9.2",
5859
"tsx": "^3.12.7",
5960
"typedoc": "^0.24.8",
6061
"typescript": "^5.1.6"
62+
},
63+
"dependencies": {
64+
"ink": "^5.1.0"
6165
}
6266
}

spans.json

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
[
2+
{
3+
"spanId": "span-1739940930333-7m6gy",
4+
"name": "User Request",
5+
"startTime": 1739940930333,
6+
"endTime": 1739940930336,
7+
"parentSpanId": null,
8+
"isCompleted": true,
9+
"children": [
10+
{
11+
"spanId": "span-1739940930336-f00p2",
12+
"name": "Order Processing",
13+
"startTime": 1739940930336,
14+
"endTime": 1739940930336,
15+
"parentSpanId": "span-1739940930333-7m6gy",
16+
"isCompleted": true,
17+
"children": [
18+
{
19+
"spanId": "span-1739940930336-9b868",
20+
"name": "Payment Processing",
21+
"startTime": 1739940930336,
22+
"endTime": 1739940930337,
23+
"parentSpanId": "span-1739940930336-f00p2",
24+
"isCompleted": true,
25+
"children": [
26+
{
27+
"spanId": "span-1739940933340-k80y3",
28+
"name": "Payment Completed",
29+
"startTime": 1739940933340,
30+
"endTime": 1739940933341,
31+
"parentSpanId": "span-1739940930336-9b868",
32+
"isCompleted": true,
33+
"children": []
34+
}
35+
]
36+
},
37+
{
38+
"spanId": "span-1739940935340-zcywd",
39+
"name": "Order Completed",
40+
"startTime": 1739940935340,
41+
"endTime": 1739940935341,
42+
"parentSpanId": "span-1739940930336-f00p2",
43+
"isCompleted": true,
44+
"children": []
45+
}
46+
]
47+
},
48+
{
49+
"spanId": "span-1739940937338-xgx5l",
50+
"name": "User Request Completed",
51+
"startTime": 1739940937338,
52+
"endTime": 1739940937340,
53+
"parentSpanId": "span-1739940930333-7m6gy",
54+
"isCompleted": true,
55+
"children": []
56+
}
57+
]
58+
},
59+
{
60+
"spanId": "span-1739940930336-f00p2",
61+
"name": "Order Processing",
62+
"startTime": 1739940930336,
63+
"endTime": 1739940930336,
64+
"parentSpanId": "span-1739940930333-7m6gy",
65+
"isCompleted": true,
66+
"children": [
67+
{
68+
"spanId": "span-1739940930336-9b868",
69+
"name": "Payment Processing",
70+
"startTime": 1739940930336,
71+
"endTime": 1739940930337,
72+
"parentSpanId": "span-1739940930336-f00p2",
73+
"isCompleted": true,
74+
"children": [
75+
{
76+
"spanId": "span-1739940933340-k80y3",
77+
"name": "Payment Completed",
78+
"startTime": 1739940933340,
79+
"endTime": 1739940933341,
80+
"parentSpanId": "span-1739940930336-9b868",
81+
"isCompleted": true,
82+
"children": []
83+
}
84+
]
85+
},
86+
{
87+
"spanId": "span-1739940935340-zcywd",
88+
"name": "Order Completed",
89+
"startTime": 1739940935340,
90+
"endTime": 1739940935341,
91+
"parentSpanId": "span-1739940930336-f00p2",
92+
"isCompleted": true,
93+
"children": []
94+
}
95+
]
96+
},
97+
{
98+
"spanId": "span-1739940930336-9b868",
99+
"name": "Payment Processing",
100+
"startTime": 1739940930336,
101+
"endTime": 1739940930337,
102+
"parentSpanId": "span-1739940930336-f00p2",
103+
"isCompleted": true,
104+
"children": [
105+
{
106+
"spanId": "span-1739940933340-k80y3",
107+
"name": "Payment Completed",
108+
"startTime": 1739940933340,
109+
"endTime": 1739940933341,
110+
"parentSpanId": "span-1739940930336-9b868",
111+
"isCompleted": true,
112+
"children": []
113+
}
114+
]
115+
},
116+
{
117+
"spanId": "span-1739940933340-k80y3",
118+
"name": "Payment Completed",
119+
"startTime": 1739940933340,
120+
"endTime": 1739940933341,
121+
"parentSpanId": "span-1739940930336-9b868",
122+
"isCompleted": true,
123+
"children": []
124+
},
125+
{
126+
"spanId": "span-1739940935340-zcywd",
127+
"name": "Order Completed",
128+
"startTime": 1739940935340,
129+
"endTime": 1739940935341,
130+
"parentSpanId": "span-1739940930336-f00p2",
131+
"isCompleted": true,
132+
"children": []
133+
},
134+
{
135+
"spanId": "span-1739940937338-xgx5l",
136+
"name": "User Request Completed",
137+
"startTime": 1739940937338,
138+
"endTime": 1739940937340,
139+
"parentSpanId": "span-1739940930333-7m6gy",
140+
"isCompleted": true,
141+
"children": []
142+
}
143+
]

src/Logger.ts

Lines changed: 59 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import type Handler from './Handler.js';
33
import { LogLevel } from './types.js';
44
import ConsoleErrHandler from './handlers/ConsoleErrHandler.js';
55
import * as utils from './utils.js';
6+
import { openSpan, closeSpan } from "./tracing/tracingManager.js";
7+
68

79
class Logger {
810
public readonly key: string;
@@ -107,89 +109,86 @@ class Logger {
107109
delete this.filter;
108110
}
109111

110-
public debug(msg?: ToString, format?: LogFormatter): void;
111-
public debug(
112-
msg: ToString | undefined,
113-
data: LogData,
114-
format?: LogFormatter,
115-
): void;
116-
public debug(
117-
msg?: ToString,
118-
formatOrData?: LogFormatter | LogData,
119-
format?: LogFormatter,
120-
): void {
121-
if (formatOrData == null || typeof formatOrData === 'function') {
122-
return this.log(msg, {}, LogLevel.DEBUG, formatOrData as LogFormatter);
123-
} else {
124-
return this.log(msg, formatOrData, LogLevel.DEBUG, format);
125-
}
112+
public debug(
113+
msg?: ToString,
114+
formatOrData?: LogFormatter | LogData,
115+
format?: LogFormatter,
116+
parentSpanId?: string | undefined
117+
): string {
118+
if (formatOrData == null || typeof formatOrData === 'function') {
119+
return this.log(msg, {}, LogLevel.DEBUG, formatOrData as LogFormatter, parentSpanId);
120+
} else {
121+
return this.log(msg, formatOrData, LogLevel.DEBUG, format, parentSpanId);
122+
}
126123
}
127124

128-
public info(msg?: ToString, format?: LogFormatter): void;
129-
public info(
130-
msg: ToString | undefined,
131-
data: LogData,
132-
format?: LogFormatter,
133-
): void;
125+
134126
public info(
135127
msg?: ToString,
136128
formatOrData?: LogFormatter | LogData,
137129
format?: LogFormatter,
138-
): void {
130+
parentSpanId?: string | undefined
131+
): string {
139132
if (formatOrData == null || typeof formatOrData === 'function') {
140-
return this.log(msg, {}, LogLevel.INFO, formatOrData as LogFormatter);
133+
return this.log(msg, {}, LogLevel.INFO, formatOrData as LogFormatter, parentSpanId);
141134
} else {
142-
return this.log(msg, formatOrData, LogLevel.INFO, format);
135+
return this.log(msg, formatOrData, LogLevel.INFO, format, parentSpanId);
143136
}
144137
}
145138

146-
public warn(msg?: ToString, format?: LogFormatter): void;
147-
public warn(
148-
msg: ToString | undefined,
149-
data: LogData,
150-
format?: LogFormatter,
151-
): void;
152-
public warn(
153-
msg?: ToString,
154-
formatOrData?: LogFormatter | LogData,
155-
format?: LogFormatter,
156-
): void {
157-
if (formatOrData == null || typeof formatOrData === 'function') {
158-
return this.log(msg, {}, LogLevel.WARN, formatOrData as LogFormatter);
159-
} else {
160-
return this.log(msg, formatOrData, LogLevel.WARN, format);
161-
}
139+
140+
141+
public warn(
142+
msg?: ToString,
143+
formatOrData?: LogFormatter | LogData,
144+
format?: LogFormatter,
145+
parentSpanId?: string | undefined
146+
): string {
147+
if (formatOrData == null || typeof formatOrData === 'function') {
148+
return this.log(msg, {}, LogLevel.WARN, formatOrData as LogFormatter, parentSpanId);
149+
} else {
150+
return this.log(msg, formatOrData, LogLevel.WARN, format, parentSpanId);
151+
}
162152
}
163153

164-
public error(msg?: ToString, format?: LogFormatter): void;
165-
public error(
166-
msg: ToString | undefined,
167-
data: LogData,
168-
format?: LogFormatter,
169-
): void;
170-
public error(
171-
msg?: ToString,
172-
formatOrData?: LogFormatter | LogData,
173-
format?: LogFormatter,
174-
): void {
175-
if (formatOrData == null || typeof formatOrData === 'function') {
176-
return this.log(msg, {}, LogLevel.ERROR, formatOrData as LogFormatter);
177-
} else {
178-
return this.log(msg, formatOrData, LogLevel.ERROR, format);
179-
}
154+
155+
156+
public error(
157+
msg?: ToString,
158+
formatOrData?: LogFormatter | LogData,
159+
format?: LogFormatter,
160+
parentSpanId?: string | undefined
161+
): string {
162+
if (formatOrData == null || typeof formatOrData === 'function') {
163+
return this.log(msg, {}, LogLevel.ERROR, formatOrData as LogFormatter, parentSpanId);
164+
} else {
165+
return this.log(msg, formatOrData, LogLevel.ERROR, format, parentSpanId);
166+
}
180167
}
181168

169+
170+
182171
protected log(
183172
msg: ToString | undefined,
184173
data: LogData,
185174
level: LogLevel,
186175
format?: LogFormatter,
187-
): void {
176+
parentSpanId?: string // Optional parent span
177+
): string {
188178
// Filter on level before making a record
189-
if (level < this.getEffectiveLevel()) return;
179+
if (level < this.getEffectiveLevel()) return "";
180+
181+
// 🌟 Open a span, linking it to a parent if provided
182+
const spanId = openSpan(msg?.toString() || 'Log Event', parentSpanId);
183+
190184
const record = this.makeRecord(msg, data, level);
191185
this.callHandlers(record, level, format);
192-
}
186+
187+
// 🌟 Close the span
188+
closeSpan(spanId);
189+
return spanId
190+
}
191+
193192

194193
/**
195194
* Constructs a `LogRecord`

src/tracing/cli.tsx

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import React, { useEffect, useState } from 'react';
2+
import { render, Box, Text } from 'ink';
3+
import { getActiveSpans } from './tracingManager.js';
4+
import fs from "fs";
5+
6+
const SPAN_FILE = "spans.json";
7+
8+
// Function to fetch active spans
9+
const fetchSpanData = () => {
10+
if (!fs.existsSync(SPAN_FILE)) return []; // If file doesn't exist, return empty
11+
const fileData = fs.readFileSync(SPAN_FILE, "utf8");
12+
return JSON.parse(fileData);
13+
};
14+
15+
16+
/**
17+
* Recursively renders spans in a hierarchical structure.
18+
*/
19+
const SpanTree = ({ spans, depth = 0 }) => {
20+
return spans.map(span => (
21+
<Box key={span.spanId} flexDirection="column" paddingLeft={depth}>
22+
<Text color={span.endTime ? "gray" : "green"}>
23+
{span.endTime ? `[✓ Completed]` : `[Running]`} {span.name}
24+
</Text>
25+
{span.children.length > 0 && <SpanTree spans={span.children} depth={depth + 2} />}
26+
</Box>
27+
));
28+
};
29+
30+
/**
31+
* Main React-Ink CLI component.
32+
*/
33+
const App = () => {
34+
const [spans, setSpans] = useState([]);
35+
36+
useEffect(() => {
37+
const interval = setInterval(() => {
38+
setSpans([...fetchSpanData()]);
39+
}, 1000);
40+
return () => clearInterval(interval);
41+
}, []);
42+
43+
44+
return (
45+
<Box flexDirection="column" paddingLeft={2}>
46+
<Text color="cyan">🚀 Real-Time Span Visualization:</Text>
47+
<SpanTree spans={spans} />
48+
</Box>
49+
);
50+
};
51+
52+
// Start the CLI application
53+
render(<App />);

0 commit comments

Comments
 (0)