Skip to content

Commit 9923bef

Browse files
committed
safeStringify & %c support
1 parent e4277e7 commit 9923bef

File tree

1 file changed

+86
-16
lines changed

1 file changed

+86
-16
lines changed

src/addons/consoleCatcher.ts

Lines changed: 86 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
* @file Module for intercepting console logs with stack trace capture
33
*/
44

5-
import type { ConsoleLogEvent } from '@hawk.so/types';
5+
import type { ConsoleLogEvent } from "@hawk.so/types";
66

77
const createConsoleCatcher = (): {
88
initConsoleCatcher: () => void;
@@ -13,6 +13,66 @@ const createConsoleCatcher = (): {
1313
const consoleOutput: ConsoleLogEvent[] = [];
1414
let isInitialized = false;
1515

16+
/**
17+
* Safely stringifies objects handling circular references
18+
*/
19+
const safeStringify = (obj: unknown): string => {
20+
const seen = new WeakSet();
21+
return JSON.stringify(obj, (key, value) => {
22+
if (typeof value === "object" && value !== null) {
23+
if (seen.has(value)) {
24+
return "[Circular]";
25+
}
26+
seen.add(value);
27+
}
28+
return value;
29+
});
30+
};
31+
32+
/**
33+
* Formats console arguments handling %c directives
34+
*/
35+
const formatConsoleArgs = (
36+
args: unknown[]
37+
): { message: string; styles: string[] } => {
38+
if (args.length === 0) return { message: "", styles: [] };
39+
40+
const firstArg = args[0];
41+
if (typeof firstArg !== "string" || !firstArg.includes("%c")) {
42+
return {
43+
message: args
44+
.map((arg) => (typeof arg === "string" ? arg : safeStringify(arg)))
45+
.join(" "),
46+
styles: [],
47+
};
48+
}
49+
50+
// Handle %c formatting
51+
const message = args[0] as string;
52+
const styles: string[] = [];
53+
54+
// Extract styles from arguments
55+
let styleIndex = 0;
56+
for (let i = 1; i < args.length; i++) {
57+
const arg = args[i];
58+
if (typeof arg === "string" && message.indexOf("%c", styleIndex) !== -1) {
59+
styles.push(arg);
60+
styleIndex = message.indexOf("%c", styleIndex) + 2;
61+
}
62+
}
63+
64+
// Add remaining arguments that aren't styles
65+
const remainingArgs = args
66+
.slice(styles.length + 1)
67+
.map((arg) => (typeof arg === "string" ? arg : safeStringify(arg)))
68+
.join(" ");
69+
70+
return {
71+
message: message + (remainingArgs ? " " + remainingArgs : ""),
72+
styles,
73+
};
74+
};
75+
1676
const addToConsoleOutput = (logEvent: ConsoleLogEvent): void => {
1777
if (consoleOutput.length >= MAX_LOGS) {
1878
consoleOutput.shift();
@@ -25,24 +85,24 @@ const createConsoleCatcher = (): {
2585
): ConsoleLogEvent => {
2686
if (event instanceof ErrorEvent) {
2787
return {
28-
method: 'error',
88+
method: "error",
2989
timestamp: new Date(),
30-
type: event.error?.name || 'Error',
90+
type: event.error?.name || "Error",
3191
message: event.error?.message || event.message,
32-
stack: event.error?.stack || '',
92+
stack: event.error?.stack || "",
3393
fileLine: event.filename
3494
? `${event.filename}:${event.lineno}:${event.colno}`
35-
: '',
95+
: "",
3696
};
3797
}
3898

3999
return {
40-
method: 'error',
100+
method: "error",
41101
timestamp: new Date(),
42-
type: 'UnhandledRejection',
102+
type: "UnhandledRejection",
43103
message: event.reason?.message || String(event.reason),
44-
stack: event.reason?.stack || '',
45-
fileLine: '',
104+
stack: event.reason?.stack || "",
105+
fileLine: "",
46106
};
47107
};
48108

@@ -53,25 +113,34 @@ const createConsoleCatcher = (): {
53113
}
54114

55115
isInitialized = true;
56-
const consoleMethods: string[] = ['log', 'warn', 'error', 'info', 'debug'];
116+
const consoleMethods: string[] = [
117+
"log",
118+
"warn",
119+
"error",
120+
"info",
121+
"debug",
122+
];
57123

58124
consoleMethods.forEach((method) => {
59-
if (typeof window.console[method] !== 'function') {
125+
if (typeof window.console[method] !== "function") {
60126
return;
61127
}
62128

63129
const oldFunction = window.console[method].bind(window.console);
64130

65131
window.console[method] = function (...args: unknown[]): void {
66-
const stack = new Error().stack?.split('\n').slice(2).join('\n') || '';
132+
const stack =
133+
new Error().stack?.split("\n").slice(2).join("\n") || "";
134+
const { message, styles } = formatConsoleArgs(args);
67135

68136
const logEvent: ConsoleLogEvent = {
69137
method,
70138
timestamp: new Date(),
71139
type: method,
72-
message: args.map((arg) => typeof arg === 'string' ? arg : JSON.stringify(arg)).join(' '),
140+
message,
73141
stack,
74-
fileLine: stack.split('\n')[0]?.trim(),
142+
fileLine: stack.split("\n")[0]?.trim(),
143+
styles,
75144
};
76145

77146
addToConsoleOutput(logEvent);
@@ -87,10 +156,11 @@ const createConsoleCatcher = (): {
87156
},
88157

89158
getConsoleLogStack(): ConsoleLogEvent[] {
90-
return [ ...consoleOutput ];
159+
return [...consoleOutput];
91160
},
92161
};
93162
};
94163

95164
const consoleCatcher = createConsoleCatcher();
96-
export const { initConsoleCatcher, getConsoleLogStack, addErrorEvent } = consoleCatcher;
165+
export const { initConsoleCatcher, getConsoleLogStack, addErrorEvent } =
166+
consoleCatcher;

0 commit comments

Comments
 (0)