From 1843ffa32856300ce1781e7e0dbd56b0d2305bcd Mon Sep 17 00:00:00 2001 From: Daniel Roe Date: Thu, 18 Jul 2024 15:59:50 +0100 Subject: [PATCH] refactor(nuxt): use `errx` to handle dev log traces (#28027) --- packages/nuxt/package.json | 1 + .../nuxt/src/app/plugins/dev-server-logs.ts | 22 +++++++--- .../src/core/runtime/nitro/dev-server-logs.ts | 42 +++++++++---------- pnpm-lock.yaml | 12 +++++- 4 files changed, 47 insertions(+), 30 deletions(-) diff --git a/packages/nuxt/package.json b/packages/nuxt/package.json index 68bd6ce779..0178f65047 100644 --- a/packages/nuxt/package.json +++ b/packages/nuxt/package.json @@ -78,6 +78,7 @@ "defu": "^6.1.4", "destr": "^2.0.3", "devalue": "^5.0.0", + "errx": "^0.1.0", "esbuild": "^0.23.0", "escape-string-regexp": "^5.0.0", "estree-walker": "^3.0.3", diff --git a/packages/nuxt/src/app/plugins/dev-server-logs.ts b/packages/nuxt/src/app/plugins/dev-server-logs.ts index eeae8d7562..5da79ba16f 100644 --- a/packages/nuxt/src/app/plugins/dev-server-logs.ts +++ b/packages/nuxt/src/app/plugins/dev-server-logs.ts @@ -1,6 +1,7 @@ import { createConsola } from 'consola' import type { LogObject } from 'consola' import { parse } from 'devalue' +import type { ParsedTrace } from 'errx' import { h } from 'vue' import { defineNuxtPlugin } from '../nuxt' @@ -45,15 +46,24 @@ export default defineNuxtPlugin(async (nuxtApp) => { } }) -function normalizeFilenames (stack?: string) { - stack = stack?.split('\n')[0] || '' - stack = stack.replace(`${devRootDir}/`, '') - stack = stack.replace(/:\d+:\d+\)?$/, '') - return stack +function normalizeFilenames (stack?: ParsedTrace[]) { + if (!stack) { + return '' + } + let message = '' + for (const item of stack) { + const source = item.source.replace(`${devRootDir}/`, '') + if (item.function) { + message += ` at ${item.function} (${source})\n` + } else { + message += ` at ${source}\n` + } + } + return message } function normalizeServerLog (log: LogObject) { - log.additional = normalizeFilenames(log.stack as string) + log.additional = normalizeFilenames(log.stack as ParsedTrace[]) log.tag = 'ssr' delete log.stack return log diff --git a/packages/nuxt/src/core/runtime/nitro/dev-server-logs.ts b/packages/nuxt/src/core/runtime/nitro/dev-server-logs.ts index 3ff3f16676..eb42598f39 100644 --- a/packages/nuxt/src/core/runtime/nitro/dev-server-logs.ts +++ b/packages/nuxt/src/core/runtime/nitro/dev-server-logs.ts @@ -5,6 +5,8 @@ import { stringify } from 'devalue' import type { H3Event } from 'h3' import { withTrailingSlash } from 'ufo' import { getContext } from 'unctx' +import { captureRawStackTrace, parseRawStackTrace } from 'errx' +import type { ParsedTrace } from 'errx' import { isVNode } from 'vue' import type { NitroApp } from '#internal/nitro/app' @@ -34,15 +36,28 @@ export default (nitroApp: NitroApp) => { const ctx = asyncContext.tryUse() if (!ctx) { return } - const stack = getStack() - if (stack.includes('runtime/vite-node.mjs')) { return } + const rawStack = captureRawStackTrace() + if (!rawStack || rawStack.includes('runtime/vite-node.mjs')) { return } + + const trace: ParsedTrace[] = [] + let filename = '' + for (const entry of parseRawStackTrace(rawStack)) { + if (entry.source === import.meta.url) { continue } + if (EXCLUDE_TRACE_RE.test(entry.source)) { continue } + + filename ||= entry.source.replace(withTrailingSlash(rootDir), '') + trace.push({ + ...entry, + source: entry.source.startsWith('file://') ? entry.source.replace('file://', '') : entry.source, + }) + } const log = { ..._log, // Pass along filename to allow the client to display more info about where log comes from - filename: extractFilenameFromStack(stack), + filename, // Clean up file names in stack trace - stack: normalizeFilenames(stack), + stack: trace, } // retain log to be include in the next render @@ -68,24 +83,7 @@ export default (nitroApp: NitroApp) => { }) } -const EXCLUDE_TRACE_RE = /^.*at.*(\/node_modules\/(.*\/)?(nuxt|nuxt-nightly|nuxt-edge|nuxt3|consola|@vue)\/.*|core\/runtime\/nitro.*)$\n?/gm -function getStack () { - // Pass along stack traces if needed (for error and warns) - // eslint-disable-next-line unicorn/error-message - const stack = new Error() - Error.captureStackTrace(stack) - return stack.stack?.replace(EXCLUDE_TRACE_RE, '').replace(/^Error.*\n/, '') || '' -} - -const FILENAME_RE = /at[^(]*\(([^:)]+)[):]/ -const FILENAME_RE_GLOBAL = /at[^(]*\(([^)]+)\)/g -function extractFilenameFromStack (stacktrace: string) { - return stacktrace.match(FILENAME_RE)?.[1].replace(withTrailingSlash(rootDir), '') -} -function normalizeFilenames (stacktrace: string) { - // remove line numbers and file: protocol - TODO: sourcemap support for line numbers - return stacktrace.replace(FILENAME_RE_GLOBAL, (match, filename) => match.replace(filename, filename.replace('file:///', '/').replace(/:.*$/, ''))) -} +const EXCLUDE_TRACE_RE = /\/node_modules\/(?:.*\/)?(?:nuxt|nuxt-nightly|nuxt-edge|nuxt3|consola|@vue)\/|core\/runtime\/nitro/ function onConsoleLog (callback: (log: LogObject) => void) { consola.addReporter({ diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 463352d3f4..63b98a0321 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -312,6 +312,9 @@ importers: devalue: specifier: ^5.0.0 version: 5.0.0 + errx: + specifier: ^0.1.0 + version: 0.1.0 esbuild: specifier: ^0.23.0 version: 0.23.0 @@ -3771,7 +3774,7 @@ packages: resolution: {integrity: sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==} engines: {node: '>=14'} peerDependencies: - typescript: 5.5.3 + typescript: '>=4.9.5' peerDependenciesMeta: typescript: optional: true @@ -3780,7 +3783,7 @@ packages: resolution: {integrity: sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==} engines: {node: '>=14'} peerDependencies: - typescript: 5.5.3 + typescript: '>=4.9.5' peerDependenciesMeta: typescript: optional: true @@ -4169,6 +4172,9 @@ packages: error-stack-parser@2.1.4: resolution: {integrity: sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==} + errx@0.1.0: + resolution: {integrity: sha512-fZmsRiDNv07K6s2KkKFTiD2aIvECa7++PKyD5NC32tpRw46qZA3sOz+aM+/V9V0GDHxVTKLziveV4JhzBHDp9Q==} + es-define-property@1.0.0: resolution: {integrity: sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==} engines: {node: '>= 0.4'} @@ -11021,6 +11027,8 @@ snapshots: dependencies: stackframe: 1.3.4 + errx@0.1.0: {} + es-define-property@1.0.0: dependencies: get-intrinsic: 1.2.4