Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
import { expect, test } from '@playwright/test';
import { waitForError, waitForTransaction } from '@sentry-internal/test-utils';

const packageJson = require('../package.json');
const nextjsVersion = packageJson.dependencies.next;
const nextjsMajor = Number(nextjsVersion.split('.')[0]);

test('Should create a transaction for middleware', async ({ request }) => {
test.skip(
nextjsMajor === 13,
'Middleware transactions are not created in Next.js 13 after dropping tracing from middleware',
);
const middlewareTransactionPromise = waitForTransaction('nextjs-pages-dir', async transactionEvent => {
return transactionEvent?.transaction === 'middleware GET';
});
Expand All @@ -22,6 +30,10 @@ test('Should create a transaction for middleware', async ({ request }) => {
});

test('Faulty middlewares', async ({ request }) => {
test.skip(
nextjsMajor === 13,
'Middleware transactions are not created in Next.js 13 after dropping tracing from middleware',
);
const middlewareTransactionPromise = waitForTransaction('nextjs-pages-dir', async transactionEvent => {
return transactionEvent?.transaction === 'middleware GET';
});
Expand Down Expand Up @@ -53,6 +65,10 @@ test('Faulty middlewares', async ({ request }) => {
});

test('Should trace outgoing fetch requests inside middleware and create breadcrumbs for it', async ({ request }) => {
test.skip(
nextjsMajor === 13,
'Middleware transactions are not created in Next.js 13 after dropping tracing from middleware',
);
const middlewareTransactionPromise = waitForTransaction('nextjs-pages-dir', async transactionEvent => {
return (
transactionEvent?.transaction === 'middleware GET' &&
Expand Down
93 changes: 19 additions & 74 deletions packages/nextjs/src/common/wrapMiddlewareWithSentry.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,4 @@
import type { TransactionSource } from '@sentry/core';
import {
captureException,
getActiveSpan,
getCurrentScope,
getRootSpan,
handleCallbackErrors,
SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN,
SEMANTIC_ATTRIBUTE_SENTRY_SOURCE,
setCapturedScopesOnSpan,
startSpan,
winterCGRequestToRequestData,
withIsolationScope,
} from '@sentry/core';
import { captureException, getCurrentScope, handleCallbackErrors } from '@sentry/core';
import { flushSafelyWithTimeout, waitUntil } from '../common/utils/responseEnd';
import type { EdgeRouteHandler } from '../edge/types';

Expand All @@ -31,6 +18,7 @@ export function wrapMiddlewareWithSentry<H extends EdgeRouteHandler>(
? (globalThis as Record<string, unknown>)._sentryRewritesTunnelPath
: undefined;

// TODO: This can never work with Turbopack, need to remove it for consistency between builds.
if (tunnelRoute && typeof tunnelRoute === 'string') {
const req: unknown = args[0];
// Check if the current request matches the tunnel route
Expand All @@ -51,68 +39,25 @@ export function wrapMiddlewareWithSentry<H extends EdgeRouteHandler>(
}
}
}
// TODO: We still should add central isolation scope creation for when our build-time instrumentation does not work anymore with turbopack.
return withIsolationScope(isolationScope => {
const req: unknown = args[0];
const currentScope = getCurrentScope();

let spanName: string;
let spanSource: TransactionSource;

if (req instanceof Request) {
isolationScope.setSDKProcessingMetadata({
normalizedRequest: winterCGRequestToRequestData(req),
});
spanName = `middleware ${req.method}`;
spanSource = 'url';
} else {
spanName = 'middleware';
spanSource = 'component';
}

currentScope.setTransactionName(spanName);

const activeSpan = getActiveSpan();

if (activeSpan) {
// If there is an active span, it likely means that the automatic Next.js OTEL instrumentation worked and we can
// rely on that for parameterization.
spanName = 'middleware';
spanSource = 'component';

const rootSpan = getRootSpan(activeSpan);
if (rootSpan) {
setCapturedScopesOnSpan(rootSpan, currentScope, isolationScope);
}
}

return startSpan(
{
name: spanName,
op: 'http.server.middleware',
attributes: {
[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: spanSource,
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.function.nextjs.wrap_middleware',
const req: unknown = args[0];
const spanName = req instanceof Request ? `middleware ${req.method}` : 'middleware';
getCurrentScope().setTransactionName(spanName);

return handleCallbackErrors(
() => wrappingTarget.apply(thisArg, args),
error => {
captureException(error, {
mechanism: {
type: 'auto.function.nextjs.wrap_middleware',
handled: false,
},
},
() => {
return handleCallbackErrors(
() => wrappingTarget.apply(thisArg, args),
error => {
captureException(error, {
mechanism: {
type: 'auto.function.nextjs.wrap_middleware',
handled: false,
},
});
},
() => {
waitUntil(flushSafelyWithTimeout());
},
);
},
);
});
});
},
() => {
waitUntil(flushSafelyWithTimeout());
},
);
},
});
}