@@ -11,11 +11,15 @@ import * as Sentry from '../index.server';
1111// eslint-disable-next-line @typescript-eslint/no-explicit-any
1212type PlainObject < T = any > = { [ key : string ] : T } ;
1313
14+ // class used by `next` as a proxy to the real server; see
15+ // https://github.com/vercel/next.js/blob/4443d6f3d36b107e833376c2720c1e206eee720d/packages/next/server/next.ts#L32
1416interface NextServer {
1517 server : Server ;
1618 createServer : ( options : PlainObject ) => Server ;
1719}
1820
21+ // `next`'s main server class; see
22+ // https://github.com/vercel/next.js/blob/4443d6f3d36b107e833376c2720c1e206eee720d/packages/next/next-server/server/next-server.ts#L132
1923interface Server {
2024 dir : string ;
2125 publicDir : string ;
@@ -47,17 +51,37 @@ const closure: PlainObject = {};
4751let sdkSetupComplete = false ;
4852
4953/**
50- * Do the monkeypatching and wrapping necessary to catch errors in page routes. Along the way, as a bonus, grab ( and
51- * return) the path of the project root, for use in `RewriteFrames`.
54+ * Do the monkeypatching and wrapping necessary to catch errors in page routes and record transactions for both page and
55+ * API routes. Along the way, as a bonus, grab (and return) the path of the project root, for use in `RewriteFrames`.
5256 *
5357 * @returns The absolute path of the project root directory
5458 *
5559 */
5660export function instrumentServer ( ) : string {
57- const nextServerPrototype = Object . getPrototypeOf ( createNextServer ( { } ) ) ;
61+ // The full implementation here involves a lot of indirection and multiple layers of callbacks and wrapping, and is
62+ // therefore potentially a little hard to follow. Here's the overall idea:
63+
64+ // Next.js uses two server classes, `NextServer` and `Server`, with the former proxying calls to the latter, which
65+ // then does the all real work. The only access we have to either is through Next's default export,
66+ // `createNextServer()`, which returns a `NextServer` instance.
67+
68+ // At server startup:
69+ // `next.config.js` imports SDK -> SDK index.ts -> `instrumentServer()` (the function we're in right now) ->
70+ // `createNextServer()` -> `NextServer` instance -> `NextServer` prototype -> wrap
71+ // `NextServer.getServerRequestHandler()`, purely to get us to the next step
72+
73+ // At time of first request:
74+ // Wrapped `getServerRequestHandler` runs for the first time -> live `NextServer` instance (via `this`) -> live
75+ // `Server` instance -> `Server` prototype -> wrap `Server.logError` and `Server.handleRequest` methods, then pass
76+ // wrapped version of `handleRequest` to caller of `getServerRequestHandler`
5877
59- // wrap this getter because it runs before the request handler runs, which gives us a chance to wrap the logger before
60- // it's called for the first time
78+ // Whenever caller of `NextServer.getServerRequestHandler` calls the wrapped `Server.handleRequest`:
79+ // Trace request
80+
81+ // Whenever something calls the wrapped `Server.logError`:
82+ // Capture error
83+
84+ const nextServerPrototype = Object . getPrototypeOf ( createNextServer ( { } ) ) ;
6185 fill ( nextServerPrototype , 'getServerRequestHandler' , makeWrappedHandlerGetter ) ;
6286
6387 // TODO replace with an env var, since at this point we don't have a value yet
@@ -93,9 +117,11 @@ function makeWrappedHandlerGetter(origHandlerGetter: HandlerGetter): WrappedHand
93117
94118 const serverPrototype = Object . getPrototypeOf ( this . server ) ;
95119
96- // wrap the logger so we can capture errors in page-level functions like `getServerSideProps`
120+ // wrap for error capturing (`logError` gets called by `next` for all server-side errors)
97121 fill ( serverPrototype , 'logError' , makeWrappedErrorLogger ) ;
98122
123+ // wrap for request transaction creation (`handleRequest` is called for all incoming requests, and dispatches them
124+ // to the appropriate handlers)
99125 fill ( serverPrototype , 'handleRequest' , makeWrappedReqHandler ) ;
100126
101127 sdkSetupComplete = true ;
0 commit comments