@@ -2,7 +2,9 @@ import { isAsyncIterable, Plugin } from '@envelop/core';
2
2
import { GraphQLError , ResponsePath } from 'graphql' ;
3
3
import { google , Trace } from 'apollo-reporting-protobuf' ;
4
4
5
- interface ApolloInlineTracer {
5
+ const ctxKey = Symbol ( 'ApolloInlineTracePluginContextKey' ) ;
6
+
7
+ interface ApolloInlineTracePluginContext {
6
8
startHrTime : [ number , number ] ;
7
9
rootNode : Trace . Node ;
8
10
trace : Trace ;
@@ -45,64 +47,68 @@ export interface ApolloInlineTracePluginOptions<PluginContext extends Record<str
45
47
export function useApolloInlineTrace < PluginContext extends Record < string , any > = { } > ( {
46
48
shouldTrace,
47
49
rewriteError,
48
- } : ApolloInlineTracePluginOptions < PluginContext > ) : Plugin < PluginContext > {
49
- const tracerForCtx = new WeakMap < PluginContext , ApolloInlineTracer > ( ) ;
50
+ } : ApolloInlineTracePluginOptions < PluginContext > ) : Plugin <
51
+ PluginContext & { [ ctxKey ] : ApolloInlineTracePluginContext }
52
+ > {
50
53
return {
51
- onEnveloped ( { context } ) {
54
+ onEnveloped ( { context, extendContext } ) {
52
55
if ( ! context ) {
53
56
throw new Error ( "Context must be set for Apollo's inline tracing plugin" ) ;
54
57
}
55
58
56
59
if ( shouldTrace ( context ) ) {
57
60
const startHrTime = process . hrtime ( ) ;
58
61
const rootNode = new Trace . Node ( ) ;
59
- tracerForCtx . set ( context , {
60
- startHrTime,
61
- rootNode,
62
- trace : new Trace ( {
63
- root : rootNode ,
64
- fieldExecutionWeight : 1 , // Why 1? See: https://github.com/apollographql/apollo-server/blob/9389da785567a56e989430962564afc71e93bd7f/packages/apollo-server-core/src/plugin/traceTreeBuilder.ts#L16-L23
65
- startTime : nowTimestamp ( ) ,
66
- } ) ,
67
- nodes : new Map ( [ [ responsePathToString ( ) , rootNode ] ] ) ,
68
- stopped : false ,
62
+ extendContext ( {
63
+ ...context ,
64
+ [ ctxKey ] : {
65
+ startHrTime,
66
+ rootNode,
67
+ trace : new Trace ( {
68
+ root : rootNode ,
69
+ fieldExecutionWeight : 1 , // Why 1? See: https://github.com/apollographql/apollo-server/blob/9389da785567a56e989430962564afc71e93bd7f/packages/apollo-server-core/src/plugin/traceTreeBuilder.ts#L16-L23
70
+ startTime : nowTimestamp ( ) ,
71
+ } ) ,
72
+ nodes : new Map ( [ [ responsePathToString ( ) , rootNode ] ] ) ,
73
+ stopped : false ,
74
+ } ,
69
75
} ) ;
70
76
}
71
77
} ,
72
78
onResolverCalled ( { context, info } ) {
73
- const tracer = tracerForCtx . get ( context ) ;
74
- if ( ! tracer ) return ;
79
+ const ctx = context [ ctxKey ] ;
80
+ if ( ! ctx ) return ;
75
81
76
- // result was already shipped (see ApolloInlineTracer .stopped)
77
- if ( tracer . stopped ) {
82
+ // result was already shipped (see ApolloInlineTracePluginContext .stopped)
83
+ if ( ctx . stopped ) {
78
84
return ( ) => {
79
85
// noop
80
86
} ;
81
87
}
82
88
83
- const node = newTraceNode ( tracer , info . path ) ;
89
+ const node = newTraceNode ( ctx , info . path ) ;
84
90
node . type = info . returnType . toString ( ) ;
85
91
node . parentType = info . parentType . toString ( ) ;
86
- node . startTime = hrTimeToDurationInNanos ( process . hrtime ( tracer . startHrTime ) ) ;
92
+ node . startTime = hrTimeToDurationInNanos ( process . hrtime ( ctx . startHrTime ) ) ;
87
93
if ( typeof info . path . key === 'string' && info . path . key !== info . fieldName ) {
88
94
// field was aliased, send the original field name too
89
95
node . originalFieldName = info . fieldName ;
90
96
}
91
97
92
98
return ( ) => {
93
- node . endTime = hrTimeToDurationInNanos ( process . hrtime ( tracer . startHrTime ) ) ;
99
+ node . endTime = hrTimeToDurationInNanos ( process . hrtime ( ctx . startHrTime ) ) ;
94
100
} ;
95
101
} ,
96
102
onParse ( ) {
97
103
return ( { context, result } ) => {
98
- const tracer = tracerForCtx . get ( context ) ;
99
- if ( ! tracer ) return ;
104
+ const ctx = context [ ctxKey ] ;
105
+ if ( ! ctx ) return ;
100
106
101
107
if ( result instanceof GraphQLError ) {
102
- handleErrors ( tracer , [ result ] , rewriteError ) ;
108
+ handleErrors ( ctx , [ result ] , rewriteError ) ;
103
109
} else if ( result instanceof Error ) {
104
110
handleErrors (
105
- tracer ,
111
+ ctx ,
106
112
[
107
113
new GraphQLError ( result . message , {
108
114
originalError : result ,
@@ -116,16 +122,16 @@ export function useApolloInlineTrace<PluginContext extends Record<string, any> =
116
122
onValidate ( ) {
117
123
return ( { context, result : errors } ) => {
118
124
if ( errors . length ) {
119
- const tracer = tracerForCtx . get ( context ) ;
120
- if ( tracer ) handleErrors ( tracer , errors , rewriteError ) ;
125
+ const ctx = context [ ctxKey ] ;
126
+ if ( ctx ) handleErrors ( ctx , errors , rewriteError ) ;
121
127
}
122
128
} ;
123
129
} ,
124
130
onExecute ( ) {
125
131
return {
126
132
onExecuteDone ( { args : { contextValue } , result, setResult } ) {
127
- const tracer = tracerForCtx . get ( contextValue ) ;
128
- if ( ! tracer ) return ;
133
+ const ctx = contextValue [ ctxKey ] ;
134
+ if ( ! ctx ) return ;
129
135
130
136
// TODO: should handle streaming results? how?
131
137
if ( isAsyncIterable ( result ) ) return ;
@@ -135,17 +141,17 @@ export function useApolloInlineTrace<PluginContext extends Record<string, any> =
135
141
}
136
142
137
143
if ( result . errors ?. length ) {
138
- handleErrors ( tracer , result . errors , rewriteError ) ;
144
+ handleErrors ( ctx , result . errors , rewriteError ) ;
139
145
}
140
146
141
147
// onResultProcess will be called only once since we disallow async iterables
142
- if ( tracer . stopped ) throw new Error ( 'Trace stopped multiple times' ) ;
148
+ if ( ctx . stopped ) throw new Error ( 'Trace stopped multiple times' ) ;
143
149
144
- tracer . stopped = true ;
145
- tracer . trace . durationNs = hrTimeToDurationInNanos ( process . hrtime ( tracer . startHrTime ) ) ;
146
- tracer . trace . endTime = nowTimestamp ( ) ;
150
+ ctx . stopped = true ;
151
+ ctx . trace . durationNs = hrTimeToDurationInNanos ( process . hrtime ( ctx . startHrTime ) ) ;
152
+ ctx . trace . endTime = nowTimestamp ( ) ;
147
153
148
- const encodedUint8Array = Trace . encode ( tracer . trace ) . finish ( ) ;
154
+ const encodedUint8Array = Trace . encode ( ctx . trace ) . finish ( ) ;
149
155
const encodedBuffer = Buffer . from (
150
156
encodedUint8Array ,
151
157
encodedUint8Array . byteOffset ,
@@ -211,14 +217,14 @@ function responsePathToString(path?: ResponsePath): string {
211
217
return res ;
212
218
}
213
219
214
- function ensureParentTraceNode ( ctx : ApolloInlineTracer , path : ResponsePath ) : Trace . Node {
220
+ function ensureParentTraceNode ( ctx : ApolloInlineTracePluginContext , path : ResponsePath ) : Trace . Node {
215
221
const parentNode = ctx . nodes . get ( responsePathToString ( path . prev ) ) ;
216
222
if ( parentNode ) return parentNode ;
217
223
// path.prev isn't undefined because we set up the root path in ctx.nodes
218
224
return newTraceNode ( ctx , path . prev ! ) ;
219
225
}
220
226
221
- function newTraceNode ( ctx : ApolloInlineTracer , path : ResponsePath ) {
227
+ function newTraceNode ( ctx : ApolloInlineTracePluginContext , path : ResponsePath ) {
222
228
const node = new Trace . Node ( ) ;
223
229
const id = path . key ;
224
230
if ( typeof id === 'number' ) {
@@ -233,7 +239,7 @@ function newTraceNode(ctx: ApolloInlineTracer, path: ResponsePath) {
233
239
}
234
240
235
241
function handleErrors (
236
- ctx : ApolloInlineTracer ,
242
+ ctx : ApolloInlineTracePluginContext ,
237
243
errors : readonly GraphQLError [ ] ,
238
244
rewriteError : ApolloInlineTracePluginOptions [ 'rewriteError' ]
239
245
) {
0 commit comments