From 2e135346cf0847efd85329fb77e86f9e21ad417a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hannes=20Born=C3=B6?= Date: Wed, 11 May 2022 19:02:15 +0200 Subject: [PATCH] Don't convert error to string (#36804) Stack trace disappears when error is converted to string. I changed the types in `log.ts` to match `console.log`/`console.error`/`console.warn`. fixes #31591 --- packages/next/build/output/log.ts | 14 ++++---- packages/next/server/dev/next-dev-server.ts | 8 ++--- test/integration/custom-server/server.js | 5 +++ .../custom-server/test/index.test.js | 22 +++++++++++++ .../pages/uncaught-empty-exception.js | 12 +++++++ .../pages/uncaught-empty-rejection.js | 12 +++++++ .../server-side-dev-errors/test/index.test.js | 33 +++++++++++++++++++ 7 files changed, 95 insertions(+), 11 deletions(-) create mode 100644 test/integration/server-side-dev-errors/pages/uncaught-empty-exception.js create mode 100644 test/integration/server-side-dev-errors/pages/uncaught-empty-rejection.js diff --git a/packages/next/build/output/log.ts b/packages/next/build/output/log.ts index 9220fe1834e36..bbf520da350d7 100644 --- a/packages/next/build/output/log.ts +++ b/packages/next/build/output/log.ts @@ -10,30 +10,30 @@ export const prefixes = { trace: chalk.magenta('trace') + ' -', } -export function wait(...message: string[]) { +export function wait(...message: any[]) { console.log(prefixes.wait, ...message) } -export function error(...message: string[]) { +export function error(...message: any[]) { console.error(prefixes.error, ...message) } -export function warn(...message: string[]) { +export function warn(...message: any[]) { console.warn(prefixes.warn, ...message) } -export function ready(...message: string[]) { +export function ready(...message: any[]) { console.log(prefixes.ready, ...message) } -export function info(...message: string[]) { +export function info(...message: any[]) { console.log(prefixes.info, ...message) } -export function event(...message: string[]) { +export function event(...message: any[]) { console.log(prefixes.event, ...message) } -export function trace(...message: string[]) { +export function trace(...message: any[]) { console.log(prefixes.trace, ...message) } diff --git a/packages/next/server/dev/next-dev-server.ts b/packages/next/server/dev/next-dev-server.ts index 7a5c28597ddd6..91222ebabe7d5 100644 --- a/packages/next/server/dev/next-dev-server.ts +++ b/packages/next/server/dev/next-dev-server.ts @@ -693,7 +693,7 @@ export default class DevServer extends Server { ) { let usedOriginalStack = false - if (isError(err) && err.name && err.stack && err.message) { + if (isError(err) && err.stack) { try { const frames = parseStack(err.stack!) const frame = frames[0] @@ -751,11 +751,11 @@ export default class DevServer extends Server { if (!usedOriginalStack) { if (type === 'warning') { - Log.warn(err + '') + Log.warn(err) } else if (type) { - Log.error(`${type}:`, err + '') + Log.error(`${type}:`, err) } else { - Log.error(err + '') + Log.error(err) } } } diff --git a/test/integration/custom-server/server.js b/test/integration/custom-server/server.js index 29550fb1906f4..94d2442356659 100644 --- a/test/integration/custom-server/server.js +++ b/test/integration/custom-server/server.js @@ -18,6 +18,11 @@ app.prepare().then(() => { return app.render(req, res, '/no-query') } + if (req.url === '/unhandled-rejection') { + Promise.reject(new Error('unhandled rejection')) + return res.end('ok') + } + if (/setAssetPrefix/.test(req.url)) { app.setAssetPrefix(`http://127.0.0.1:${port}`) } else if (/setEmptyAssetPrefix/.test(req.url)) { diff --git a/test/integration/custom-server/test/index.test.js b/test/integration/custom-server/test/index.test.js index 8d4e2ee70311e..c30ce36049d6f 100644 --- a/test/integration/custom-server/test/index.test.js +++ b/test/integration/custom-server/test/index.test.js @@ -216,4 +216,26 @@ describe('Custom Server', () => { expect(data).toMatch(/hello world/) }) }) + + describe('unhandled rejection', () => { + afterEach(() => killApp(server)) + + it('stderr should include error message and stack trace', async () => { + let stderr = '' + await startServer( + {}, + { + onStderr(msg) { + stderr += msg || '' + }, + } + ) + await fetchViaHTTP(appPort, '/unhandled-rejection') + await check(() => stderr, /unhandledRejection/) + expect(stderr).toContain( + 'error - unhandledRejection: Error: unhandled rejection' + ) + expect(stderr).toContain('server.js:22:22') + }) + }) }) diff --git a/test/integration/server-side-dev-errors/pages/uncaught-empty-exception.js b/test/integration/server-side-dev-errors/pages/uncaught-empty-exception.js new file mode 100644 index 0000000000000..8674cae72bdaa --- /dev/null +++ b/test/integration/server-side-dev-errors/pages/uncaught-empty-exception.js @@ -0,0 +1,12 @@ +export default function Page() { + return

