1
- import {
2
- ExecutionResult ,
3
- FieldNode ,
4
- GraphQLError ,
5
- Kind ,
6
- OperationDefinitionNode ,
7
- print ,
8
- } from 'graphql' ;
1
+ import { ExecutionResult , FieldNode , getOperationAST , GraphQLError , Kind , print } from 'graphql' ;
2
+ import newRelic from 'newrelic' ;
9
3
import { DefaultContext , getDocumentString , isAsyncIterable , Path , Plugin } from '@envelop/core' ;
10
4
import { useOnResolve } from '@envelop/on-resolve' ;
11
5
12
- enum AttributeName {
6
+ export enum AttributeName {
13
7
COMPONENT_NAME = 'Envelop_NewRelic_Plugin' ,
14
8
ANONYMOUS_OPERATION = '<anonymous>' ,
15
9
EXECUTION_RESULT = 'graphql.execute.result' ,
@@ -40,6 +34,8 @@ export type UseNewRelicOptions = {
40
34
* By default, this plugin skips all `Error` errors and does not report them to NewRelic.
41
35
*/
42
36
skipError ?: ( error : GraphQLError ) => boolean ;
37
+
38
+ shim ?: any ;
43
39
} ;
44
40
45
41
interface InternalOptions extends UseNewRelicOptions {
@@ -64,36 +60,38 @@ export const useNewRelic = (rawOptions?: UseNewRelicOptions): Plugin => {
64
60
} ;
65
61
options . isExecuteVariablesRegex = options . includeExecuteVariables instanceof RegExp ;
66
62
options . isResolverArgsRegex = options . includeResolverArgs instanceof RegExp ;
67
- const instrumentationApi$ = import ( 'newrelic' )
68
- . then ( m => m . default || m )
69
- . then ( ( { shim } ) => {
70
- if ( ! shim ?. agent ) {
71
- throw new Error (
72
- 'Agent unavailable. Please check your New Relic Agent configuration and ensure New Relic is enabled.' ,
73
- ) ;
74
- }
75
- shim . agent . metrics
76
- . getOrCreateMetric ( `Supportability/ExternalModules/${ AttributeName . COMPONENT_NAME } ` )
77
- . incrementCallCount ( ) ;
78
- return shim ;
79
- } ) ;
63
+ const instrumentationApi = rawOptions ?. shim || newRelic ?. shim ;
64
+ if ( ! instrumentationApi ?. agent ) {
65
+ // eslint-disable-next-line no-console
66
+ console . warn (
67
+ 'Agent unavailable. Please check your New Relic Agent configuration and ensure New Relic is enabled.' ,
68
+ ) ;
69
+ return { } ;
70
+ }
71
+ instrumentationApi . agent . metrics
72
+ . getOrCreateMetric ( `Supportability/ExternalModules/${ AttributeName . COMPONENT_NAME } ` )
73
+ . incrementCallCount ( ) ;
80
74
81
- const logger$ = instrumentationApi$ . then ( ( { logger } ) => {
82
- const childLogger = logger . child ( { component : AttributeName . COMPONENT_NAME } ) ;
83
- childLogger . info ( `${ AttributeName . COMPONENT_NAME } registered` ) ;
84
- return childLogger ;
85
- } ) ;
75
+ const logger = instrumentationApi . logger . child ( { component : AttributeName . COMPONENT_NAME } ) ;
76
+ logger . info ( `${ AttributeName . COMPONENT_NAME } registered` ) ;
86
77
87
78
return {
88
79
onPluginInit ( { addPlugin } ) {
89
80
if ( options . trackResolvers ) {
90
81
addPlugin (
91
- useOnResolve ( async ( { args : resolversArgs , info } ) => {
92
- const instrumentationApi = await instrumentationApi$ ;
93
- const transactionNameState = instrumentationApi . agent . tracer . getTransaction ( ) . nameState ;
82
+ useOnResolve ( ( { args : resolversArgs , info } ) => {
83
+ const transaction = instrumentationApi . agent . tracer . getTransaction ( ) ;
84
+ if ( ! transaction ) {
85
+ logger . trace ( 'No transaction found. Not recording resolver.' ) ;
86
+ return ( ) => { } ;
87
+ }
88
+ const transactionNameState = transaction . nameState ;
89
+ if ( ! transactionNameState ) {
90
+ logger . trace ( 'No transaction name state found. Not recording resolver.' ) ;
91
+ return ( ) => { } ;
92
+ }
94
93
const delimiter = transactionNameState . delimiter ;
95
94
96
- const logger = await logger$ ;
97
95
const { returnType, path, parentType } = info ;
98
96
const formattedPath = flattenPath ( path , delimiter ) ;
99
97
const currentSegment = instrumentationApi . getActiveSegment ( ) ;
@@ -102,7 +100,7 @@ export const useNewRelic = (rawOptions?: UseNewRelicOptions): Plugin => {
102
100
'No active segment found at resolver call. Not recording resolver (%s).' ,
103
101
formattedPath ,
104
102
) ;
105
- return ( ) => { } ;
103
+ return ( ) => { } ;
106
104
}
107
105
108
106
const resolverSegment = instrumentationApi . createSegment (
@@ -112,7 +110,7 @@ export const useNewRelic = (rawOptions?: UseNewRelicOptions): Plugin => {
112
110
) ;
113
111
if ( ! resolverSegment ) {
114
112
logger . trace ( 'Resolver segment was not created (%s).' , formattedPath ) ;
115
- return ( ) => { } ;
113
+ return ( ) => { } ;
116
114
}
117
115
resolverSegment . start ( ) ;
118
116
resolverSegment . addAttribute ( AttributeName . RESOLVER_FIELD_PATH , formattedPath ) ;
@@ -138,53 +136,62 @@ export const useNewRelic = (rawOptions?: UseNewRelicOptions): Plugin => {
138
136
) ;
139
137
}
140
138
} ,
141
- async onExecute ( { args } ) {
142
- const instrumentationApi = await instrumentationApi$ ;
143
- const transactionNameState = instrumentationApi . agent . tracer . getTransaction ( ) . nameState ;
144
- const spanContext = instrumentationApi . agent . tracer . getSpanContext ( ) ;
145
- const delimiter = transactionNameState . delimiter ;
146
- const rootOperation = args . document . definitions . find (
147
- // @ts -expect-error TODO: not sure how we will make it dev friendly
148
- definitionNode => definitionNode . kind === Kind . OPERATION_DEFINITION ,
149
- ) as OperationDefinitionNode ;
139
+ onExecute ( { args } ) {
140
+ const rootOperation = getOperationAST ( args . document , args . operationName ) ;
141
+ if ( ! rootOperation ) {
142
+ logger . trace ( 'No root operation found. Not recording transaction.' ) ;
143
+ return ;
144
+ }
150
145
const operationType = rootOperation . operation ;
151
- const document = getDocumentString ( args . document , print ) ;
152
146
const operationName =
153
147
options . extractOperationName ?.( args . contextValue ) ||
154
148
args . operationName ||
155
149
rootOperation . name ?. value ||
156
150
AttributeName . ANONYMOUS_OPERATION ;
157
- let rootFields : string [ ] | null = null ;
158
151
159
- if ( options . rootFieldsNaming ) {
160
- const fieldNodes = rootOperation . selectionSet . selections . filter (
161
- selectionNode => selectionNode . kind === Kind . FIELD ,
162
- ) as FieldNode [ ] ;
163
- rootFields = fieldNodes . map ( fieldNode => fieldNode . name . value ) ;
164
- }
152
+ const transaction = instrumentationApi . agent . tracer . getTransaction ( ) ;
153
+ const transactionNameState = transaction ?. nameState ;
154
+ if ( transactionNameState ) {
155
+ const delimiter = transactionNameState . delimiter || '/' ;
156
+ let rootFields : string [ ] | null = null ;
157
+
158
+ if ( options . rootFieldsNaming ) {
159
+ const fieldNodes = rootOperation . selectionSet . selections . filter (
160
+ selectionNode => selectionNode . kind === Kind . FIELD ,
161
+ ) as FieldNode [ ] ;
162
+ rootFields = fieldNodes . map ( fieldNode => fieldNode . name . value ) ;
163
+ }
165
164
166
- transactionNameState . setName (
167
- transactionNameState . prefix ,
168
- transactionNameState . verb ,
169
- delimiter ,
170
- operationType +
165
+ const operationType = rootOperation . operation ;
166
+
167
+ transactionNameState . setName (
168
+ transactionNameState . prefix ,
169
+ transactionNameState . verb ,
170
+ delimiter ,
171
+ operationType +
171
172
delimiter +
172
173
operationName +
173
174
( rootFields ? delimiter + rootFields . join ( '&' ) : '' ) ,
174
- ) ;
175
+ ) ;
176
+ }
177
+
178
+ const spanContext = instrumentationApi . agent . tracer . getSpanContext ( ) ;
175
179
176
- spanContext . addCustomAttribute ( AttributeName . EXECUTION_OPERATION_NAME , operationName ) ;
177
- spanContext . addCustomAttribute ( AttributeName . EXECUTION_OPERATION_TYPE , operationType ) ;
180
+ spanContext ? .addCustomAttribute ( AttributeName . EXECUTION_OPERATION_NAME , operationName ) ;
181
+ spanContext ? .addCustomAttribute ( AttributeName . EXECUTION_OPERATION_TYPE , operationType ) ;
178
182
options . includeOperationDocument &&
179
- spanContext . addCustomAttribute ( AttributeName . EXECUTION_OPERATION_DOCUMENT , document ) ;
183
+ spanContext ?. addCustomAttribute (
184
+ AttributeName . EXECUTION_OPERATION_DOCUMENT ,
185
+ getDocumentString ( args . document , print ) ,
186
+ ) ;
180
187
181
188
if ( options . includeExecuteVariables ) {
182
189
const rawVariables = args . variableValues || { } ;
183
190
const executeVariablesToTrack = options . isExecuteVariablesRegex
184
191
? filterPropertiesByRegex ( rawVariables , options . includeExecuteVariables as RegExp )
185
192
: rawVariables ;
186
193
187
- spanContext . addCustomAttribute (
194
+ spanContext ? .addCustomAttribute (
188
195
AttributeName . EXECUTION_VARIABLES ,
189
196
JSON . stringify ( executeVariablesToTrack ) ,
190
197
) ;
@@ -196,7 +203,7 @@ export const useNewRelic = (rawOptions?: UseNewRelicOptions): Plugin => {
196
203
onExecuteDone ( { result } ) {
197
204
const sendResult = ( singularResult : ExecutionResult ) => {
198
205
if ( singularResult . data && options . includeRawResult ) {
199
- spanContext . addCustomAttribute (
206
+ spanContext ? .addCustomAttribute (
200
207
AttributeName . EXECUTION_RESULT ,
201
208
JSON . stringify ( singularResult ) ,
202
209
) ;
@@ -206,9 +213,11 @@ export const useNewRelic = (rawOptions?: UseNewRelicOptions): Plugin => {
206
213
const agent = instrumentationApi . agent ;
207
214
const transaction = instrumentationApi . tracer . getTransaction ( ) ;
208
215
209
- for ( const error of singularResult . errors ) {
210
- if ( options . skipError ?.( error ) ) continue ;
211
- agent . errors . add ( transaction , JSON . stringify ( error ) ) ;
216
+ if ( transaction ) {
217
+ for ( const error of singularResult . errors ) {
218
+ if ( options . skipError ?.( error ) ) continue ;
219
+ agent . errors . add ( transaction , JSON . stringify ( error ) ) ;
220
+ }
212
221
}
213
222
}
214
223
} ;
@@ -218,12 +227,12 @@ export const useNewRelic = (rawOptions?: UseNewRelicOptions): Plugin => {
218
227
sendResult ( singularResult ) ;
219
228
} ,
220
229
onEnd : ( ) => {
221
- operationSegment . end ( ) ;
230
+ operationSegment ? .end ( ) ;
222
231
} ,
223
232
} ;
224
233
}
225
234
sendResult ( result ) ;
226
- operationSegment . end ( ) ;
235
+ operationSegment ? .end ( ) ;
227
236
return { } ;
228
237
} ,
229
238
} ;
0 commit comments