1- /**
2- * Maximum length for string values in display output
3- * Prevents database storage issues with very large trace spans
4- */
51const MAX_STRING_LENGTH = 10000
6-
7- /**
8- * Maximum recursion depth to prevent stack overflow
9- */
102const MAX_DEPTH = 50
113
12- /**
13- * Truncates a string if it exceeds the maximum length
14- */
154function truncateString ( value : string , maxLength = MAX_STRING_LENGTH ) : string {
165 if ( value . length <= maxLength ) {
176 return value
187 }
198 return `${ value . substring ( 0 , maxLength ) } ... [truncated ${ value . length - maxLength } chars]`
209}
2110
22- /**
23- * Type guard to check if an object is a UserFile
24- */
2511export function isUserFile ( candidate : unknown ) : candidate is {
2612 id : string
2713 name : string
@@ -44,11 +30,6 @@ export function isUserFile(candidate: unknown): candidate is {
4430 )
4531}
4632
47- /**
48- * Filter function that transforms UserFile objects for display
49- * Removes internal fields: key, context
50- * Keeps user-friendly fields: id, name, url, size, type
51- */
5233function filterUserFile ( data : any ) : any {
5334 if ( isUserFile ( data ) ) {
5435 const { id, name, url, size, type } = data
@@ -57,116 +38,122 @@ function filterUserFile(data: any): any {
5738 return data
5839}
5940
60- /**
61- * Registry of filter functions to apply to data for cleaner display in logs/console.
62- * Add new filter functions here to handle additional data types.
63- */
64- const DISPLAY_FILTERS = [
65- filterUserFile ,
66- // Add more filters here as needed
67- ]
68-
69- /**
70- * Generic helper to filter internal/technical fields from data for cleaner display in logs and console.
71- * Applies all registered filters recursively to the data structure.
72- * Also truncates long strings to prevent database storage issues.
73- *
74- *
75- * To add a new filter:
76- * 1. Create a filter function that checks and transforms a specific data type
77- * 2. Add it to the DISPLAY_FILTERS array above
78- *
79- * @param data - Data to filter (objects, arrays, primitives)
80- * @returns Filtered data with internal fields removed and long strings truncated
81- */
41+ const DISPLAY_FILTERS = [ filterUserFile ]
42+
8243export function filterForDisplay ( data : any ) : any {
8344 const seen = new WeakSet ( )
8445 return filterForDisplayInternal ( data , seen , 0 )
8546}
8647
87- function filterForDisplayInternal ( data : any , seen : WeakSet < object > , depth : number ) : any {
88- // Handle null/undefined
89- if ( data === null || data === undefined ) {
90- return data
91- }
92-
93- // Truncate long strings
94- if ( typeof data === 'string' ) {
95- return truncateString ( data )
96- }
97-
98- // Return primitives as-is (number, boolean, bigint, symbol, function)
99- if ( typeof data !== 'object' ) {
100- return data
101- }
48+ function getObjectType ( data : unknown ) : string {
49+ return Object . prototype . toString . call ( data ) . slice ( 8 , - 1 )
50+ }
10251
103- // Prevent infinite recursion from circular references
104- if ( seen . has ( data ) ) {
105- return '[Circular Reference]'
106- }
52+ function filterForDisplayInternal ( data : any , seen : WeakSet < object > , depth : number ) : any {
53+ try {
54+ if ( data === null || data === undefined ) {
55+ return data
56+ }
10757
108- // Prevent stack overflow from very deep nesting
109- if ( depth > MAX_DEPTH ) {
110- return '[Max Depth Exceeded]'
111- }
58+ if ( typeof data === 'string' ) {
59+ return truncateString ( data )
60+ }
11261
113- // Handle special object types before adding to seen set
114- // Date objects - convert to ISO string
115- if ( data instanceof Date ) {
116- return data . toISOString ( )
117- }
62+ if ( typeof data !== 'object' ) {
63+ return data
64+ }
11865
119- // Error objects - preserve message and stack
120- if ( data instanceof Error ) {
121- return {
122- name : data . name ,
123- message : truncateString ( data . message ) ,
124- stack : data . stack ? truncateString ( data . stack ) : undefined ,
66+ if ( seen . has ( data ) ) {
67+ return '[Circular Reference]'
12568 }
126- }
12769
128- // Buffer or TypedArray - don't serialize full content
129- if ( ArrayBuffer . isView ( data ) || data instanceof ArrayBuffer ) {
130- return `[Binary Data: ${ data . byteLength } bytes]`
131- }
70+ if ( depth > MAX_DEPTH ) {
71+ return '[Max Depth Exceeded]'
72+ }
13273
133- // Map - convert to object
134- if ( data instanceof Map ) {
135- const obj : Record < string , any > = { }
136- for ( const [ key , value ] of data . entries ( ) ) {
137- const keyStr = typeof key === 'string' ? key : String ( key )
138- obj [ keyStr ] = filterForDisplayInternal ( value , seen , depth + 1 )
74+ const objectType = getObjectType ( data )
75+
76+ switch ( objectType ) {
77+ case 'Date' : {
78+ const timestamp = ( data as Date ) . getTime ( )
79+ if ( Number . isNaN ( timestamp ) ) {
80+ return '[Invalid Date]'
81+ }
82+ return ( data as Date ) . toISOString ( )
83+ }
84+
85+ case 'RegExp' :
86+ return ( data as RegExp ) . toString ( )
87+
88+ case 'URL' :
89+ return ( data as URL ) . toString ( )
90+
91+ case 'Error' : {
92+ const err = data as Error
93+ return {
94+ name : err . name ,
95+ message : truncateString ( err . message ) ,
96+ stack : err . stack ? truncateString ( err . stack ) : undefined ,
97+ }
98+ }
99+
100+ case 'ArrayBuffer' :
101+ return `[ArrayBuffer: ${ ( data as ArrayBuffer ) . byteLength } bytes]`
102+
103+ case 'Map' : {
104+ const obj : Record < string , any > = { }
105+ for ( const [ key , value ] of ( data as Map < any , any > ) . entries ( ) ) {
106+ const keyStr = typeof key === 'string' ? key : String ( key )
107+ obj [ keyStr ] = filterForDisplayInternal ( value , seen , depth + 1 )
108+ }
109+ return obj
110+ }
111+
112+ case 'Set' :
113+ return Array . from ( data as Set < any > ) . map ( ( item ) =>
114+ filterForDisplayInternal ( item , seen , depth + 1 )
115+ )
116+
117+ case 'WeakMap' :
118+ return '[WeakMap]'
119+
120+ case 'WeakSet' :
121+ return '[WeakSet]'
122+
123+ case 'WeakRef' :
124+ return '[WeakRef]'
125+
126+ case 'Promise' :
127+ return '[Promise]'
139128 }
140- return obj
141- }
142129
143- // Set - convert to array
144- if ( data instanceof Set ) {
145- return Array . from ( data ) . map ( ( item ) => filterForDisplayInternal ( item , seen , depth + 1 ) )
146- }
130+ if ( ArrayBuffer . isView ( data ) ) {
131+ return `[${ objectType } : ${ ( data as ArrayBufferView ) . byteLength } bytes]`
132+ }
147133
148- // Track this object to detect circular references
149- seen . add ( data )
134+ seen . add ( data )
150135
151- // Apply all registered filters
152- for ( const filterFn of DISPLAY_FILTERS ) {
153- const result = filterFn ( data )
154- if ( result !== data ) {
155- // Filter matched and transformed the data
156- // Recursively filter the result in case it contains nested objects
157- return filterForDisplayInternal ( result , seen , depth + 1 )
136+ for ( const filterFn of DISPLAY_FILTERS ) {
137+ const result = filterFn ( data )
138+ if ( result !== data ) {
139+ return filterForDisplayInternal ( result , seen , depth + 1 )
140+ }
158141 }
159- }
160142
161- // No filters matched - recursively filter nested structures
162- if ( Array . isArray ( data ) ) {
163- return data . map ( ( item ) => filterForDisplayInternal ( item , seen , depth + 1 ) )
164- }
143+ if ( Array . isArray ( data ) ) {
144+ return data . map ( ( item ) => filterForDisplayInternal ( item , seen , depth + 1 ) )
145+ }
165146
166- // Recursively filter object properties
167- const result : Record < string , any > = { }
168- for ( const [ key , value ] of Object . entries ( data ) ) {
169- result [ key ] = filterForDisplayInternal ( value , seen , depth + 1 )
147+ const result : Record < string , any > = { }
148+ for ( const key of Object . keys ( data ) ) {
149+ try {
150+ result [ key ] = filterForDisplayInternal ( data [ key ] , seen , depth + 1 )
151+ } catch {
152+ result [ key ] = '[Error accessing property]'
153+ }
154+ }
155+ return result
156+ } catch {
157+ return '[Unserializable]'
170158 }
171- return result
172159}
0 commit comments