mirror of
https://github.com/vercel/next.js.git
synced 2026-09-20 02:25:18 +08:00
3a5d293c38
## Goals - Pass through a custom `TurbopackInternalError` type when a Rust panic or an unexpected top-level `anyhow::Error` occurs. - Make a best-effort attempt to log a location of the error to telemetry. - In the case of a panic, we should have the file, line, and column where the panic occured. This could be obscured inside of a helper method, but it's better than nothing. Since this is a position in Turbopack's source code, there's zero risk of user data being included here, so it should be safe to log to telemetry. ## How it works In previous PRs, @wbinnssmith did the following: - Added a thread-local for storing the most recent panic information in Rust - Added `catch_unwind` logic to intercept the panic in turbo-tasks and read the thread-local information, with care take to make sure we're reading from the same thread (not across an `await` boundary). - Added a custom `std::error::Error` type for internal errors, with support for storing information about a source panic. - Ensured we set up `Error::source` correctly everywhere so that you can trace an error back to the root cause (presumably a panic). In #80881, I added a `NextTurbopackContext` type that we can extend to store additional helpers on. In this PR: - Adds a callback to the `Project` constructor called `throwTurbopackInternalError` that constructs and throws a custom `TurbopackInternalError`. The definition of `TurbopackInternalError` must be in JS, as we cannot reasonably define the error subclass using NAPI. - Wraps the callback in a `ThreadsafeFunction` so that it can be called in any of our async functions. - Replaces all the places we were previously converting to `napi::Error` with logic to run `throwTurbopackInternalError`. In a subsequent PR, we could move more of the panic handling into JS using `throwTurbopackInternalError`. ## Testing Added a panic and ran: ``` NEXT_TELEMETRY_DEBUG=1 pnpm next dev --turbopack examples/basic-css/ ``` <img src="https://graphite-user-uploaded-assets-prod.s3.amazonaws.com/HAZVitxRNnZz8QMiPn4a/7f261d3f-351e-436a-9345-fd6e9056d20c.png" width="600">
49 lines
1.4 KiB
TypeScript
49 lines
1.4 KiB
TypeScript
import path from 'path'
|
|
import os from 'os'
|
|
import {
|
|
throwTurbopackInternalError,
|
|
TurbopackInternalError,
|
|
} from 'next/dist/shared/lib/turbopack/internal-error'
|
|
import { Telemetry } from 'next/dist/telemetry/storage'
|
|
import { setGlobal } from 'next/dist/trace'
|
|
import { traceGlobals } from 'next/dist/trace/shared'
|
|
|
|
describe('TurbopackInternalError', () => {
|
|
it('sends a telemetry event when throwTurbopackInternalError() is called', async () => {
|
|
const oldTelemetry = traceGlobals.get('telemetry')
|
|
|
|
try {
|
|
const distDir = path.join(os.tmpdir(), 'next-telemetry')
|
|
const telemetry = new Telemetry({ distDir })
|
|
setGlobal('telemetry', telemetry)
|
|
const submitRecord = jest
|
|
// @ts-ignore
|
|
.spyOn(telemetry, 'submitRecord')
|
|
// @ts-ignore
|
|
.mockImplementation(() => Promise.resolve())
|
|
|
|
let internalError = null
|
|
try {
|
|
throwTurbopackInternalError(null, {
|
|
message: 'test error',
|
|
anonymizedLocation: 'file.rs:120:1',
|
|
})
|
|
} catch (err) {
|
|
internalError = err
|
|
}
|
|
|
|
expect(internalError).toBeInstanceOf(TurbopackInternalError)
|
|
|
|
expect(submitRecord).toHaveBeenCalledWith({
|
|
eventName: 'NEXT_ERROR_THROWN',
|
|
payload: {
|
|
errorCode: 'TurbopackInternalError',
|
|
location: 'file.rs:120:1',
|
|
},
|
|
})
|
|
} finally {
|
|
setGlobal('telemetry', oldTelemetry)
|
|
}
|
|
})
|
|
})
|