Skip to content

Commit d9cf8af

Browse files
Sync public snapshot from freebuff-private
Source: CodebuffAI/freebuff-private@1f0708185b62148df446e0bd5c5f67905afeac2b
1 parent 1e49cb7 commit d9cf8af

3 files changed

Lines changed: 77 additions & 5 deletions

File tree

‎bun.lock‎

Lines changed: 1 addition & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎common/src/util/__tests__/log-data.test.ts‎

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,46 @@ describe('serializeLogData', () => {
2626
expect(row.error.cause.code).toBe('57014')
2727
})
2828

29+
// The 2026-10-02 ad-revenue rollup failure: a bulk insert's DrizzleQueryError
30+
// was 522 KB of SQL and params, the row was cut at the data cap, and the
31+
// Postgres reason on `cause` never reached Axiom.
32+
test('a huge failed query keeps its cause and drops the payload', () => {
33+
const cause = Object.assign(
34+
new Error('insert or update violates foreign key constraint'),
35+
{ code: '23503', constraint_name: 'ad_revenue_user_daily_user_id_fk' },
36+
)
37+
const placeholders = Array.from({ length: 25_000 }, (_, i) => `$${i + 1}`)
38+
const query = `insert into "ad_revenue_user_daily" values (${placeholders.join(', ')})`
39+
const params = Array.from({ length: 25_000 }, (_, i) => `user-${i}`)
40+
const error = Object.assign(
41+
new Error(`Failed query: ${query}\nparams: ${params.join(',')}`, {
42+
cause,
43+
}),
44+
{ query, params },
45+
)
46+
const serialized = serializeLogData({ error })!
47+
expect(serialized.length).toBeLessThan(10_000)
48+
const row = JSON.parse(serialized)
49+
expect(row._truncated).toBeUndefined()
50+
expect(row.error.cause.code).toBe('23503')
51+
expect(row.error.cause.constraint_name).toBe(
52+
'ad_revenue_user_daily_user_id_fk',
53+
)
54+
expect(
55+
row.error.query.startsWith('insert into "ad_revenue_user_daily"'),
56+
).toBe(true)
57+
expect(row.error.params).toBe('[25000 params omitted]')
58+
expect(row.error.message.length).toBeLessThan(2_100)
59+
})
60+
61+
test('a short failed query keeps its params', () => {
62+
const error = Object.assign(new Error('Failed query: select $1'), {
63+
query: 'select $1',
64+
params: ['x'],
65+
})
66+
expect(parse({ error }).error.params).toEqual(['x'])
67+
})
68+
2969
test('trims a long stack', () => {
3070
const error = new Error('deep')
3171
error.stack = `Error: deep\n${' at frame (file.ts:1:1)\n'.repeat(500)}`

‎common/src/util/log-data.ts‎

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,23 @@ export const LOG_LEVEL_ORDER: Record<LogLevel, number> = {
2323

2424
/** Stack traces cost ingest on every error row; the top frames carry it. */
2525
const MAX_ERROR_STACK_CHARS = 2_000
26+
/** An error message or SQL statement longer than this is a payload, not a reason. */
27+
const MAX_ERROR_MESSAGE_CHARS = 2_000
28+
const MAX_ERROR_QUERY_CHARS = 500
29+
30+
function safeLength(value: unknown): number {
31+
try {
32+
return JSON.stringify(value)?.length ?? 0
33+
} catch {
34+
return Number.POSITIVE_INFINITY
35+
}
36+
}
37+
38+
function clip(text: string, max: number): string {
39+
return text.length > max
40+
? `${text.slice(0, max)}… [${text.length - max} chars omitted]`
41+
: text
42+
}
2643

2744
/**
2845
* An Error as plain JSON. `name`, `message` and `stack` are non-enumerable, so
@@ -34,10 +51,27 @@ const MAX_ERROR_STACK_CHARS = 2_000
3451
* same replacer.
3552
*/
3653
function errorToJson(error: Error): Record<string, unknown> {
54+
const fields: Record<string, unknown> = { ...error }
55+
// A drizzle `DrizzleQueryError` carries the whole statement as `query`, every
56+
// bound value as `params`, and both again in `message`. For a bulk insert
57+
// that is hundreds of KB: the ad-revenue rollup's 2026-10-02 failure logged
58+
// 522 KB, the row was cut at MAX_LOG_DATA_BYTES, and the `cause` holding the
59+
// Postgres reason came after the cut, so it never reached Axiom. Keep the
60+
// head of the statement, replace a long param list with its count, and keep
61+
// the cause.
62+
if (typeof fields.query === 'string') {
63+
fields.query = clip(fields.query, MAX_ERROR_QUERY_CHARS)
64+
}
65+
if (
66+
Array.isArray(fields.params) &&
67+
safeLength(fields.params) > MAX_ERROR_QUERY_CHARS
68+
) {
69+
fields.params = `[${fields.params.length} params omitted]`
70+
}
3771
return {
38-
...error,
72+
...fields,
3973
name: error.name,
40-
message: error.message,
74+
message: clip(error.message, MAX_ERROR_MESSAGE_CHARS),
4175
...(error.stack
4276
? { stack: error.stack.slice(0, MAX_ERROR_STACK_CHARS) }
4377
: {}),

0 commit comments

Comments
 (0)