-
-
Notifications
You must be signed in to change notification settings - Fork 7.4k
feat: forward runtime error logs to server #20916
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
hi-ogawa
wants to merge
18
commits into
vitejs:main
Choose a base branch
from
hi-ogawa:10-10-feat_log_unhandled_runtime_error_on_server
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+297
−0
Open
Changes from 13 commits
Commits
Show all changes
18 commits
Select commit
Hold shift + click to select a range
c897df2
feat: log unhandled runtime error on server
hi-ogawa c9fb028
tweak
hi-ogawa 13958cf
test: add test
hi-ogawa e8ea26a
cleanup
hi-ogawa 67c7ac2
feat: add forwardRuntimeLogs
hi-ogawa bfd7a89
docs: add server.forwardRuntimeLogs documentation
hi-ogawa b6e589c
docs
hi-ogawa b8db0dc
docs: improve grammar in forwardRuntimeLogs documentation
hi-ogawa f2a7d2f
test: on ci
hi-ogawa 807df92
chore: cleanup
hi-ogawa 5c86a99
tweak
hi-ogawa cd90e31
cleanup
hi-ogawa f32e11e
fix: windows
hi-ogawa 991a5d4
chore: comment
hi-ogawa 42d364d
refactor: tweak types
hi-ogawa f674955
chore: more types
hi-ogawa 0dc47db
fix: setupRuntimeLogHandler only when enabled
hi-ogawa 3b1a07f
Merge branch 'main' into 10-10-feat_log_unhandled_runtime_error_on_se…
hi-ogawa File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,96 @@ | ||
| import path from 'node:path' | ||
| import fs from 'node:fs' | ||
| import { parseErrorStacktrace } from '@vitest/utils/source-map' | ||
| import c from 'picocolors' | ||
| import type { DevEnvironment, Plugin } from '..' | ||
| import { normalizePath } from '..' | ||
| import { generateCodeFrame } from '../utils' | ||
|
|
||
| export function runtimeLogPlugin(pluginOpts: { | ||
| environments: string[] | ||
| }): Plugin { | ||
| return { | ||
| name: 'vite:runtime-log', | ||
| apply: 'serve', | ||
| configureServer(server) { | ||
| for (const name of pluginOpts.environments) { | ||
| const environment = server.environments[name] | ||
| environment.hot.on('vite:runtime-log', (payload: RuntimeLogPayload) => { | ||
| const output = formatError(payload.error, environment) | ||
| environment.config.logger.error(output, { | ||
| timestamp: true, | ||
| }) | ||
| }) | ||
| } | ||
| }, | ||
| } | ||
| } | ||
|
|
||
| type RuntimeLogPayload = { | ||
| error: { | ||
| name: string | ||
| message: string | ||
| stack?: string | ||
| } | ||
| } | ||
|
|
||
| function formatError(error: any, environment: DevEnvironment) { | ||
| // https://github.com/vitest-dev/vitest/blob/4783137cd8d766cf998bdf2d638890eaa51e08d9/packages/browser/src/node/projectParent.ts#L58 | ||
| const stacks = parseErrorStacktrace(error, { | ||
| getUrlId(id) { | ||
| const moduleGraph = environment.moduleGraph | ||
| const mod = moduleGraph.getModuleById(id) | ||
| if (mod) { | ||
| return id | ||
| } | ||
| const resolvedPath = normalizePath( | ||
| path.resolve(environment.config.root, id.slice(1)), | ||
| ) | ||
| const modUrl = moduleGraph.getModuleById(resolvedPath) | ||
| if (modUrl) { | ||
| return resolvedPath | ||
| } | ||
| // some browsers (looking at you, safari) don't report queries in stack traces | ||
| // the next best thing is to try the first id that this file resolves to | ||
| const files = moduleGraph.getModulesByFile(resolvedPath) | ||
| if (files && files.size) { | ||
| return files.values().next().value!.id! | ||
| } | ||
| return id | ||
| }, | ||
| getSourceMap(id) { | ||
| // stack is already rewritten on server | ||
| if (environment.name === 'client') { | ||
| return environment.moduleGraph.getModuleById(id)?.transformResult?.map | ||
| } | ||
| }, | ||
| }) | ||
|
|
||
| // https://github.com/vitest-dev/vitest/blob/4783137cd8d766cf998bdf2d638890eaa51e08d9/packages/vitest/src/node/printError.ts#L64 | ||
| const nearest = stacks.find((stack) => { | ||
| const modules = environment.moduleGraph.getModulesByFile(stack.file) | ||
| return ( | ||
| [...(modules || [])].some((m) => m.transformResult) && | ||
| fs.existsSync(stack.file) | ||
| ) | ||
| }) | ||
|
|
||
| let output = '' | ||
| const errorName = error.name || 'Unknown Error' | ||
| output += c.red(`[Unhandled error] ${c.bold(errorName)}: ${error.message}\n`) | ||
| for (const stack of stacks) { | ||
| const file = normalizePath( | ||
| path.relative(environment.config.root, stack.file), | ||
| ) | ||
| output += ` > ${[stack.method, `${file}:${stack.line}:${stack.column}`] | ||
| .filter(Boolean) | ||
| .join(' ')}\n` | ||
| if (stack === nearest) { | ||
| const code = fs.readFileSync(stack.file, 'utf-8') | ||
| // TODO: highlight? | ||
| output += generateCodeFrame(code, stack).replace(/^/gm, ' ') | ||
| output += '\n' | ||
| } | ||
| } | ||
| return output | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| import type { NormalizedModuleRunnerTransport } from './moduleRunnerTransport' | ||
|
|
||
| export type RuntimeLogPayload = { | ||
| error: { | ||
| name: string | ||
| message: string | ||
| stack?: string | ||
| } | ||
| } | ||
|
|
||
| export function setupRuntimeLogHandler( | ||
| transport: NormalizedModuleRunnerTransport, | ||
| ): void { | ||
| function sendError(error: any) { | ||
| // TODO: serialize extra properties, recursive cause, etc. | ||
| transport.send({ | ||
| type: 'custom', | ||
| event: 'vite:runtime-log', | ||
| data: { | ||
| error: { | ||
| name: error.name, | ||
| message: error.message, | ||
| stack: error.stack, | ||
| }, | ||
| } satisfies RuntimeLogPayload, | ||
| }) | ||
| } | ||
|
|
||
| if (typeof window !== 'undefined') { | ||
| window.addEventListener('error', (event) => { | ||
| sendError(event.error) | ||
| }) | ||
| window.addEventListener('unhandledrejection', (event) => { | ||
| sendError(event.reason) | ||
| }) | ||
| } | ||
|
|
||
| // TODO: server runtime? | ||
| // if (typeof process !== 'undefined') {} | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| import { stripVTControlCharacters } from 'node:util' | ||
| import { expect, test } from 'vitest' | ||
| import { isServe, page, serverLogs } from '~utils' | ||
|
|
||
| test.runIf(isServe)('unhandled error', async () => { | ||
| await page.click('#test-error') | ||
| await expect.poll(() => stripVTControlCharacters(serverLogs.at(-1))) | ||
| .toEqual(`\ | ||
| [Unhandled error] Error: this is test error | ||
| > testError src/main.ts:20:8 | ||
| 18 | | ||
| 19 | function testError() { | ||
| 20 | throw new Error('this is test error') | ||
| | ^ | ||
| 21 | } | ||
| 22 | | ||
| > HTMLButtonElement.<anonymous> src/main.ts:6:2 | ||
| `) | ||
| }) | ||
|
|
||
| test.runIf(isServe)('unhandled rejection', async () => { | ||
| await page.click('#test-unhandledrejection') | ||
| await expect.poll(() => stripVTControlCharacters(serverLogs.at(-1))) | ||
| .toEqual(`\ | ||
| [Unhandled error] Error: this is test unhandledrejection | ||
| > testUnhandledRejection src/main.ts:24:8 | ||
| 22 | | ||
| 23 | async function testUnhandledRejection() { | ||
| 24 | throw new Error('this is test unhandledrejection') | ||
| | ^ | ||
| 25 | } | ||
| 26 | | ||
| > HTMLButtonElement.<anonymous> src/main.ts:12:4 | ||
| `) | ||
| }) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| <button id="test-error">Test error</button> | ||
| <button id="test-unhandledrejection">Test unhandledrejection</button> | ||
| <script type="module" src="/src/main.ts"></script> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| { | ||
| "name": "@vitejs/test-runtime-log", | ||
| "private": true, | ||
| "version": "0.0.0", | ||
| "type": "module", | ||
| "scripts": { | ||
| "dev": "vite", | ||
| "build": "vite build", | ||
| "debug": "node --inspect-brk ../../packages/vite/bin/vite", | ||
| "preview": "vite preview" | ||
| }, | ||
| "dependencies": {}, | ||
| "devDependencies": {} | ||
| } |
Binary file not shown.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| export type SomePadding = { | ||
| here: boolean | ||
| } | ||
|
|
||
| document.getElementById('test-error').addEventListener('click', () => { | ||
| testError() | ||
| }) | ||
|
|
||
| document | ||
| .getElementById('test-unhandledrejection') | ||
| .addEventListener('click', () => { | ||
| testUnhandledRejection() | ||
| }) | ||
|
|
||
| export type AnotherPadding = { | ||
| there: boolean | ||
| } | ||
|
|
||
| function testError() { | ||
| throw new Error('this is test error') | ||
| } | ||
|
|
||
| async function testUnhandledRejection() { | ||
| throw new Error('this is test unhandledrejection') | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| import { defineConfig } from 'vite' | ||
|
|
||
| export default defineConfig({ | ||
| server: { | ||
| forwardRuntimeLogs: true, | ||
| }, | ||
| }) |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.