Skip to content

Commit 41b6ee1

Browse files
committed
feat: transactions in withSentry handler
1 parent b3ffadd commit 41b6ee1

File tree

1 file changed

+52
-4
lines changed

1 file changed

+52
-4
lines changed

packages/nextjs/src/utils/handlers.ts

Lines changed: 52 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
1-
import { captureException, flush, Handlers, withScope } from '@sentry/node';
2-
import { addExceptionMechanism } from '@sentry/utils';
1+
import { captureException, flush, getCurrentHub, Handlers, startTransaction, withScope } from '@sentry/node';
2+
import { extractTraceparentData, getActiveTransaction, hasTracingEnabled } from '@sentry/tracing';
3+
import { addExceptionMechanism, isString, logger, stripUrlQueryAndFragment } from '@sentry/utils';
34
import { NextApiHandler } from 'next';
45

6+
import { addRequestDataToEvent, NextRequest } from './instrumentServer';
7+
58
const { parseRequest } = Handlers;
69

710
// purely for clarity
@@ -12,9 +15,43 @@ export const withSentry = (handler: NextApiHandler): WrappedNextApiHandler => {
1215
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
1316
return async (req, res) => {
1417
try {
15-
// TODO: Start Transaction
18+
const currentScope = getCurrentHub().getScope();
19+
20+
if (currentScope) {
21+
currentScope.addEventProcessor(event => addRequestDataToEvent(event, req as NextRequest));
22+
23+
// We only want to record page and API requests
24+
if (hasTracingEnabled()) {
25+
// If there is a trace header set, extract the data from it (parentSpanId, traceId, and sampling decision)
26+
let traceparentData;
27+
if (req.headers && isString(req.headers['sentry-trace'])) {
28+
traceparentData = extractTraceparentData(req.headers['sentry-trace'] as string);
29+
logger.log(`[Tracing] Continuing trace ${traceparentData?.traceId}.`);
30+
}
31+
32+
const url = `${req.url}`;
33+
// pull off query string, if any
34+
const reqPath = stripUrlQueryAndFragment(url);
35+
36+
// requests for pages will only ever be GET requests, so don't bother to include the method in the transaction
37+
// name; requests to API routes could be GET, POST, PUT, etc, so do include it there
38+
const namePrefix = `${(req.method || 'GET').toUpperCase()} `;
39+
40+
const transaction = startTransaction(
41+
{
42+
name: `${namePrefix}${reqPath}`,
43+
op: 'http.server',
44+
metadata: { requestPath: reqPath },
45+
...traceparentData,
46+
},
47+
// extra context passed to the `tracesSampler`
48+
{ request: req },
49+
);
50+
currentScope.setSpan(transaction);
51+
}
52+
}
53+
1654
return await handler(req, res); // Call Handler
17-
// TODO: Finish Transaction
1855
} catch (e) {
1956
withScope(scope => {
2057
scope.addEventProcessor(event => {
@@ -27,6 +64,17 @@ export const withSentry = (handler: NextApiHandler): WrappedNextApiHandler => {
2764
});
2865
throw e;
2966
} finally {
67+
const transaction = getActiveTransaction();
68+
if (transaction) {
69+
transaction.setHttpStatus(res.statusCode);
70+
71+
// we'll collect this data in a more targeted way in the event processor we added above,
72+
// `addRequestDataToEvent`
73+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
74+
delete transaction.metadata.requestPath;
75+
76+
transaction.finish();
77+
}
3078
await flush(2000);
3179
}
3280
};

0 commit comments

Comments
 (0)