@@ -11,11 +11,15 @@ import * as Sentry from '../index.server';
11
11
// eslint-disable-next-line @typescript-eslint/no-explicit-any
12
12
type PlainObject < T = any > = { [ key : string ] : T } ;
13
13
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
14
16
interface NextServer {
15
17
server : Server ;
16
18
createServer : ( options : PlainObject ) => Server ;
17
19
}
18
20
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
19
23
interface Server {
20
24
dir : string ;
21
25
publicDir : string ;
@@ -47,17 +51,37 @@ const closure: PlainObject = {};
47
51
let sdkSetupComplete = false ;
48
52
49
53
/**
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`.
52
56
*
53
57
* @returns The absolute path of the project root directory
54
58
*
55
59
*/
56
60
export 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`
58
77
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 ( { } ) ) ;
61
85
fill ( nextServerPrototype , 'getServerRequestHandler' , makeWrappedHandlerGetter ) ;
62
86
63
87
// 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
93
117
94
118
const serverPrototype = Object . getPrototypeOf ( this . server ) ;
95
119
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)
97
121
fill ( serverPrototype , 'logError' , makeWrappedErrorLogger ) ;
98
122
123
+ // wrap for request transaction creation (`handleRequest` is called for all incoming requests, and dispatches them
124
+ // to the appropriate handlers)
99
125
fill ( serverPrototype , 'handleRequest' , makeWrappedReqHandler ) ;
100
126
101
127
sdkSetupComplete = true ;
0 commit comments