Skip to content

Commit 67325ea

Browse files
authored
Turbopack: log telemetry events when TurbopackInternalErrors occur (#77660)
Note: **This does not include any error content aside from that the error is a `TurbopackInternalError`.** This adds a function, `TurbopackInternalError.createAndRecordTelemetry` that both creates the `TurbopackInternalError` instance and logs that it occurred to telemetry. This telemetry event only includes 1) that an error occurred once and 2) the error code or error name of the error, in this case `TurbopackInternalError`. Test Plan: Added a unit test, `TurbopackInternalError.test.ts` Manual test plan: - Insert a panic into Turbopack source in `analyse_ecmascript_module`. - Run `NEXT_TELEMETRY_DEBUG=1 NODE_OPTIONS="--trace-deprecation --enable-source-maps" pnpm next "build" "--turbo" "test/e2e/app-dir/app/“`. - Verify telemetry debugging shows an event called `NEXT_ERROR_THROWN` with `errorCode` of `TurbopackInternalError`. - Do the same with `NEXT_TELEMETRY_DEBUG=1 NODE_OPTIONS="--trace-deprecation --enable-source-maps" pnpm next "dev" "--turbo" "test/e2e/app-dir/app/“`
1 parent 6acd29d commit 67325ea

File tree

6 files changed

+78
-3
lines changed

6 files changed

+78
-3
lines changed

packages/next/errors.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -669,5 +669,6 @@
669669
"668": "Internal Next.js error: Router action dispatched before initialization.",
670670
"669": "Invariant: --turbopack is set but the build used Webpack",
671671
"670": "Invariant: --turbopack is not set but the build used Turbopack. Add --turbopack to \"next start\".",
672-
"671": "Specified images.remotePatterns must have protocol \"http\" or \"https\" received \"%s\"."
672+
"671": "Specified images.remotePatterns must have protocol \"http\" or \"https\" received \"%s\".",
673+
"672": "Expected `telemetry` to be set in globals"
673674
}

packages/next/src/build/swc/index.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -509,7 +509,7 @@ function bindingToApi(
509509
try {
510510
return await fn()
511511
} catch (nativeError: any) {
512-
throw new TurbopackInternalError(nativeError)
512+
throw TurbopackInternalError.createAndRecordTelemetry(nativeError)
513513
}
514514
}
515515

