Skip to content

Commit c0cd530

Browse files
authored
Allow silencing unhandled rejection filter warnings and improve debugging (#84572)
The original debug logs were added to help validate the filter worked correctly in real applications. They leak implementation details which is more permissive than ideal. One reason to keep these around however is if you end up seeing the filter being removed you might want to understand what code is actually doing the removal. The debug mode now allows you to receive stacks so you can pinpoint what is manipulating the listeners. Additionally if you are ok with whatever is leading to these warnings you might want to quiet them. This introduces a silent mode Additionally this removes any warnings except for the one about uninstalling the filter because doing so is so likely to be a bad idea that we must highlight it if it is happening.
1 parent 844f0a6 commit c0cd530

File tree

4 files changed

+305
-104
lines changed

4 files changed

+305
-104
lines changed

packages/next/src/server/node-environment-extensions/unhandled-rejection.test.ts

Lines changed: 128 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ type WorkerResult = {
6161
stderr: string
6262
uhr: Array<UHRReport>
6363
count: Array<CountReport>
64-
errorLog: Array<ErrorLogReport>
64+
errorLog: Array<string>
6565
data: Record<string, unknown>
6666
messages: Array<ReportableResult>
6767
}
@@ -104,7 +104,7 @@ export function runWorkerCode(fn: Function): Promise<WorkerResult> {
104104
const messages: Array<ReportableResult> = []
105105
const uhr: Array<UHRReport> = []
106106
const count: Array<CountReport> = []
107-
const errorLog: Array<ErrorLogReport> = []
107+
const errorLog: Array<string> = []
108108
const data = {} as Record<string, unknown>
109109
let stderr = ''
110110

@@ -166,7 +166,7 @@ describe('unhandled-rejection filter', () => {
166166

167167
it('should not install filter when disabled', async () => {
168168
async function testForWorker() {
169-
process.env.NEXT_USE_UNHANDLED_REJECTION_FILTER = 'disabled'
169+
process.env.NEXT_UNHANDLED_REJECTION_FILTER = 'disabled'
170170
require('next/dist/server/node-environment-extensions/unhandled-rejection')
171171

172172
reportResult({
@@ -185,7 +185,7 @@ describe('unhandled-rejection filter', () => {
185185

186186
it('should install filter rejections when environment variable is enabled', async () => {
187187
async function testForWorker() {
188-
process.env.NEXT_USE_UNHANDLED_REJECTION_FILTER = 'enabled'
188+
process.env.NEXT_UNHANDLED_REJECTION_FILTER = 'enabled'
189189
require('next/dist/server/node-environment-extensions/unhandled-rejection')
190190

191191
reportResult({
@@ -204,7 +204,7 @@ describe('unhandled-rejection filter', () => {
204204

205205
it('should install filter rejections when environment variable is enabled in debug mode', async () => {
206206
async function testForWorker() {
207-
process.env.NEXT_USE_UNHANDLED_REJECTION_FILTER = 'debug'
207+
process.env.NEXT_UNHANDLED_REJECTION_FILTER = 'debug'
208208
require('next/dist/server/node-environment-extensions/unhandled-rejection')
209209

210210
reportResult({
@@ -220,12 +220,121 @@ describe('unhandled-rejection filter', () => {
220220
expect.arrayContaining([expect.objectContaining({ count: 1 })])
221221
)
222222
})
223+
224+
it('should warn once when you uninstall the filter with removeListener', async () => {
225+
async function testForWorker() {
226+
const originalWarn = console.warn
227+
console.warn = (...args: Array<any>) => {
228+
reportResult({ type: 'error-log', message: args.join(' ') })
229+
originalWarn(...args)
230+
}
231+
232+
require('next/dist/server/node-environment-extensions/unhandled-rejection')
233+
234+
const filterListener = process.listeners('unhandledRejection')[0]
235+
process.removeListener('unhandledRejection', filterListener)
236+
process.removeListener('unhandledRejection', filterListener)
237+
process.removeAllListeners()
238+
}
239+
240+
const { errorLog, exitCode } = await runWorkerCode(testForWorker)
241+
expect(exitCode).toBe(0)
242+
expect(errorLog).toMatchInlineSnapshot(`
243+
[
244+
"[Next.js Unhandled Rejection Filter]: Uninstalling filter because \`process.removeListener('unhandledRejection', listener)\` was called with the filter listener. Uninstalling this filter is not recommended and will cause you to observe 'unhandledRejection' events related to intentionally aborted prerenders.
245+
246+
You can silence warnings related to this behavior by running Next.js with \`NEXT_UNHANDLED_REJECTION_FILTER=silent\` environment variable.
247+
248+
You can debug event listener operations by running Next.js with \`NEXT_UNHANDLED_REJECTION_FILTER=debug\` environment variable.",
249+
]
250+
`)
251+
})
252+
253+
it('should warn once when you uninstall the filter with off', async () => {
254+
async function testForWorker() {
255+
const originalWarn = console.warn
256+
console.warn = (...args: Array<any>) => {
257+
reportResult({ type: 'error-log', message: args.join(' ') })
258+
originalWarn(...args)
259+
}
260+
261+
require('next/dist/server/node-environment-extensions/unhandled-rejection')
262+
263+
const filterListener = process.listeners('unhandledRejection')[0]
264+
process.off('unhandledRejection', filterListener)
265+
process.off('unhandledRejection', filterListener)
266+
process.removeAllListeners()
267+
}
268+
269+
const { errorLog, exitCode } = await runWorkerCode(testForWorker)
270+
expect(exitCode).toBe(0)
271+
expect(errorLog).toMatchInlineSnapshot(`
272+
[
273+
"[Next.js Unhandled Rejection Filter]: Uninstalling filter because \`process.removeListener('unhandledRejection', listener)\` was called with the filter listener. Uninstalling this filter is not recommended and will cause you to observe 'unhandledRejection' events related to intentionally aborted prerenders.
274+
275+
You can silence warnings related to this behavior by running Next.js with \`NEXT_UNHANDLED_REJECTION_FILTER=silent\` environment variable.
276+
277+
You can debug event listener operations by running Next.js with \`NEXT_UNHANDLED_REJECTION_FILTER=debug\` environment variable.",
278+
]
279+
`)
280+
})
281+
282+
it('should warn once when you uninstall the filter with removeAllListeners', async () => {
283+
async function testForWorker() {
284+
const originalWarn = console.warn
285+
console.warn = (...args: Array<any>) => {
286+
reportResult({ type: 'error-log', message: args.join(' ') })
287+
originalWarn(...args)
288+
}
289+
290+
require('next/dist/server/node-environment-extensions/unhandled-rejection')
291+
292+
const filterListener = process.listeners('unhandledRejection')[0]
293+
process.removeAllListeners()
294+
process.off('unhandledRejection', filterListener)
295+
process.removeListener('unhandledRejection', filterListener)
296+
}
297+
298+
const { errorLog, exitCode } = await runWorkerCode(testForWorker)
299+
expect(exitCode).toBe(0)
300+
expect(errorLog).toMatchInlineSnapshot(`
301+
[
302+
"[Next.js Unhandled Rejection Filter]: Uninstalling filter because \`process.removeAllListeners()\` was called. Uninstalling this filter is not recommended and will cause you to observe 'unhandledRejection' events related to intentionally aborted prerenders.
303+
304+
You can silence warnings related to this behavior by running Next.js with \`NEXT_UNHANDLED_REJECTION_FILTER=silent\` environment variable.
305+
306+
You can debug event listener operations by running Next.js with \`NEXT_UNHANDLED_REJECTION_FILTER=debug\` environment variable.",
307+
]
308+
`)
309+
})
310+
311+
it('does not warn when environment variable is set to silent mode', async () => {
312+
async function testForWorker() {
313+
process.env.NEXT_UNHANDLED_REJECTION_FILTER = 'silent'
314+
const originalWarn = console.warn
315+
console.warn = (...args: Array<any>) => {
316+
reportResult({ type: 'error-log', message: args.join(' ') })
317+
originalWarn(...args)
318+
}
319+
320+
require('next/dist/server/node-environment-extensions/unhandled-rejection')
321+
322+
const filterListener = process.listeners('unhandledRejection')[0]
323+
process.removeAllListeners()
324+
process.off('unhandledRejection', filterListener)
325+
process.removeListener('unhandledRejection', filterListener)
326+
}
327+
328+
const { errorLog, exitCode } = await runWorkerCode(testForWorker)
329+
expect(exitCode).toBe(0)
330+
expect(errorLog).toMatchInlineSnapshot(`[]`)
331+
})
223332
})
224333

225334
describe('filtering functionality', () => {
226335
it('should suppress rejections from aborted prerender contexts', async () => {
227336
async function testForWorker() {
228-
process.env.NEXT_USE_UNHANDLED_REJECTION_FILTER = '1'
337+
process.env.NEXT_UNHANDLED_REJECTION_FILTER = '1'
229338
require('next/dist/server/node-environment-extensions/unhandled-rejection')
230339

231340
const {
@@ -293,7 +402,7 @@ describe('unhandled-rejection filter', () => {
293402

294403
it('should suppress rejections from aborted prerender-client contexts', async () => {
295404
async function testForWorker() {
296-
process.env.NEXT_USE_UNHANDLED_REJECTION_FILTER = '1'
405+
process.env.NEXT_UNHANDLED_REJECTION_FILTER = '1'
297406
require('next/dist/server/node-environment-extensions/unhandled-rejection')
298407

299408
const {
@@ -361,7 +470,7 @@ describe('unhandled-rejection filter', () => {
361470

362471
it('should suppress rejections from aborted prerender-runtime contexts', async () => {
363472
async function testForWorker() {
364-
process.env.NEXT_USE_UNHANDLED_REJECTION_FILTER = '1'
473+
process.env.NEXT_UNHANDLED_REJECTION_FILTER = '1'
365474
require('next/dist/server/node-environment-extensions/unhandled-rejection')
366475

367476
const {
@@ -429,7 +538,7 @@ describe('unhandled-rejection filter', () => {
429538

430539
it('should pass through rejections from non-aborted prerender contexts', async () => {
431540
async function testForWorker() {
432-
process.env.NEXT_USE_UNHANDLED_REJECTION_FILTER = '1'
541+
process.env.NEXT_UNHANDLED_REJECTION_FILTER = '1'
433542
require('next/dist/server/node-environment-extensions/unhandled-rejection')
434543

435544
const {
@@ -472,7 +581,7 @@ describe('unhandled-rejection filter', () => {
472581

473582
it('should call console.error when no handlers are present', async () => {
474583
async function testForWorker() {
475-
process.env.NEXT_USE_UNHANDLED_REJECTION_FILTER = '1'
584+
process.env.NEXT_UNHANDLED_REJECTION_FILTER = '1'
476585
require('next/dist/server/node-environment-extensions/unhandled-rejection')
477586

478587
console.error = (...args: Array<any>) => {
@@ -493,7 +602,7 @@ describe('unhandled-rejection filter', () => {
493602
describe('process method interception', () => {
494603
it('should handle process.once listeners correctly', async () => {
495604
async function testForWorker() {
496-
process.env.NEXT_USE_UNHANDLED_REJECTION_FILTER = 'enabled'
605+
process.env.NEXT_UNHANDLED_REJECTION_FILTER = 'enabled'
497606
require('next/dist/server/node-environment-extensions/unhandled-rejection')
498607

499608
let callCount = 0
@@ -528,7 +637,7 @@ describe('unhandled-rejection filter', () => {
528637

529638
it('should handle process.removeListener correctly', async () => {
530639
async function testForWorker() {
531-
process.env.NEXT_USE_UNHANDLED_REJECTION_FILTER = 'enabled'
640+
process.env.NEXT_UNHANDLED_REJECTION_FILTER = 'enabled'
532641
require('next/dist/server/node-environment-extensions/unhandled-rejection')
533642

534643
const handler1 = (reason: unknown) => {
@@ -598,7 +707,7 @@ describe('unhandled-rejection filter', () => {
598707

599708
it('should uninstall filter when removeAllListeners() is called without arguments', async () => {
600709
async function testForWorker() {
601-
process.env.NEXT_USE_UNHANDLED_REJECTION_FILTER = 'enabled'
710+
process.env.NEXT_UNHANDLED_REJECTION_FILTER = 'enabled'
602711
require('next/dist/server/node-environment-extensions/unhandled-rejection')
603712

604713
const {
@@ -660,7 +769,7 @@ describe('unhandled-rejection filter', () => {
660769

661770
it('should not uninstall filter when removeAllListeners("unhandledRejection") is called', async () => {
662771
async function testForWorker() {
663-
process.env.NEXT_USE_UNHANDLED_REJECTION_FILTER = 'enabled'
772+
process.env.NEXT_UNHANDLED_REJECTION_FILTER = 'enabled'
664773
require('next/dist/server/node-environment-extensions/unhandled-rejection')
665774

666775
const {
@@ -719,7 +828,7 @@ describe('unhandled-rejection filter', () => {
719828
// an event is emitted will be invoked regardless of whether there are mutations to the listeners
720829
// during event handling.
721830
async function testForWorker() {
722-
process.env.NEXT_USE_UNHANDLED_REJECTION_FILTER = 'enabled'
831+
process.env.NEXT_UNHANDLED_REJECTION_FILTER = 'enabled'
723832
require('next/dist/server/node-environment-extensions/unhandled-rejection')
724833

725834
const onceHandler = (reason: unknown) => {
@@ -825,7 +934,7 @@ describe('unhandled-rejection filter', () => {
825934
const originalToStrings = originalMethods.map((m) => m.toString())
826935
const originalNames = originalMethods.map((m) => m.name)
827936

828-
process.env.NEXT_USE_UNHANDLED_REJECTION_FILTER = 'enabled'
937+
process.env.NEXT_UNHANDLED_REJECTION_FILTER = 'enabled'
829938
require('next/dist/server/node-environment-extensions/unhandled-rejection')
830939

831940
const patchedMethods = [
@@ -885,7 +994,7 @@ describe('unhandled-rejection filter', () => {
885994
describe('error handling in handlers', () => {
886995
it('should handle errors thrown by user handlers gracefully', async () => {
887996
async function testForWorker() {
888-
process.env.NEXT_USE_UNHANDLED_REJECTION_FILTER = 'enabled'
997+
process.env.NEXT_UNHANDLED_REJECTION_FILTER = 'enabled'
889998
require('next/dist/server/node-environment-extensions/unhandled-rejection')
890999

8911000
const {
@@ -929,7 +1038,7 @@ describe('unhandled-rejection filter', () => {
9291038
reportResult({ type: 'uhr', reason: `existing: ${String(reason)}` })
9301039
})
9311040

932-
process.env.NEXT_USE_UNHANDLED_REJECTION_FILTER = 'enabled'
1041+
process.env.NEXT_UNHANDLED_REJECTION_FILTER = 'enabled'
9331042
require('next/dist/server/node-environment-extensions/unhandled-rejection')
9341043

9351044
const {
@@ -971,7 +1080,7 @@ describe('unhandled-rejection filter', () => {
9711080
reportResult({ type: 'uhr', reason: `existing: ${String(reason)}` })
9721081
})
9731082

974-
process.env.NEXT_USE_UNHANDLED_REJECTION_FILTER = 'enabled'
1083+
process.env.NEXT_UNHANDLED_REJECTION_FILTER = 'enabled'
9751084
require('next/dist/server/node-environment-extensions/unhandled-rejection')
9761085

9771086
process.removeAllListeners('unhandledRejection')

0 commit comments

Comments
 (0)