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' ;
@@ -32,22 +32,18 @@ interface NextRequest extends http.IncomingMessage {
3232 url : string ;
3333 query : { [ key : string ] : string } ;
3434}
35+ type NextResponse = http . ServerResponse ;
3536
36- interface NextResponse extends http . ServerResponse {
37- __sentry__ : {
38- transaction ?: Transaction ;
39- } ;
40- }
41-
37+ // the methods we'll wrap
4238type HandlerGetter = ( ) => Promise < ReqHandler > ;
4339type ReqHandler = ( req : NextRequest , res : NextResponse , parsedUrl ?: url . UrlWithParsedQuery ) => Promise < void > ;
4440type ErrorLogger = ( err : Error ) => void ;
4541type ApiPageEnsurer = ( path : string ) => Promise < void > ;
4642type PageComponentFinder = (
4743 pathname : string ,
4844 query : querystring . ParsedUrlQuery ,
49- params : { [ key : string ] : any } | null ,
50- ) => Promise < { [ key : string ] : any } | null > ;
45+ params : { [ key : string ] : unknown } | null ,
46+ ) => Promise < { [ key : string ] : unknown } | null > ;
5147
5248// these aliases are purely to make the function signatures more easily understandable
5349type WrappedHandlerGetter = HandlerGetter ;
@@ -56,19 +52,14 @@ type WrappedReqHandler = ReqHandler;
5652type WrappedApiPageEnsurer = ApiPageEnsurer ;
5753type WrappedPageComponentFinder = PageComponentFinder ;
5854
59- // TODO is it necessary for this to be an object?
60- const closure : PlainObject = { } ;
61-
55+ let liveServer : Server ;
6256let sdkSetupComplete = false ;
6357
6458/**
6559 * Do the monkeypatching and wrapping necessary to catch errors in page routes and record transactions for both page and
66- * API routes. Along the way, as a bonus, grab (and return) the path of the project root, for use in `RewriteFrames`.
67- *
68- * @returns The absolute path of the project root directory
69- *
60+ * API routes.
7061 */
71- export function instrumentServer ( ) : string {
62+ export function instrumentServer ( ) : void {
7263 // The full implementation here involves a lot of indirection and multiple layers of callbacks and wrapping, and is
7364 // therefore potentially a little hard to follow. Here's the overall idea:
7465
@@ -77,26 +68,34 @@ export function instrumentServer(): string {
7768 // `createNextServer()`, which returns a `NextServer` instance.
7869
7970 // At server startup:
80- // `next.config.js` imports SDK -> SDK index.ts -> `instrumentServer()` (the function we're in right now) ->
81- // `createNextServer()` -> `NextServer` instance -> `NextServer` prototype -> wrap
82- // `NextServer.getServerRequestHandler()`, purely to get us to the next step
71+ // `next.config.js` imports SDK ->
72+ // SDK's `index.ts` runs ->
73+ // `instrumentServer()` (the function we're in right now) ->
74+ // `createNextServer()` ->
75+ // `NextServer` instance ->
76+ // `NextServer` prototype ->
77+ // Wrap `NextServer.getServerRequestHandler()`, purely to get us to the next step
8378
8479 // At time of first request:
85- // Wrapped `getServerRequestHandler` runs for the first time -> live `NextServer` instance (via `this`) -> live
86- // `Server` instance -> `Server` prototype -> wrap `Server.logError` and `Server.handleRequest` methods, then pass
87- // wrapped version of `handleRequest` to caller of `getServerRequestHandler`
80+ // Wrapped `getServerRequestHandler` runs for the first time ->
81+ // Live `NextServer` instance(via`this`) ->
82+ // Live `Server` instance (via `NextServer.server`) ->
83+ // `Server` prototype ->
84+ // Wrap `Server.logError`, `Server.handleRequest`, `Server.ensureApiPage`, and `Server.findPageComponents` methods,
85+ // then fulfill original purpose of function by passing wrapped version of `handleRequest` to caller
8886
8987 // Whenever caller of `NextServer.getServerRequestHandler` calls the wrapped `Server.handleRequest`:
90- // Trace request
88+ // Trace request
9189
9290 // Whenever something calls the wrapped `Server.logError`:
93- // Capture error
91+ // Capture error
92+
93+ // Whenever an API request is handled and the wrapped `Server.ensureApiPage` is called, or whenever a page request is
94+ // handled and the wrapped `Server.findPageComponents` is called:
95+ // Replace URL in transaction name with parameterized version
9496
9597 const nextServerPrototype = Object . getPrototypeOf ( createNextServer ( { } ) ) ;
9698 fill ( nextServerPrototype , 'getServerRequestHandler' , makeWrappedHandlerGetter ) ;
97-
98- // TODO replace with an env var, since at this point we don't have a value yet
99- return closure . projectRootDir ;
10099}
101100
102101/**
@@ -121,17 +120,14 @@ function makeWrappedHandlerGetter(origHandlerGetter: HandlerGetter): WrappedHand
121120 logger . error ( `[Sentry] Could not initialize SDK. Received error:\n${ err } ` ) ;
122121 }
123122
124- // TODO: Replace projectRootDir with env variables
125- closure . projectRootDir = this . server . dir ;
126- closure . server = this . server ;
127- closure . publicDir = this . server . publicDir ;
128-
129- const serverPrototype = Object . getPrototypeOf ( this . server ) ;
123+ // stash this in the closure so that `makeWrappedReqHandler` can use it
124+ liveServer = this . server ;
125+ const serverPrototype = Object . getPrototypeOf ( liveServer ) ;
130126
131- // wrap for error capturing (`logError` gets called by `next` for all server-side errors)
127+ // Wrap for error capturing (`logError` gets called by `next` for all server-side errors)
132128 fill ( serverPrototype , 'logError' , makeWrappedErrorLogger ) ;
133129
134- // wrap for request transaction creation (`handleRequest` is called for all incoming requests, and dispatches them
130+ // Wrap for request transaction creation (`handleRequest` is called for all incoming requests, and dispatches them
135131 // to the appropriate handlers)
136132 fill ( serverPrototype , 'handleRequest' , makeWrappedReqHandler ) ;
137133
@@ -171,8 +167,6 @@ function makeWrappedErrorLogger(origErrorLogger: ErrorLogger): WrappedErrorLogge
171167 * @returns A wrapped version of that handler
172168 */
173169function makeWrappedReqHandler ( origReqHandler : ReqHandler ) : WrappedReqHandler {
174- const liveServer = closure . server as Server ;
175-
176170 // inspired by next's public file routing; see
177171 // https://github.com/vercel/next.js/blob/4443d6f3d36b107e833376c2720c1e206eee720d/packages/next/next-server/server/next-server.ts#L1166
178172 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 // TODO body/data
0 commit comments