Skip to content

Commit e7c954b

Browse files
authored
fix(nitro): skip default logging for expected evlog 4xx errors (#819)
Co-authored-by: evlogai[bot] <evlogai[bot]@users.noreply.github.com>
1 parent 7fb04a2 commit e7c954b

6 files changed

Lines changed: 74 additions & 2 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'evlog': patch
3+
---
4+
5+
Stop Nitro and Nuxt from printing expected `createError` 4xx responses as `[request error] [unhandled]` in production. The wide event already records these errors, so evlog's Nitro error handler now skips Nitro's default logging for `EvlogError` with a status below 500. Server errors and errors that are not from evlog are still logged by Nitro.

‎packages/evlog/src/nitro-v3/errorHandler.ts‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
extractErrorStatus,
66
buildPlainNitroErrorBody,
77
isSensitiveNitroError,
8+
isExpectedEvlogError,
89
serializeEvlogErrorResponse,
910
shouldSerializeNitroErrorAsJson,
1011
shouldSuppressNitroDevOverlay,
@@ -48,7 +49,7 @@ export default defineErrorHandler(async (error, event, ctx: NitroErrorHandlerCon
4849

4950
const suppressOverlay = shouldSuppressNitroDevOverlay()
5051

51-
if (!suppressOverlay) {
52+
if (!suppressOverlay && !isExpectedEvlogError(evlogError)) {
5253
await ctx.defaultHandler(error, event, { silent: false })
5354
}
5455

‎packages/evlog/src/nitro.ts‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,15 @@ export function resolveEvlogError(error: Error): Error | null {
115115

116116
export { extractErrorStatus } from './shared/errors'
117117

118+
/**
119+
* Whether an error is an EvlogError with a 4xx status: an expected outcome that
120+
* the wide event already records, so Nitro's default logging is skipped for it.
121+
* @internal
122+
*/
123+
export function isExpectedEvlogError(evlogError: Error | null): boolean {
124+
return evlogError !== null && extractErrorStatus(evlogError) < 500
125+
}
126+
118127
/** Request metadata used to decide JSON vs framework error-page rendering. */
119128
export interface NitroErrorRequestContext {
120129
pathname: string

‎packages/evlog/src/nitro/errorHandler.ts‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
extractErrorStatus,
99
buildPlainNitroErrorBody,
1010
isSensitiveNitroError,
11+
isExpectedEvlogError,
1112
serializeEvlogErrorResponse,
1213
markH3ErrorHandled,
1314
shouldSerializeNitroErrorAsJson,
@@ -55,7 +56,7 @@ export default defineNitroErrorHandler(async (error, event, ctx) => {
5556

5657
// Nitro v2 always passes `ctx`, but a missing context (e.g. the handler
5758
// invoked directly) must degrade to a flushed response, not a crash.
58-
if (!suppressOverlay && ctx?.defaultHandler) {
59+
if (!suppressOverlay && !isExpectedEvlogError(evlogError) && ctx?.defaultHandler) {
5960
await ctx.defaultHandler(error, event, { silent: false })
6061
}
6162

‎packages/evlog/test/nitro-v3/errorHandler.test.ts‎

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,34 @@ describe('nitro-v3 errorHandler', () => {
253253
expect(body.message).toBe('Invalid email format')
254254
expect(body.statusMessage).toBe('Invalid email format')
255255
})
256+
257+
it('skips Nitro default logging for an expected 4xx EvlogError in production', async () => {
258+
vi.stubEnv('NODE_ENV', 'production')
259+
260+
const evlogError = Object.assign(new Error('Payment failed'), {
261+
name: 'EvlogError',
262+
status: 402,
263+
data: { why: 'Card declined', fix: 'Use another card' },
264+
})
265+
266+
const response = await invokeErrorHandler(evlogError)
267+
268+
expect(defaultHandlerMock).not.toHaveBeenCalled()
269+
expect(response?.status).toBe(402)
270+
})
271+
272+
it('keeps Nitro default logging for an EvlogError with a 5xx status in production', async () => {
273+
vi.stubEnv('NODE_ENV', 'production')
274+
275+
const evlogError = Object.assign(new Error('Payment provider unavailable'), {
276+
name: 'EvlogError',
277+
status: 503,
278+
})
279+
280+
await invokeErrorHandler(evlogError)
281+
282+
expect(defaultHandlerMock).toHaveBeenCalledWith(evlogError, mockEvent, { silent: false })
283+
})
256284
})
257285

258286
describe('HTML page delegation (#390)', () => {

‎packages/evlog/test/nitro/errorHandler.test.ts‎

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,34 @@ describe('errorHandler', () => {
306306
expect(sentBody.message).toBe('Invalid email format')
307307
expect(sentBody.statusMessage).toBe('Invalid email format')
308308
})
309+
310+
it('skips Nitro default logging for an expected 4xx EvlogError in production', async () => {
311+
vi.stubEnv('NODE_ENV', 'production')
312+
313+
const evlogError = Object.assign(new Error('Payment failed'), {
314+
name: 'EvlogError',
315+
status: 402,
316+
data: { why: 'Card declined', fix: 'Use another card' },
317+
})
318+
319+
await invokeErrorHandler(evlogError)
320+
321+
expect(defaultHandlerMock).not.toHaveBeenCalled()
322+
expect(readResponseBody().statusCode).toBe(402)
323+
})
324+
325+
it('keeps Nitro default logging for an EvlogError with a 5xx status in production', async () => {
326+
vi.stubEnv('NODE_ENV', 'production')
327+
328+
const evlogError = Object.assign(new Error('Payment provider unavailable'), {
329+
name: 'EvlogError',
330+
status: 503,
331+
})
332+
333+
await invokeErrorHandler(evlogError)
334+
335+
expect(defaultHandlerMock).toHaveBeenCalledWith(evlogError, mockEvent, { silent: false })
336+
})
309337
})
310338

311339
describe('HTML page delegation (#390)', () => {

0 commit comments

Comments
 (0)