fix(monitoring): cache payload instance in logger to prevent flaky tests

The fire-and-forget dynamic import chain (3 awaits) was racing with
test flush timeouts. Caching the resolved payload instance fixes both
the flakiness and eliminates per-call import overhead.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Martin Porwoll 2026-02-15 00:52:17 +00:00
parent dd73162035
commit 151b96d641
2 changed files with 21 additions and 5 deletions

View file

@ -39,6 +39,22 @@ export interface MonitoringLoggerInstance {
fatal(message: string, context?: LogContext): void
}
/** Cached Payload instance — resolved once, reused for all subsequent writes. */
let cachedPayload: { create: (...args: unknown[]) => Promise<unknown> } | null = null
async function getPayloadInstance() {
if (cachedPayload) return cachedPayload
const { getPayload } = await import('payload')
const config = (await import(/* @vite-ignore */ '@payload-config')).default
cachedPayload = await getPayload({ config })
return cachedPayload
}
/** Reset cached instance (used in tests). */
export function _resetPayloadCache(): void {
cachedPayload = null
}
async function writeLog(
source: LogSource,
level: LogLevel,
@ -48,9 +64,7 @@ async function writeLog(
if (!shouldLog(level)) return
try {
const { getPayload } = await import('payload')
const config = (await import('@payload-config')).default
const payload = await getPayload({ config })
const payload = await getPayloadInstance()
const { requestId, userId, tenant, duration, ...rest } = context || {}
@ -69,6 +83,7 @@ async function writeLog(
})
} catch {
// Fallback to console if Payload is not yet initialized
cachedPayload = null
const prefix = `[${source}][${level.toUpperCase()}]`
console.log(prefix, message, context || '')
}

View file

@ -11,16 +11,17 @@ vi.mock('@payload-config', () => ({
default: {},
}))
import { createMonitoringLogger } from '@/lib/monitoring/monitoring-logger'
import { createMonitoringLogger, _resetPayloadCache } from '@/lib/monitoring/monitoring-logger'
/** Wait long enough for fire-and-forget promises to settle. */
async function flush(): Promise<void> {
await new Promise((resolve) => setTimeout(resolve, 50))
await new Promise((resolve) => setTimeout(resolve, 150))
}
describe('MonitoringLogger', () => {
beforeEach(() => {
vi.clearAllMocks()
_resetPayloadCache()
mockGetPayload.mockResolvedValue({ create: mockCreate })
mockCreate.mockResolvedValue({ id: '1' })
delete process.env.MONITORING_LOG_LEVEL