Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tracing Visualization #46

Closed
wants to merge 9 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
808 changes: 796 additions & 12 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion package.json
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are a lot of changes here which shouldn't have been made here. You should only make the smallest amount of changes required. At your level, you should only need to change packages and maybe add one or two new commands.

Compare your new package.json with the one on staging and merge the changes in both.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I remember this now. I made those changes because I was usingts-node, but since we have migrated to tsx, merging the files isn't necessary. The existing file works with my code.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then make sure the additions like new bin commands or new packages are reflected in the newer file as well.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't want any additional dependencies for the tracing system. It be entirely self-contained.

Original file line number Diff line number Diff line change
Expand Up @@ -59,4 +59,4 @@
"typedoc": "^0.24.8",
"typescript": "^5.1.6"
}
}
}
57 changes: 57 additions & 0 deletions spans.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
[
{
"spanId": "span-1740997147711-c4j4n",
"name": "Root Span",
"startTime": 1740997147711,
"endTime": 1740997155715,
"parentSpanId": null,
"isCompleted": true,
"children": [
{
"spanId": "span-1740997148715-wle8c",
"name": "Parent span ends earlier",
"startTime": 1740997148715,
"endTime": 1740997150717,
"parentSpanId": "span-1740997147711-c4j4n",
"isCompleted": true,
"children": []
},
{
"spanId": "span-1740997149716-2u7wc",
"name": "Forking",
"startTime": 1740997149716,
"endTime": 1740997153718,
"parentSpanId": "span-1740997147711-c4j4n",
"isCompleted": true,
"children": []
}
]
},
{
"spanId": "span-1740997148715-wle8c",
"name": "Parent span ends earlier",
"startTime": 1740997148715,
"endTime": 1740997150717,
"parentSpanId": "span-1740997147711-c4j4n",
"isCompleted": true,
"children": []
},
{
"spanId": "span-1740997149716-2u7wc",
"name": "Forking",
"startTime": 1740997149716,
"endTime": 1740997153718,
"parentSpanId": "span-1740997147711-c4j4n",
"isCompleted": true,
"children": []
},
{
"spanId": "span-1740997150715-7a1qz",
"name": "Orphan",
"startTime": 1740997150715,
"endTime": 1740997154717,
"parentSpanId": null,
"isCompleted": true,
"children": []
}
]
117 changes: 57 additions & 60 deletions src/Logger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import type Handler from './Handler.js';
import { LogLevel } from './types.js';
import ConsoleErrHandler from './handlers/ConsoleErrHandler.js';
import * as utils from './utils.js';
import { openSpan, closeSpan } from "./lib/tracingManager.js";


