1
1
/**
2
2
* @file Module for intercepting console logs with stack trace capture
3
3
*/
4
-
4
+ import safeStringify from 'safe-stringify' ;
5
5
import type { ConsoleLogEvent } from '@hawk.so/types' ;
6
6
7
- const createConsoleCatcher = ( ) : {
7
+ /**
8
+ * Creates a console interceptor that captures and formats console output
9
+ */
10
+ function createConsoleCatcher ( ) : {
8
11
initConsoleCatcher : ( ) => void ;
9
12
addErrorEvent : ( event : ErrorEvent | PromiseRejectionEvent ) => void ;
10
13
getConsoleLogStack : ( ) => ConsoleLogEvent [ ] ;
11
- } => {
14
+ } {
12
15
const MAX_LOGS = 20 ;
13
16
const consoleOutput : ConsoleLogEvent [ ] = [ ] ;
14
17
let isInitialized = false ;
15
18
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 {
17
94
if ( consoleOutput . length >= MAX_LOGS ) {
18
95
consoleOutput . shift ( ) ;
19
96
}
20
97
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 (
24
106
event : ErrorEvent | PromiseRejectionEvent
25
- ) : ConsoleLogEvent => {
107
+ ) : ConsoleLogEvent {
26
108
if ( event instanceof ErrorEvent ) {
27
109
return {
28
110
method : 'error' ,
@@ -44,53 +126,73 @@ const createConsoleCatcher = (): {
44
126
stack : event . reason ?. stack || '' ,
45
127
fileLine : '' ,
46
128
} ;
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
+ }
48
138
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' ) {
52
144
return ;
53
145
}
54
146
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,
79
162
} ;
80
- } ) ;
81
- } ,
82
163
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
+ }
88
187
89
- getConsoleLogStack ( ) : ConsoleLogEvent [ ] {
90
- return [ ...consoleOutput ] ;
91
- } ,
188
+ return {
189
+ initConsoleCatcher,
190
+ addErrorEvent,
191
+ getConsoleLogStack,
92
192
} ;
93
- } ;
193
+ }
94
194
95
195
const consoleCatcher = createConsoleCatcher ( ) ;
96
- export const { initConsoleCatcher, getConsoleLogStack, addErrorEvent } = consoleCatcher ;
196
+
197
+ export const { initConsoleCatcher, getConsoleLogStack, addErrorEvent } =
198
+ consoleCatcher ;
0 commit comments