11/**
22 * @file Module for intercepting console logs with stack trace capture
33 */
4-
4+ import safeStringify from 'safe-stringify' ;
55import type { ConsoleLogEvent } from '@hawk.so/types' ;
66
7- const createConsoleCatcher = ( ) : {
7+ /**
8+ * Creates a console interceptor that captures and formats console output
9+ */
10+ function createConsoleCatcher ( ) : {
811 initConsoleCatcher : ( ) => void ;
912 addErrorEvent : ( event : ErrorEvent | PromiseRejectionEvent ) => void ;
1013 getConsoleLogStack : ( ) => ConsoleLogEvent [ ] ;
11- } => {
14+ } {
1215 const MAX_LOGS = 20 ;
1316 const consoleOutput : ConsoleLogEvent [ ] = [ ] ;
1417 let isInitialized = false ;
1518
16- const addToConsoleOutput = ( logEvent : ConsoleLogEvent ) : void => {
19+ /**
20+ * Converts any argument to its string representation
21+ *
22+ * @param arg - Value to convert to string
23+ */
24+ function stringifyArg ( arg : unknown ) : string {
25+ if ( typeof arg === 'string' ) {
26+ return arg ;
27+ }
28+ if ( typeof arg === 'number' || typeof arg === 'boolean' ) {
29+ return String ( arg ) ;
30+ }
31+
32+ return safeStringify ( arg ) ;
33+ }
34+
35+ /**
36+ * Formats console arguments handling %c directives
37+ *
38+ * @param args - Console arguments that may include style directives
39+ */
40+ function formatConsoleArgs ( args : unknown [ ] ) : {
41+ message : string ;
42+ styles : string [ ] ;
43+ } {
44+ if ( args . length === 0 ) {
45+ return {
46+ message : '' ,
47+ styles : [ ] ,
48+ } ;
49+ }
50+
51+ const firstArg = args [ 0 ] ;
52+
53+ if ( typeof firstArg !== 'string' || ! firstArg . includes ( '%c' ) ) {
54+ return {
55+ message : args . map ( stringifyArg ) . join ( ' ' ) ,
56+ styles : [ ] ,
57+ } ;
58+ }
59+
60+ // Handle %c formatting
61+ const message = args [ 0 ] as string ;
62+ const styles : string [ ] = [ ] ;
63+
64+ // Extract styles from arguments
65+ let styleIndex = 0 ;
66+
67+ for ( let i = 1 ; i < args . length ; i ++ ) {
68+ const arg = args [ i ] ;
69+
70+ if ( typeof arg === 'string' && message . indexOf ( '%c' , styleIndex ) !== - 1 ) {
71+ styles . push ( arg ) ;
72+ styleIndex = message . indexOf ( '%c' , styleIndex ) + 2 ;
73+ }
74+ }
75+
76+ // Add remaining arguments that aren't styles
77+ const remainingArgs = args
78+ . slice ( styles . length + 1 )
79+ . map ( stringifyArg )
80+ . join ( ' ' ) ;
81+
82+ return {
83+ message : message + ( remainingArgs ? ' ' + remainingArgs : '' ) ,
84+ styles,
85+ } ;
86+ }
87+
88+ /**
89+ * Adds a console log event to the output buffer
90+ *
91+ * @param logEvent - The console log event to be added to the output buffer
92+ */
93+ function addToConsoleOutput ( logEvent : ConsoleLogEvent ) : void {
1794 if ( consoleOutput . length >= MAX_LOGS ) {
1895 consoleOutput . shift ( ) ;
1996 }
2097 consoleOutput . push ( logEvent ) ;
21- } ;
22-
23- const createConsoleEventFromError = (
98+ }
99+
100+ /**
101+ * Creates a console log event from an error or promise rejection
102+ *
103+ * @param event - The error event or promise rejection event to convert
104+ */
105+ function createConsoleEventFromError (
24106 event : ErrorEvent | PromiseRejectionEvent
25- ) : ConsoleLogEvent => {
107+ ) : ConsoleLogEvent {
26108 if ( event instanceof ErrorEvent ) {
27109 return {
28110 method : 'error' ,
@@ -44,53 +126,73 @@ const createConsoleCatcher = (): {
44126 stack : event . reason ?. stack || '' ,
45127 fileLine : '' ,
46128 } ;
47- } ;
129+ }
130+
131+ /**
132+ * Initializes the console interceptor by overriding default console methods
133+ */
134+ function initConsoleCatcher ( ) : void {
135+ if ( isInitialized ) {
136+ return ;
137+ }
48138
49- return {
50- initConsoleCatcher ( ) : void {
51- if ( isInitialized ) {
139+ isInitialized = true ;
140+ const consoleMethods : string [ ] = [ 'log' , 'warn' , 'error' , 'info' , 'debug' ] ;
141+
142+ consoleMethods . forEach ( function overrideConsoleMethod ( method ) {
143+ if ( typeof window . console [ method ] !== 'function' ) {
52144 return ;
53145 }
54146
55- isInitialized = true ;
56- const consoleMethods : string [ ] = [ 'log' , 'warn' , 'error' , 'info' , 'debug' ] ;
57-
58- consoleMethods . forEach ( ( method ) => {
59- if ( typeof window . console [ method ] !== 'function' ) {
60- return ;
61- }
62-
63- const oldFunction = window . console [ method ] . bind ( window . console ) ;
64-
65- window . console [ method ] = function ( ...args : unknown [ ] ) : void {
66- const stack = new Error ( ) . stack ?. split ( '\n' ) . slice ( 2 ) . join ( '\n' ) || '' ;
67-
68- const logEvent : ConsoleLogEvent = {
69- method,
70- timestamp : new Date ( ) ,
71- type : method ,
72- message : args . map ( ( arg ) => typeof arg === 'string' ? arg : JSON . stringify ( arg ) ) . join ( ' ' ) ,
73- stack,
74- fileLine : stack . split ( '\n' ) [ 0 ] ?. trim ( ) ,
75- } ;
76-
77- addToConsoleOutput ( logEvent ) ;
78- oldFunction ( ...args ) ;
147+ const oldFunction = window . console [ method ] . bind ( window . console ) ;
148+
149+ window . console [ method ] = function ( ...args : unknown [ ] ) : void {
150+ const stack = new Error ( ) . stack ?. split ( '\n' ) . slice ( 2 )
151+ . join ( '\n' ) || '' ;
152+ const { message, styles } = formatConsoleArgs ( args ) ;
153+
154+ const logEvent : ConsoleLogEvent = {
155+ method,
156+ timestamp : new Date ( ) ,
157+ type : method ,
158+ message,
159+ stack,
160+ fileLine : stack . split ( '\n' ) [ 0 ] ?. trim ( ) ,
161+ styles,
79162 } ;
80- } ) ;
81- } ,
82163
83- addErrorEvent ( event : ErrorEvent | PromiseRejectionEvent ) : void {
84- const logEvent = createConsoleEventFromError ( event ) ;
85-
86- addToConsoleOutput ( logEvent ) ;
87- } ,
164+ addToConsoleOutput ( logEvent ) ;
165+ oldFunction ( ...args ) ;
166+ } ;
167+ } ) ;
168+ }
169+
170+ /**
171+ * Handles error events by converting them to console log events
172+ *
173+ * @param event - The error or promise rejection event to handle
174+ */
175+ function addErrorEvent ( event : ErrorEvent | PromiseRejectionEvent ) : void {
176+ const logEvent = createConsoleEventFromError ( event ) ;
177+
178+ addToConsoleOutput ( logEvent ) ;
179+ }
180+
181+ /**
182+ * Returns the current console output buffer
183+ */
184+ function getConsoleLogStack ( ) : ConsoleLogEvent [ ] {
185+ return [ ...consoleOutput ] ;
186+ }
88187
89- getConsoleLogStack ( ) : ConsoleLogEvent [ ] {
90- return [ ...consoleOutput ] ;
91- } ,
188+ return {
189+ initConsoleCatcher,
190+ addErrorEvent,
191+ getConsoleLogStack,
92192 } ;
93- } ;
193+ }
94194
95195const consoleCatcher = createConsoleCatcher ( ) ;
96- export const { initConsoleCatcher, getConsoleLogStack, addErrorEvent } = consoleCatcher ;
196+
197+ export const { initConsoleCatcher, getConsoleLogStack, addErrorEvent } =
198+ consoleCatcher ;
0 commit comments