class Logger {
public readonly key: string;
Expand Down Expand Up @@ -107,89 +109,84 @@ class Logger {
delete this.filter;
}

public debug(msg?: ToString, format?: LogFormatter): void;
public debug(
msg: ToString | undefined,
data: LogData,
format?: LogFormatter,
): void;
public debug(
msg?: ToString,
formatOrData?: LogFormatter | LogData,
format?: LogFormatter,
): void {
if (formatOrData == null || typeof formatOrData === 'function') {
return this.log(msg, {}, LogLevel.DEBUG, formatOrData as LogFormatter);
} else {
return this.log(msg, formatOrData, LogLevel.DEBUG, format);
}
public debug(
msg?: ToString,
formatOrData?: LogFormatter | LogData,
format?: LogFormatter,
parentSpanId?: string | undefined
): string {
if (formatOrData == null || typeof formatOrData === 'function') {
return this.log(msg, {}, LogLevel.DEBUG, formatOrData as LogFormatter, parentSpanId);
} else {
return this.log(msg, formatOrData, LogLevel.DEBUG, format, parentSpanId);
}
}

public info(msg?: ToString, format?: LogFormatter): void;
public info(
msg: ToString | undefined,
data: LogData,
format?: LogFormatter,
): void;

public info(
msg?: ToString,
formatOrData?: LogFormatter | LogData,
format?: LogFormatter,
): void {
parentSpanId?: string | undefined
): string {
if (formatOrData == null || typeof formatOrData === 'function') {
return this.log(msg, {}, LogLevel.INFO, formatOrData as LogFormatter);
return this.log(msg, {}, LogLevel.INFO, formatOrData as LogFormatter, parentSpanId);
} else {
return this.log(msg, formatOrData, LogLevel.INFO, format);
return this.log(msg, formatOrData, LogLevel.INFO, format, parentSpanId);
}
}

public warn(msg?: ToString, format?: LogFormatter): void;
public warn(
msg: ToString | undefined,
data: LogData,
format?: LogFormatter,
): void;
public warn(
msg?: ToString,
formatOrData?: LogFormatter | LogData,
format?: LogFormatter,
): void {
if (formatOrData == null || typeof formatOrData === 'function') {
return this.log(msg, {}, LogLevel.WARN, formatOrData as LogFormatter);
} else {
return this.log(msg, formatOrData, LogLevel.WARN, format);
}


public warn(
msg?: ToString,
formatOrData?: LogFormatter | LogData,
format?: LogFormatter,
parentSpanId?: string | undefined
): string {
if (formatOrData == null || typeof formatOrData === 'function') {
return this.log(msg, {}, LogLevel.WARN, formatOrData as LogFormatter, parentSpanId);
} else {
return this.log(msg, formatOrData, LogLevel.WARN, format, parentSpanId);
}
}

public error(msg?: ToString, format?: LogFormatter): void;
public error(
msg: ToString | undefined,
data: LogData,
format?: LogFormatter,
): void;
public error(
msg?: ToString,
formatOrData?: LogFormatter | LogData,
format?: LogFormatter,
): void {
if (formatOrData == null || typeof formatOrData === 'function') {
return this.log(msg, {}, LogLevel.ERROR, formatOrData as LogFormatter);
} else {
return this.log(msg, formatOrData, LogLevel.ERROR, format);
}


public error(
msg?: ToString,
formatOrData?: LogFormatter | LogData,
format?: LogFormatter,
parentSpanId?: string | undefined
): string {
if (formatOrData == null || typeof formatOrData === 'function') {
return this.log(msg, {}, LogLevel.ERROR, formatOrData as LogFormatter, parentSpanId);
} else {
return this.log(msg, formatOrData, LogLevel.ERROR, format, parentSpanId);
}
}



protected log(
msg: ToString | undefined,
data: LogData,
level: LogLevel,
format?: LogFormatter,
): void {
parentSpanId?: string // Optional parent span
): string {
// Filter on level before making a record
if (level < this.getEffectiveLevel()) return;
if (level < this.getEffectiveLevel()) return "";

const spanId = openSpan(msg?.toString() || 'Log Event', parentSpanId);

const record = this.makeRecord(msg, data, level);
this.callHandlers(record, level, format);
}

closeSpan(spanId);
return spanId
}


/**
* Constructs a `LogRecord`
Expand Down
112 changes: 112 additions & 0 deletions src/bin/SpanTree.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import React, { FC } from 'react';
import { Box, Text } from 'ink';
import { Span } from "../lib/span.js";

// Props for handling both root and child spans
interface SpanTreeProps {
spans: Span[];
sampleMode: string;
}

/**
* Sort spans based on the selected sampling mode.
*/
const sortSpans = (spans: Span[], mode: string): Span[] => {
if (mode === "logical") {
const spanMap = new Map<string, Span>();

// Step 1: Convert raw objects to Span instances
spans.forEach(span => {
if (!spanMap.has(span.spanId)) {
const newSpan = new Span(span.name, span.parentSpanId);
Object.assign(newSpan, span);
newSpan.children = [];
spanMap.set(span.spanId, newSpan);
}
});

const rootSpans: Span[] = [];

// Step 2: Link children to parents
spans.forEach(span => {
if (span.parentSpanId && spanMap.has(span.parentSpanId)) {
spanMap.get(span.parentSpanId)!.children.push(spanMap.get(span.spanId)!);
}
});

// Step 3: Collect only true root spans (avoiding duplicates)
spans.forEach(span => {
if (!span.parentSpanId) {
const rootSpan = spanMap.get(span.spanId);
if (rootSpan) {
rootSpans.push(rootSpan);
}
}
});

return rootSpans;
} else {
return spans
.map(span => new Span(span.name, span.parentSpanId)) // Convert to Span instances
.sort((a, b) => a.startTime - b.startTime); // Sort purely by start time
}
};


/**
* **Recursive Renderer**
* - Uses box-drawing characters (│ ├ └) for structured layout.
*/
const RecursiveSpanTree: FC<{ span: Span; prefix: string; isLastChild: boolean }> = ({
span,
prefix,
isLastChild,
}) => {
const connector = isLastChild ? '└── ' : '├── ';
const newPrefix = prefix + (isLastChild ? ' ' : '│ '); // Maintain vertical structure

return (
<Box flexDirection="column">
<Text>
{prefix}
{connector}
{span.name}
</Text>

{span.children.map((child, idx) => (
<RecursiveSpanTree
key={child.spanId}
span={child}
prefix={newPrefix}
isLastChild={idx === span.children.length - 1}
/>
))}
</Box>
);
};

/**
* **Main Component** (Sorts & Passes Data)
*/
const SpanTree: FC<SpanTreeProps> = ({ spans, sampleMode }) => {
const sortedSpans = sortSpans(spans, sampleMode);

return (
<Box flexDirection="column">
{sortedSpans.length === 0 ? (
<Text>No spans to display</Text>
) : (
sortedSpans.map((span, idx) => (
<RecursiveSpanTree
key={span.spanId}
span={span}
prefix=""
isLastChild={idx === sortedSpans.length - 1}
/>
))
)}
</Box>
);
};

export default SpanTree;
Loading
Loading