11import { deepReadDirSync } from '@sentry/node' ;
22import { getActiveTransaction , hasTracingEnabled } from '@sentry/tracing' ;
3- import { Event as SentryEvent , Transaction } from '@sentry/types' ;
3+ import { Event as SentryEvent } from '@sentry/types' ;
44import { fill , logger } from '@sentry/utils' ;
55import * as domain from 'domain' ;
66import * as http from 'http' ;
@@ -33,22 +33,18 @@ interface NextRequest extends http.IncomingMessage {
3333 query : { [ key : string ] : string } ;
3434 headers : { [ key : string ] : string } ;
3535}
36+ type NextResponse = http . ServerResponse ;
3637
37- interface NextResponse extends http . ServerResponse {
38- __sentry__ : {
39- transaction ?: Transaction ;
40- } ;
41- }
42-
38+ // the methods we'll wrap
4339type HandlerGetter = ( ) => Promise < ReqHandler > ;
4440type ReqHandler = ( req : NextRequest , res : NextResponse , parsedUrl ?: url . UrlWithParsedQuery ) => Promise < void > ;
4541type ErrorLogger = ( err : Error ) => void ;
4642type ApiPageEnsurer = ( path : string ) => Promise < void > ;
4743type PageComponentFinder = (
4844 pathname : string ,
4945 query : querystring . ParsedUrlQuery ,
50- params : { [ key : string ] : any } | null ,
51- ) => Promise < { [ key : string ] : any } | null > ;
46+ params : { [ key : string ] : unknown } | null ,
47+ ) => Promise < { [ key : string ] : unknown } | null > ;
5248
5349// these aliases are purely to make the function signatures more easily understandable
5450type WrappedHandlerGetter = HandlerGetter ;
@@ -57,19 +53,14 @@ type WrappedReqHandler = ReqHandler;
5753type WrappedApiPageEnsurer = ApiPageEnsurer ;
5854type WrappedPageComponentFinder = PageComponentFinder ;
5955
60- // TODO is it necessary for this to be an object?
61- const closure : PlainObject = { } ;
62-
56+ let liveServer : Server ;
6357let sdkSetupComplete = false ;
6458
6559/**
6660 * Do the monkeypatching and wrapping necessary to catch errors in page routes and record transactions for both page and
67- * API routes. Along the way, as a bonus, grab (and return) the path of the project root, for use in `RewriteFrames`.
68- *
69- * @returns The absolute path of the project root directory
70- *
61+ * API routes.
7162 */
72- export function instrumentServer ( ) : string {
63+ export function instrumentServer ( ) : void {
7364 // The full implementation here involves a lot of indirection and multiple layers of callbacks and wrapping, and is
7465 // therefore potentially a little hard to follow. Here's the overall idea:
7566
@@ -78,26 +69,34 @@ export function instrumentServer(): string {
7869 // `createNextServer()`, which returns a `NextServer` instance.
7970
8071 // At server startup:
81- // `next.config.js` imports SDK -> SDK index.ts -> `instrumentServer()` (the function we're in right now) ->
82- // `createNextServer()` -> `NextServer` instance -> `NextServer` prototype -> wrap
83- // `NextServer.getServerRequestHandler()`, purely to get us to the next step
72+ // `next.config.js` imports SDK ->
73+ // SDK's `index.ts` runs ->
74+ // `instrumentServer()` (the function we're in right now) ->
75+ // `createNextServer()` ->
76+ // `NextServer` instance ->
77+ // `NextServer` prototype ->
78+ // Wrap `NextServer.getServerRequestHandler()`, purely to get us to the next step
8479
8580 // At time of first request:
86- // Wrapped `getServerRequestHandler` runs for the first time -> live `NextServer` instance (via `this`) -> live
87- // `Server` instance -> `Server` prototype -> wrap `Server.logError` and `Server.handleRequest` methods, then pass
88- // wrapped version of `handleRequest` to caller of `getServerRequestHandler`
81+ // Wrapped `getServerRequestHandler` runs for the first time ->
82+ // Live `NextServer` instance(via`this`) ->
83+ // Live `Server` instance (via `NextServer.server`) ->
84+ // `Server` prototype ->
85+ // Wrap `Server.logError`, `Server.handleRequest`, `Server.ensureApiPage`, and `Server.findPageComponents` methods,
86+ // then fulfill original purpose of function by passing wrapped version of `handleRequest` to caller
8987
9088 // Whenever caller of `NextServer.getServerRequestHandler` calls the wrapped `Server.handleRequest`:
91- // Trace request
89+ // Trace request
9290
9391 // Whenever something calls the wrapped `Server.logError`:
94- // Capture error
92+ // Capture error
93+
94+ // Whenever an API request is handled and the wrapped `Server.ensureApiPage` is called, or whenever a page request is
95+ // handled and the wrapped `Server.findPageComponents` is called:
96+ // Replace URL in transaction name with parameterized version
9597
9698 const nextServerPrototype = Object . getPrototypeOf ( createNextServer ( { } ) ) ;
9799 fill ( nextServerPrototype , 'getServerRequestHandler' , makeWrappedHandlerGetter ) ;
98-
99- // TODO replace with an env var, since at this point we don't have a value yet
100- return closure . projectRootDir ;
101100}
102101
103102/**
@@ -122,17 +121,14 @@ function makeWrappedHandlerGetter(origHandlerGetter: HandlerGetter): WrappedHand
122121 logger . error ( `[Sentry] Could not initialize SDK. Received error:\n${ err } ` ) ;
123122 }
124123
125- // TODO: Replace projectRootDir with env variables
126- closure . projectRootDir = this . server . dir ;
127- closure . server = this . server ;
128- closure . publicDir = this . server . publicDir ;
129-
130- const serverPrototype = Object . getPrototypeOf ( this . server ) ;
124+ // stash this in the closure so that `makeWrappedReqHandler` can use it
125+ liveServer = this . server ;
126+ const serverPrototype = Object . getPrototypeOf ( liveServer ) ;
131127
132- // wrap for error capturing (`logError` gets called by `next` for all server-side errors)
128+ // Wrap for error capturing (`logError` gets called by `next` for all server-side errors)
133129 fill ( serverPrototype , 'logError' , makeWrappedErrorLogger ) ;
134130
135- // wrap for request transaction creation (`handleRequest` is called for all incoming requests, and dispatches them
131+ // Wrap for request transaction creation (`handleRequest` is called for all incoming requests, and dispatches them
136132 // to the appropriate handlers)
137133 fill ( serverPrototype , 'handleRequest' , makeWrappedReqHandler ) ;
138134
@@ -172,8 +168,6 @@ function makeWrappedErrorLogger(origErrorLogger: ErrorLogger): WrappedErrorLogge
172168 * @returns A wrapped version of that handler
173169 */
174170function makeWrappedReqHandler ( origReqHandler : ReqHandler ) : WrappedReqHandler {
175- const liveServer = closure . server as Server ;
176-
177171 // inspired by next's public file routing; see
178172 // https://github.com/vercel/next.js/blob/4443d6f3d36b107e833376c2720c1e206eee720d/packages/next/next-server/server/next-server.ts#L1166
179173 const publicDirFiles = new Set (
@@ -286,6 +280,13 @@ function shouldTraceRequest(url: string, publicDirFiles: Set<string>): boolean {
286280 return ! url . startsWith ( '/_next/' ) && ! url . startsWith ( '/static/' ) && ! publicDirFiles . has ( url ) ;
287281}
288282
283+ /**
284+ * Harvest specific data from the request, and add it to the event.
285+ *
286+ * @param event The event to which to add request data
287+ * @param req The request whose data is being added
288+ * @returns The modified event
289+ */
289290function addRequestDataToEvent ( event : SentryEvent , req : NextRequest ) : SentryEvent {
290291 event . request = {
291292 ...event . request ,
0 commit comments