getServerSideProps page

+} + +export async function getServerSideProps() { + setTimeout(() => { + throw new Error() + }, 10) + return { + props: {}, + } +} diff --git a/test/integration/server-side-dev-errors/pages/uncaught-empty-rejection.js b/test/integration/server-side-dev-errors/pages/uncaught-empty-rejection.js new file mode 100644 index 0000000000000..ca00826da1e7d --- /dev/null +++ b/test/integration/server-side-dev-errors/pages/uncaught-empty-rejection.js @@ -0,0 +1,12 @@ +export default function Page() { + return

getServerSideProps page

+} + +export async function getServerSideProps() { + setTimeout(() => { + Promise.reject(new Error()) + }, 10) + return { + props: {}, + } +} diff --git a/test/integration/server-side-dev-errors/test/index.test.js b/test/integration/server-side-dev-errors/test/index.test.js index f2570ffcd184a..272ac54af4230 100644 --- a/test/integration/server-side-dev-errors/test/index.test.js +++ b/test/integration/server-side-dev-errors/test/index.test.js @@ -11,6 +11,7 @@ import { hasRedbox, getRedboxSource, } from 'next-test-utils' +import stripAnsi from 'strip-ansi' const appDir = join(__dirname, '../') const gspPage = join(appDir, 'pages/gsp.js') @@ -213,6 +214,22 @@ describe('server-side dev errors', () => { }, 'success') }) + it('should show server-side error for uncaught empty rejection correctly', async () => { + const stderrIdx = stderr.length + await webdriver(appPort, '/uncaught-empty-rejection') + + await check(async () => { + const cleanStderr = stripAnsi(stderr.slice(stderrIdx)) + + return cleanStderr.includes('pages/uncaught-empty-rejection.js') && + cleanStderr.includes('7:19') && + cleanStderr.includes('getServerSideProps') && + cleanStderr.includes('new Error()') + ? 'success' + : cleanStderr + }, 'success') + }) + it('should show server-side error for uncaught exception correctly', async () => { const stderrIdx = stderr.length await webdriver(appPort, '/uncaught-exception') @@ -228,4 +245,20 @@ describe('server-side dev errors', () => { : err }, 'success') }) + + it('should show server-side error for uncaught empty exception correctly', async () => { + const stderrIdx = stderr.length + await webdriver(appPort, '/uncaught-empty-exception') + + await check(async () => { + const cleanStderr = stripAnsi(stderr.slice(stderrIdx)) + + return cleanStderr.includes('pages/uncaught-empty-exception.js') && + cleanStderr.includes('7:10') && + cleanStderr.includes('getServerSideProps') && + cleanStderr.includes('new Error()') + ? 'success' + : cleanStderr + }, 'success') + }) })