@@ -577,7 +577,7 @@ function bindingToApi(
577577
} catch (e) {
578578
if (e === cancel) return
579579
if (e instanceof Error) {
580-
throw new TurbopackInternalError(e)
580+
throw TurbopackInternalError.createAndRecordTelemetry(e)
581581
}
582582
throw e
583583
} finally {

packages/next/src/server/lib/router-server.ts

+3
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ import { NEXT_PATCH_SYMBOL } from './patch-fetch'
5050
import type { ServerInitResult } from './render-server'
5151
import { filterInternalHeaders } from './server-ipc/utils'
5252
import { blockCrossSite } from './router-utils/block-cross-site'
53+
import { traceGlobals } from '../../trace/shared'
5354

5455
const debug = setupDebug('next:router-server:main')
5556
const isNextFont = (pathname: string | null) =>
@@ -122,6 +123,8 @@ export async function initialize(opts: {
122123
const telemetry = new Telemetry({
123124
distDir: path.join(opts.dir, config.distDir),
124125
})
126+
traceGlobals.set('telemetry', telemetry)
127+
125128
const { pagesDir, appDir } = findPagesDir(opts.dir)
126129

127130
const { setupDevBundler } =

packages/next/src/shared/lib/turbopack/utils.ts

+19
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import type {
33
StyledString,
44
TurbopackResult,
55
} from '../../../build/swc/types'
6+
67
import { bold, green, magenta, red } from '../../../lib/picocolors'
78
import isInternal from '../is-internal'
89
import {
@@ -13,6 +14,8 @@ import type { EntryKey } from './entry-key'
1314
import * as Log from '../../../build/output/log'
1415
import type { NextConfigComplete } from '../../../server/config-shared'
1516
import loadJsConfig from '../../../build/load-jsconfig'
17+
import { eventErrorThrown } from '../../../telemetry/events'
18+
import { traceGlobals } from '../../../trace/shared'
1619

1720
type IssueKey = `${Issue['severity']}-${Issue['filePath']}-${string}-${string}`
1821
export type IssuesMap = Map<IssueKey, Issue>
@@ -30,6 +33,22 @@ export class ModuleBuildError extends Error {
3033
export class TurbopackInternalError extends Error {
3134
name = 'TurbopackInternalError'
3235

36+
// Manually set this as this isn't statically determinable
37+
__NEXT_ERROR_CODE = 'TurbopackInternalError'
38+
39+
static createAndRecordTelemetry(cause: Error) {
40+
const error = new TurbopackInternalError(cause)
41+
42+
const telemetry = traceGlobals.get('telemetry')
43+
if (telemetry) {
44+
telemetry.record(eventErrorThrown(error))
45+
} else {
46+
console.error('Expected `telemetry` to be set in globals')
47+
}
48+
49+
return error
50+
}
51+
3352
constructor(cause: Error) {
3453
super(cause.message)
3554
this.stack = cause.stack

packages/next/src/telemetry/events/build.ts

+19
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { TelemetryPlugin } from '../../build/webpack/plugins/telemetry-plugin/telemetry-plugin'
22
import type { SWC_TARGET_TRIPLE } from '../../build/webpack/plugins/telemetry-plugin/telemetry-plugin'
33
import type { UseCacheTrackerKey } from '../../build/webpack/plugins/telemetry-plugin/use-cache-tracker-utils'
4+
import { extractNextErrorCode } from '../../lib/error-telemetry-utils'
45

56
const REGEXP_DIRECTORY_DUNDER =
67
/[\\/]__[^\\/]+(?<![\\/]__(?:tests|mocks))__[\\/]/i
@@ -212,3 +213,21 @@ export function eventPackageUsedInGetServerSideProps(
212213
},
213214
}))
214215
}
216+
217+
export const ERROR_THROWN_EVENT = 'NEXT_ERROR_THROWN'
218+
type ErrorThrownEvent = {
219+
eventName: typeof ERROR_THROWN_EVENT
220+
payload: {
221+
errorCode: string | undefined
222+
}
223+
}
224+
225+
// Creates a Telemetry event for errors. For privacy, only includes the error code.
226+
export function eventErrorThrown(error: Error): ErrorThrownEvent {
227+
return {
228+
eventName: ERROR_THROWN_EVENT,
229+
payload: {
230+
errorCode: extractNextErrorCode(error) || 'Unknown',
231+
},
232+
}
233+
}
+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import path from 'path'
2+
import os from 'os'
3+
import { TurbopackInternalError } from 'next/dist/shared/lib/turbopack/utils'
4+
import { Telemetry } from 'next/dist/telemetry/storage'
5+
import { setGlobal } from 'next/dist/trace'
6+
import { traceGlobals } from 'next/dist/trace/shared'
7+
8+
describe('TurbopackInternalError', () => {
9+
it('sends a telemetry event when TurbopackInternalError.createAndRecordTelemetry() is called', async () => {
10+
const oldTelemetry = traceGlobals.get('telemetry')
11+
12+
try {
13+
const distDir = path.join(os.tmpdir(), 'next-telemetry')
14+
const telemetry = new Telemetry({ distDir })
15+
setGlobal('telemetry', telemetry)
16+
const submitRecord = jest
17+
// @ts-ignore
18+
.spyOn(telemetry, 'submitRecord')
19+
// @ts-ignore
20+
.mockImplementation(() => Promise.resolve())
21+
TurbopackInternalError.createAndRecordTelemetry(new Error('test error'))
22+
23+
expect(submitRecord).toHaveBeenCalledWith({
24+
eventName: 'NEXT_ERROR_THROWN',
25+
payload: {
26+
errorCode: 'TurbopackInternalError',
27+
},
28+
})
29+
} finally {
30+
setGlobal('telemetry', oldTelemetry)
31+
}
32+
})
33+
})

0 commit comments

Comments
 (0)