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
77const 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
95164const consoleCatcher = createConsoleCatcher ( ) ;
96- export const { initConsoleCatcher, getConsoleLogStack, addErrorEvent } = consoleCatcher ;
165+ export const { initConsoleCatcher, getConsoleLogStack, addErrorEvent } =
166+ consoleCatcher ;
0 commit comments