Skip to content

Commit 264ad0b

Browse files
authored
feat(nuxt): Implement server middleware instrumentation (#17796)
This pull request introduces instrumentation for Nuxt middleware, ensuring that all middleware handlers are automatically wrapped with tracing and error reporting functionality. The integration is achieved through build-time transformation. recap: * Adds a new build-time Rollup plugin (`middlewareInstrumentationPlugin`) that automatically wraps all detected middleware handlers with Sentry instrumentation during the Nitro build process. * Implements the `wrapMiddlewareHandler` utility, which wraps middleware handlers to start a Sentry span, capture request data, record exceptions, and flush events in serverless environments. * Updates the Nuxt module setup to inject Sentry middleware imports and instrumentation hooks during initialization, ensuring the new tracing logic is included in server builds. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > Adds build-time wrapping of Nuxt server middleware with Sentry spans and error capture, plus Nuxt 3/4 e2e and unit tests. > > - **SDK/Runtime**: > - Implement `wrapMiddlewareHandlerWithSentry` in `packages/nuxt/src/runtime/hooks/wrapMiddlewareHandler.ts` to start spans, set attributes (op, origin, route, method, headers), handle hook arrays (`onRequest`, `onBeforeResponse`), and capture errors. > - Add middleware build-time transformation via `packages/nuxt/src/vite/middlewareConfig.ts` (server import + Rollup plugin to wrap `defineEventHandler`/`eventHandler`). > - Integrate instrumentation in `packages/nuxt/src/module.ts` by calling `addMiddlewareImports` and `addMiddlewareInstrumentation` during Nitro init when server config is present. > - **Tests**: > - Add e2e apps and Playwright tests for Nuxt 3 and Nuxt 4 (`dev-packages/e2e-tests/test-applications/nuxt-{3,4}`) covering span creation, attributes, parent-child relationships, and error propagation across hooks and arrays. > - Add unit tests for wrapper behavior in `packages/nuxt/test/runtime/hooks/wrapMiddlewareHandler.test.ts`. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 9a4a1f3. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
1 parent 11ea830 commit 264ad0b

File tree

18 files changed

+1716
-0
lines changed

18 files changed

+1716
-0
lines changed
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { defineEventHandler, getHeader } from '#imports';
2+
3+
export default defineEventHandler(async event => {
4+
// Simple API endpoint that will trigger all server middleware
5+
return {
6+
message: 'Server middleware test endpoint',
7+
path: event.path,
8+
method: event.method,
9+
headers: {
10+
'x-first-middleware': getHeader(event, 'x-first-middleware'),
11+
'x-second-middleware': getHeader(event, 'x-second-middleware'),
12+
'x-auth-middleware': getHeader(event, 'x-auth-middleware'),
13+
},
14+
};
15+
});
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { defineEventHandler, setHeader } from '#imports';
2+
3+
export default defineEventHandler(async event => {
4+
// Set a header to indicate this middleware ran
5+
setHeader(event, 'x-first-middleware', 'executed');
6+
});
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { eventHandler, setHeader } from '#imports';
2+
3+
// tests out the eventHandler alias
4+
export default eventHandler(async event => {
5+
// Set a header to indicate this middleware ran
6+
setHeader(event, 'x-second-middleware', 'executed');
7+
});
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { defineEventHandler, setHeader, getQuery } from '#imports';
2+
3+
export default defineEventHandler(async event => {
4+
// Check if we should throw an error
5+
const query = getQuery(event);
6+
if (query.throwError === 'true') {
7+
throw new Error('Auth middleware error');
8+
}
9+
10+
// Set a header to indicate this middleware ran
11+
setHeader(event, 'x-auth-middleware', 'executed');
12+
});
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { defineEventHandler, setHeader, getQuery } from '#imports';
2+
3+
export default defineEventHandler({
4+
onRequest: async event => {
5+
// Set a header to indicate the onRequest hook ran
6+
setHeader(event, 'x-hooks-onrequest', 'executed');
7+
8+
// Check if we should throw an error in onRequest
9+
const query = getQuery(event);
10+
if (query.throwOnRequestError === 'true') {
11+
throw new Error('OnRequest hook error');
12+
}
13+
},
14+
15+
handler: async event => {
16+
// Set a header to indicate the main handler ran
17+
setHeader(event, 'x-hooks-handler', 'executed');
18+
19+
// Check if we should throw an error in handler
20+
const query = getQuery(event);
21+
if (query.throwHandlerError === 'true') {
22+
throw new Error('Handler error');
23+
}
24+
},
25+
26+
onBeforeResponse: async (event, response) => {
27+
// Set a header to indicate the onBeforeResponse hook ran
28+
setHeader(event, 'x-hooks-onbeforeresponse', 'executed');
29+
30+
// Check if we should throw an error in onBeforeResponse
31+
const query = getQuery(event);
32+
if (query.throwOnBeforeResponseError === 'true') {
33+
throw new Error('OnBeforeResponse hook error');
34+
}
35+
},
36+
});
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import { defineEventHandler, setHeader, getQuery } from '#imports';
2+
3+
export default defineEventHandler({
4+
// Array of onRequest handlers
5+
onRequest: [
6+
async event => {
7+
setHeader(event, 'x-array-onrequest-0', 'executed');
8+
9+
const query = getQuery(event);
10+
if (query.throwOnRequest0Error === 'true') {
11+
throw new Error('OnRequest[0] hook error');
12+
}
13+
},
14+
async event => {
15+
setHeader(event, 'x-array-onrequest-1', 'executed');
16+
17+
const query = getQuery(event);
18+
if (query.throwOnRequest1Error === 'true') {
19+
throw new Error('OnRequest[1] hook error');
20+
}
21+
},
22+
],
23+
24+
handler: async event => {
25+
setHeader(event, 'x-array-handler', 'executed');
26+
},
27+
28+
// Array of onBeforeResponse handlers
29+
onBeforeResponse: [
30+
async (event, response) => {
31+
setHeader(event, 'x-array-onbeforeresponse-0', 'executed');
32+
33+
const query = getQuery(event);
34+
if (query.throwOnBeforeResponse0Error === 'true') {
35+
throw new Error('OnBeforeResponse[0] hook error');
36+
}
37+
},
38+
async (event, response) => {
39+
setHeader(event, 'x-array-onbeforeresponse-1', 'executed');
40+
41+
const query = getQuery(event);
42+
if (query.throwOnBeforeResponse1Error === 'true') {
43+
throw new Error('OnBeforeResponse[1] hook error');
44+
}
45+
},
46+
],
47+
});

0 commit comments

Comments
 (0)