Skip to content

Commit 39c3cd6

Browse files
yiminghehuozhi
andcommitted
fix: ensure onRequestError is invoked when otel enabled (#83343)
Co-authored-by: Jiachi Liu <[email protected]>
1 parent 07d1cbc commit 39c3cd6

File tree

8 files changed

+169
-4
lines changed

8 files changed

+169
-4
lines changed

packages/next/src/build/templates/app-page.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1244,8 +1244,7 @@ export async function handler(
12441244
)
12451245
}
12461246
} catch (err) {
1247-
// if we aren't wrapped by base-server handle here
1248-
if (!activeSpan && !(err instanceof NoFallbackError)) {
1247+
if (!(err instanceof NoFallbackError)) {
12491248
await routeModule.onRequestError(
12501249
req,
12511250
err,

packages/next/src/build/templates/app-route.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -448,8 +448,7 @@ export async function handler(
448448
)
449449
}
450450
} catch (err) {
451-
// if we aren't wrapped by base-server handle here
452-
if (!activeSpan && !(err instanceof NoFallbackError)) {
451+
if (!(err instanceof NoFallbackError)) {
453452
await routeModule.onRequestError(req, err, {
454453
routerKind: 'App Router',
455454
routePath: normalizedSrcPage,
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { connection } from 'next/server'
2+
3+
export async function GET() {
4+
await connection()
5+
throw new Error('route-node-error')
6+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { Suspense } from 'react'
2+
3+
export default function Layout({ children }) {
4+
return (
5+
<html>
6+
<body>
7+
<Suspense>{children}</Suspense>
8+
</body>
9+
</html>
10+
)
11+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { connection } from 'next/server'
2+
3+
export default async function Page() {
4+
await connection()
5+
throw new Error('server-page-node-error')
6+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import fs from 'fs'
2+
import fsp from 'fs/promises'
3+
import path from 'path'
4+
5+
const dir = path.join(path.dirname(new URL(import.meta.url).pathname), '../..')
6+
const logPath = path.join(dir, 'output-log.json')
7+
8+
export async function POST(req) {
9+
let payloadString = ''
10+
const decoder = new TextDecoder()
11+
const reader = req.clone().body.getReader()
12+
while (true) {
13+
const { done, value } = await reader.read()
14+
if (done) {
15+
break
16+
}
17+
18+
payloadString += decoder.decode(value)
19+
}
20+
21+
const payload = JSON.parse(payloadString)
22+
23+
const json = fs.existsSync(logPath)
24+
? JSON.parse(await fsp.readFile(logPath, 'utf8'))
25+
: {}
26+
27+
if (!json[payload.message]) {
28+
json[payload.message] = {
29+
payload,
30+
count: 1,
31+
}
32+
} else {
33+
json[payload.message].count++
34+
}
35+
36+
await fsp.writeFile(logPath, JSON.stringify(json, null, 2), 'utf8')
37+
38+
console.log(`[instrumentation] write-log:${payload.message}`)
39+
return new Response(null, { status: 204 })
40+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { registerOTel } from '@vercel/otel'
2+
3+
export const onRequestError = async (err, request, context) => {
4+
await fetch(`http://localhost:${process.env.PORT}/write-log`, {
5+
method: 'POST',
6+
body: JSON.stringify({
7+
message: err.message,
8+
request,
9+
context,
10+
}),
11+
headers: {
12+
'Content-Type': 'application/json',
13+
},
14+
})
15+
}
16+
17+
export function register() {
18+
registerOTel({
19+
serviceName: 'test',
20+
})
21+
}
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import { nextTestSetup } from 'e2e-utils'
2+
import { retry } from 'next-test-utils'
3+
import { getOutputLogJson } from '../_testing/utils'
4+
5+
describe('on-request-error - otel', () => {
6+
const { next, skipped } = nextTestSetup({
7+
files: __dirname,
8+
skipDeployment: true,
9+
packageJson: {
10+
dependencies: {
11+
'@vercel/otel': '^1.13.0',
12+
},
13+
},
14+
})
15+
16+
if (skipped) {
17+
return
18+
}
19+
20+
const outputLogPath = 'output-log.json'
21+
22+
async function validateErrorRecord({
23+
errorMessage,
24+
url,
25+
renderSource,
26+
}: {
27+
errorMessage: string
28+
url: string
29+
renderSource: string | undefined
30+
}) {
31+
// Assert the instrumentation is called
32+
await retry(async () => {
33+
const recordLogLines = next.cliOutput
34+
.split('\n')
35+
.filter((log) => log.includes('[instrumentation] write-log'))
36+
expect(recordLogLines).toEqual(
37+
expect.arrayContaining([expect.stringContaining(errorMessage)])
38+
)
39+
// TODO: remove custom duration in case we increase the default.
40+
}, 5000)
41+
42+
const json = await getOutputLogJson(next, outputLogPath)
43+
const record = json[errorMessage]
44+
45+
const { payload } = record
46+
const { request } = payload
47+
48+
expect(request.path).toBe(url)
49+
expect(record).toMatchObject({
50+
count: 1,
51+
payload: {
52+
message: errorMessage,
53+
request: { method: 'GET', headers: { accept: '*/*' } },
54+
...(renderSource ? { context: { renderSource } } : undefined),
55+
},
56+
})
57+
}
58+
59+
beforeAll(async () => {
60+
await next.patchFile(outputLogPath, '{}')
61+
})
62+
63+
describe('app router', () => {
64+
it('should catch server component page error in node runtime', async () => {
65+
await next.fetch('/server-page')
66+
await validateErrorRecord({
67+
errorMessage: 'server-page-node-error',
68+
url: '/server-page',
69+
renderSource: 'react-server-components',
70+
})
71+
})
72+
73+
it('should catch app routes error in node runtime', async () => {
74+
await next.fetch('/app-route')
75+
76+
await validateErrorRecord({
77+
errorMessage: 'route-node-error',
78+
url: '/app-route',
79+
renderSource: undefined,
80+
})
81+
})
82+
})
83+
})

0 commit comments

Comments
 (0)