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
41 changes: 18 additions & 23 deletions packages/server/utils/src/ap-logger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,14 @@ function buildLogger(bindings: Record<string, unknown>): ApLogger {
},
info(...args: unknown[]) {
try {
const { message, fields } = normalizePinoArgs(args)
const { message, fields, err } = normalizePinoArgsWithError(args)
const safeFields = err ? { error: stringifyError(err), ...fields } : fields
const wide = wideEvent.current()
if (wide) {
wide.info(message ?? 'log', { ...bindings, ...fields })
wide.info(message ?? 'log', { ...bindings, ...safeFields })
}
else {
log.info({ msg: message, ...bindings, ...fields })
log.info({ msg: message, ...bindings, ...safeFields })
}
}
catch {
Expand All @@ -44,13 +45,14 @@ function buildLogger(bindings: Record<string, unknown>): ApLogger {
},
warn(...args: unknown[]) {
try {
const { message, fields } = normalizePinoArgs(args)
const { message, fields, err } = normalizePinoArgsWithError(args)
const safeFields = err ? { error: stringifyError(err), ...fields } : fields
const wide = wideEvent.current()
if (wide) {
wide.warn(message ?? 'log', { ...bindings, ...fields })
wide.warn(message ?? 'log', { ...bindings, ...safeFields })
}
else {
log.warn({ msg: message, ...bindings, ...fields })
log.warn({ msg: message, ...bindings, ...safeFields })
}
}
catch {
Expand All @@ -68,7 +70,7 @@ function buildLogger(bindings: Record<string, unknown>): ApLogger {
}
else {
if (err) {
log.error({ msg: message ?? err.message, error: `${err.message}\n${err.stack ?? ''}`, ...bindings, ...fields })
log.error({ msg: message ?? err.message, error: stringifyError(err), ...bindings, ...fields })
}
else {
log.error({ msg: message, ...bindings, ...fields })
Expand All @@ -88,7 +90,7 @@ function buildLogger(bindings: Record<string, unknown>): ApLogger {
}
else {
if (err) {
log.error({ msg: message ?? err.message, error: `${err.message}\n${err.stack ?? ''}`, ...bindings, ...fields })
log.error({ msg: message ?? err.message, error: stringifyError(err), ...bindings, ...fields })
}
else {
log.error({ msg: message, ...bindings, ...fields })
Expand All @@ -101,17 +103,19 @@ function buildLogger(bindings: Record<string, unknown>): ApLogger {
},
debug(...args: unknown[]) {
try {
const { message, fields } = normalizePinoArgs(args)
log.debug({ msg: message, ...bindings, ...fields })
const { message, fields, err } = normalizePinoArgsWithError(args)
const safeFields = err ? { error: stringifyError(err), ...fields } : fields
log.debug({ msg: message, ...bindings, ...safeFields })
}
catch {
// never throw
}
},
trace(...args: unknown[]) {
try {
const { message, fields } = normalizePinoArgs(args)
log.debug({ msg: message, ...bindings, ...fields })
const { message, fields, err } = normalizePinoArgsWithError(args)
const safeFields = err ? { error: stringifyError(err), ...fields } : fields
log.debug({ msg: message, ...bindings, ...safeFields })
}
catch {
// never throw
Expand All @@ -128,17 +132,8 @@ function isRecord(value: unknown): value is Record<string, unknown> {
return value !== null && typeof value === 'object' && !Array.isArray(value)
}

function normalizePinoArgs(args: unknown[]): { message: string | undefined, fields: Record<string, unknown> } {
const first = args[0]
if (typeof first === 'string') {
return { message: first, fields: {} }
}
if (isRecord(first)) {
const second = args[1]
const message = typeof second === 'string' ? second : undefined
return { message, fields: first }
}
return { message: String(first ?? ''), fields: {} }
function stringifyError(err: Error): string {
return `${err.message}\n${err.stack ?? ''}`
}

function normalizePinoArgsWithError(args: unknown[]): { message: string | undefined, fields: Record<string, unknown>, err: Error | undefined } {
Expand Down
54 changes: 54 additions & 0 deletions packages/server/utils/test/ap-logger.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -204,4 +204,58 @@ describe('apLogger', () => {
apLogger.setCurrentLevel('info') // restore
})
})

describe('Error fields are stringified at every level', () => {
function makeCyclicAxiosLikeError(): Error {
const err = new Error('Request failed with status code 521')
const request: Record<string, unknown> = { host: 'console.activepieces.com' }
const response: Record<string, unknown> = { status: 521, request }
request['res'] = response
Object.assign(err, { isAxiosError: true, code: 'ERR_BAD_RESPONSE', request, response })
return err
}

it('warn({ error: Error }) sends a string error field to the wide event, never the raw object', () => {
ambientState.active = true
const logger = apLogger.create({})
logger.warn({ error: makeCyclicAxiosLikeError(), platform: { id: 'p1' } }, 'enrollment failed')
expect(spies.wideWarnSpy).toHaveBeenCalledOnce()
const [msg, ctx] = spies.wideWarnSpy.mock.calls[0]
expect(msg).toBe('enrollment failed')
expect(typeof ctx.error).toBe('string')
expect(ctx.error).toContain('Request failed with status code 521')
expect(ctx.platform).toEqual({ id: 'p1' })
})

it('warn({ err: Error }) removes err and emits the canonical string error field', () => {
ambientState.active = true
const logger = apLogger.create({})
logger.warn({ err: makeCyclicAxiosLikeError() }, 'failed')
const [, ctx] = spies.wideWarnSpy.mock.calls[0]
expect(typeof ctx.error).toBe('string')
expect(ctx.err).toBeUndefined()
})

it('info({ error: Error }) sends a string error field to the wide event', () => {
ambientState.active = true
const logger = apLogger.create({})
logger.info({ error: makeCyclicAxiosLikeError() }, 'soft failure')
const [, ctx] = spies.wideInfoSpy.mock.calls[0]
expect(typeof ctx.error).toBe('string')
})

it('warn({ error: Error }) without ambient wide event logs a string error field', () => {
const logger = apLogger.create({})
logger.warn({ error: makeCyclicAxiosLikeError() }, 'failed')
const arg = spies.logWarnSpy.mock.calls[0][0]
expect(typeof arg.error).toBe('string')
})

it('debug({ error: Error }) logs a string error field', () => {
const logger = apLogger.create({})
logger.debug({ error: makeCyclicAxiosLikeError() }, 'debugging')
const arg = spies.logDebugSpy.mock.calls[0][0]
expect(typeof arg.error).toBe('string')
})
})
})
Loading