Skip to content

Commit eea9877

Browse files
CopilotneSpecc
andauthored
bug(sentry): Flatten nested objects in backtrace frame arguments using dot notation (#509)
* Initial plan * Implement dot notation for nested objects in backtrace frame arguments Co-authored-by: neSpecc <3684889+neSpecc@users.noreply.github.com> * Fix empty array handling to be consistent with empty objects Co-authored-by: neSpecc <3684889+neSpecc@users.noreply.github.com> * Remove unrelated file changes that should not be affected by the solution Co-authored-by: neSpecc <3684889+neSpecc@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: neSpecc <3684889+neSpecc@users.noreply.github.com>
1 parent 2cb3e5b commit eea9877

File tree

3 files changed

+206
-3
lines changed

3 files changed

+206
-3
lines changed

workers/sentry/src/utils/converter.ts

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,53 @@
11
import { BacktraceFrame, DefaultAddons, EventContext, EventData, Json, SentryAddons } from '@hawk.so/types';
22
import { Event as SentryEvent } from '@sentry/core';
33

4+
/**
5+
* Flattens a nested object into an array of strings using dot notation
6+
* For example: {foo: 1, bar: {baz: 2}} becomes ["foo=1", "bar.baz=2"]
7+
*
8+
* @param obj - The object to flatten
9+
* @param prefix - The prefix to use for nested keys (used in recursion)
10+
*/
11+
function flattenObject(obj: unknown, prefix = ''): string[] {
12+
const result: string[] = [];
13+
14+
if (obj === null || obj === undefined) {
15+
return [ prefix ? `${prefix}=${obj}` : String(obj) ];
16+
}
17+
18+
if (typeof obj !== 'object') {
19+
return [ prefix ? `${prefix}=${obj}` : String(obj) ];
20+
}
21+
22+
if (Array.isArray(obj)) {
23+
if (obj.length === 0) {
24+
return [ prefix ? `${prefix}=[]` : '[]' ];
25+
}
26+
27+
obj.forEach((value, index) => {
28+
const key = prefix ? `${prefix}.${index}` : String(index);
29+
30+
result.push(...flattenObject(value, key));
31+
});
32+
33+
return result;
34+
}
35+
36+
const entries = Object.entries(obj);
37+
38+
if (entries.length === 0) {
39+
return [ prefix ? `${prefix}={}` : '{}' ];
40+
}
41+
42+
entries.forEach(([key, value]) => {
43+
const newPrefix = prefix ? `${prefix}.${key}` : key;
44+
45+
result.push(...flattenObject(value, newPrefix));
46+
});
47+
48+
return result;
49+
}
50+
451
/**
552
* Compose title from Sentry event payload
653
*
@@ -79,8 +126,8 @@ export function composeBacktrace(eventPayload: SentryEvent): EventData<DefaultAd
79126
}
80127

81128
if (frame.vars) {
82-
backtraceFrame.arguments = Object.entries(frame.vars).map(([name, value]) => {
83-
return `${name}=${value}`;
129+
backtraceFrame.arguments = Object.entries(frame.vars).flatMap(([name, value]) => {
130+
return flattenObject(value, name);
84131
});
85132
}
86133

workers/sentry/tests/converter.test.ts

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,162 @@ describe('converter utils', () => {
9393
});
9494
});
9595

96+
it('should handle nested objects in vars using dot notation', () => {
97+
const event: SentryEvent = {
98+
exception: {
99+
values: [ {
100+
stacktrace: {
101+
frames: [ {
102+
filename: 'test.js',
103+
lineno: 10,
104+
vars: {
105+
params: {
106+
foo: 1,
107+
bar: 2,
108+
second: {
109+
glass: 3,
110+
},
111+
},
112+
},
113+
} ],
114+
},
115+
} ],
116+
},
117+
};
118+
119+
const backtrace = composeBacktrace(event);
120+
121+
expect(backtrace?.[0].arguments).toEqual([
122+
'params.foo=1',
123+
'params.bar=2',
124+
'params.second.glass=3',
125+
]);
126+
});
127+
128+
it('should handle arrays in vars using dot notation with indices', () => {
129+
const event: SentryEvent = {
130+
exception: {
131+
values: [ {
132+
stacktrace: {
133+
frames: [ {
134+
filename: 'test.js',
135+
lineno: 10,
136+
vars: {
137+
items: ['first', 'second', 'third'],
138+
},
139+
} ],
140+
},
141+
} ],
142+
},
143+
};
144+
145+
const backtrace = composeBacktrace(event);
146+
147+
expect(backtrace?.[0].arguments).toEqual([
148+
'items.0=first',
149+
'items.1=second',
150+
'items.2=third',
151+
]);
152+
});
153+
154+
it('should handle mixed nested objects and arrays in vars', () => {
155+
const event: SentryEvent = {
156+
exception: {
157+
values: [ {
158+
stacktrace: {
159+
frames: [ {
160+
filename: 'test.js',
161+
lineno: 10,
162+
vars: {
163+
config: {
164+
users: [
165+
{
166+
name: 'Alice',
167+
age: 30,
168+
},
169+
{
170+
name: 'Bob',
171+
age: 25,
172+
},
173+
],
174+
settings: {
175+
enabled: true,
176+
},
177+
},
178+
},
179+
} ],
180+
},
181+
} ],
182+
},
183+
};
184+
185+
const backtrace = composeBacktrace(event);
186+
187+
expect(backtrace?.[0].arguments).toEqual([
188+
'config.users.0.name=Alice',
189+
'config.users.0.age=30',
190+
'config.users.1.name=Bob',
191+
'config.users.1.age=25',
192+
'config.settings.enabled=true',
193+
]);
194+
});
195+
196+
it('should handle null and undefined values in vars', () => {
197+
const event: SentryEvent = {
198+
exception: {
199+
values: [ {
200+
stacktrace: {
201+
frames: [ {
202+
filename: 'test.js',
203+
lineno: 10,
204+
vars: {
205+
nullValue: null,
206+
undefinedValue: undefined,
207+
normalValue: 'test',
208+
},
209+
} ],
210+
},
211+
} ],
212+
},
213+
};
214+
215+
const backtrace = composeBacktrace(event);
216+
217+
expect(backtrace?.[0].arguments).toEqual([
218+
'nullValue=null',
219+
'undefinedValue=undefined',
220+
'normalValue=test',
221+
]);
222+
});
223+
224+
it('should handle empty objects and arrays in vars', () => {
225+
const event: SentryEvent = {
226+
exception: {
227+
values: [ {
228+
stacktrace: {
229+
frames: [ {
230+
filename: 'test.js',
231+
lineno: 10,
232+
vars: {
233+
emptyObject: {},
234+
emptyArray: [],
235+
normalValue: 'test',
236+
},
237+
} ],
238+
},
239+
} ],
240+
},
241+
};
242+
243+
const backtrace = composeBacktrace(event);
244+
245+
expect(backtrace?.[0].arguments).toEqual([
246+
'emptyObject={}',
247+
'emptyArray=[]',
248+
'normalValue=test',
249+
]);
250+
});
251+
96252
it('should reverse frames', () => {
97253
const event: SentryEvent = {
98254
exception: {

workers/sentry/tests/index.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -689,7 +689,7 @@ describe('SentryEventWorker', () => {
689689
'__package__=None',
690690
'__loader__=<_frozen_importlib_external.SourceFileLoader object at 0x102934cb0>',
691691
'__spec__=None',
692-
'__annotations__=[object Object]',
692+
'__annotations__={}',
693693
"__builtins__=<module 'builtins' (built-in)>",
694694
"__file__='/Users/nostr/dev/codex/hawk.mono/tests/manual/sentry/sentry-prod.py'",
695695
'__cached__=None',

0 commit comments

Comments
 (0)