Skip to content

Commit 63d600b

Browse files
committed
custom serialization to not break tracepsans
1 parent f5ac2de commit 63d600b

File tree

1 file changed

+98
-111
lines changed

1 file changed

+98
-111
lines changed
Lines changed: 98 additions & 111 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,13 @@
1-
/**
2-
* Maximum length for string values in display output
3-
* Prevents database storage issues with very large trace spans
4-
*/
51
const MAX_STRING_LENGTH = 10000
6-
7-
/**
8-
* Maximum recursion depth to prevent stack overflow
9-
*/
102
const MAX_DEPTH = 50
113

12-
/**
13-
* Truncates a string if it exceeds the maximum length
14-
*/
154
function 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-
*/
2511
export 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-
*/
5233
function 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+
8243
export 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

Comments
 (0)