Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 49 additions & 2 deletions workers/sentry/src/utils/converter.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,53 @@
import { BacktraceFrame, DefaultAddons, EventContext, EventData, Json, SentryAddons } from '@hawk.so/types';
import { Event as SentryEvent } from '@sentry/core';

/**
* Flattens a nested object into an array of strings using dot notation
* For example: {foo: 1, bar: {baz: 2}} becomes ["foo=1", "bar.baz=2"]
*
* @param obj - The object to flatten
* @param prefix - The prefix to use for nested keys (used in recursion)
*/
function flattenObject(obj: unknown, prefix = ''): string[] {
const result: string[] = [];

if (obj === null || obj === undefined) {
return [ prefix ? `${prefix}=${obj}` : String(obj) ];
}

if (typeof obj !== 'object') {
return [ prefix ? `${prefix}=${obj}` : String(obj) ];
}

if (Array.isArray(obj)) {
if (obj.length === 0) {
return [ prefix ? `${prefix}=[]` : '[]' ];
}

obj.forEach((value, index) => {
const key = prefix ? `${prefix}.${index}` : String(index);

result.push(...flattenObject(value, key));
});

return result;
}

const entries = Object.entries(obj);

if (entries.length === 0) {
return [ prefix ? `${prefix}={}` : '{}' ];
}

entries.forEach(([key, value]) => {
const newPrefix = prefix ? `${prefix}.${key}` : key;

result.push(...flattenObject(value, newPrefix));
});

return result;
}

/**
* Compose title from Sentry event payload
*
Expand Down Expand Up @@ -79,8 +126,8 @@ export function composeBacktrace(eventPayload: SentryEvent): EventData<DefaultAd
}

if (frame.vars) {
backtraceFrame.arguments = Object.entries(frame.vars).map(([name, value]) => {
return `${name}=${value}`;
backtraceFrame.arguments = Object.entries(frame.vars).flatMap(([name, value]) => {
return flattenObject(value, name);
});
}

Expand Down
156 changes: 156 additions & 0 deletions workers/sentry/tests/converter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,162 @@ describe('converter utils', () => {
});
});

it('should handle nested objects in vars using dot notation', () => {
const event: SentryEvent = {
exception: {
values: [ {
stacktrace: {
frames: [ {
filename: 'test.js',
lineno: 10,
vars: {
params: {
foo: 1,
bar: 2,
second: {
glass: 3,
},
},
},
} ],
},
} ],
},
};

const backtrace = composeBacktrace(event);

expect(backtrace?.[0].arguments).toEqual([
'params.foo=1',
'params.bar=2',
'params.second.glass=3',
]);
});

it('should handle arrays in vars using dot notation with indices', () => {
const event: SentryEvent = {
exception: {
values: [ {
stacktrace: {
frames: [ {
filename: 'test.js',
lineno: 10,
vars: {
items: ['first', 'second', 'third'],
},
} ],
},
} ],
},
};

const backtrace = composeBacktrace(event);

expect(backtrace?.[0].arguments).toEqual([
'items.0=first',
'items.1=second',
'items.2=third',
]);
});

it('should handle mixed nested objects and arrays in vars', () => {
const event: SentryEvent = {
exception: {
values: [ {
stacktrace: {
frames: [ {
filename: 'test.js',
lineno: 10,
vars: {
config: {
users: [
{
name: 'Alice',
age: 30,
},
{
name: 'Bob',
age: 25,
},
],
settings: {
enabled: true,
},
},
},
} ],
},
} ],
},
};

const backtrace = composeBacktrace(event);

expect(backtrace?.[0].arguments).toEqual([
'config.users.0.name=Alice',
'config.users.0.age=30',
'config.users.1.name=Bob',
'config.users.1.age=25',
'config.settings.enabled=true',
]);
});

it('should handle null and undefined values in vars', () => {
const event: SentryEvent = {
exception: {
values: [ {
stacktrace: {
frames: [ {
filename: 'test.js',
lineno: 10,
vars: {
nullValue: null,
undefinedValue: undefined,
normalValue: 'test',
},
} ],
},
} ],
},
};

const backtrace = composeBacktrace(event);

expect(backtrace?.[0].arguments).toEqual([
'nullValue=null',
'undefinedValue=undefined',
'normalValue=test',
]);
});

it('should handle empty objects and arrays in vars', () => {
const event: SentryEvent = {
exception: {
values: [ {
stacktrace: {
frames: [ {
filename: 'test.js',
lineno: 10,
vars: {
emptyObject: {},
emptyArray: [],
normalValue: 'test',
},
} ],
},
} ],
},
};

const backtrace = composeBacktrace(event);

expect(backtrace?.[0].arguments).toEqual([
'emptyObject={}',
'emptyArray=[]',
'normalValue=test',
]);
});

it('should reverse frames', () => {
const event: SentryEvent = {
exception: {
Expand Down
2 changes: 1 addition & 1 deletion workers/sentry/tests/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -689,7 +689,7 @@ describe('SentryEventWorker', () => {
'__package__=None',
'__loader__=<_frozen_importlib_external.SourceFileLoader object at 0x102934cb0>',
'__spec__=None',
'__annotations__=[object Object]',
'__annotations__={}',
"__builtins__=<module 'builtins' (built-in)>",
"__file__='/Users/nostr/dev/codex/hawk.mono/tests/manual/sentry/sentry-prod.py'",
'__cached__=None',
Expand Down
Loading