1212# See the License for the specific language governing permissions and
1313# limitations under the License.
1414
15- from newrelic .api .error_trace import ErrorTrace , ErrorTraceWrapper
16- from newrelic .api .function_trace import FunctionTrace , FunctionTraceWrapper
15+ from newrelic .api .error_trace import ErrorTrace
16+ from newrelic .api .function_trace import FunctionTrace
1717from newrelic .api .graphql_trace import GraphQLResolverTrace , GraphQLOperationTrace
1818from newrelic .api .time_trace import notice_error , current_trace
19- from newrelic .api .transaction import current_transaction
19+ from newrelic .api .transaction import current_transaction , ignore_transaction
2020from newrelic .common .object_names import callable_name , parse_exc_info
2121from newrelic .common .object_wrapper import function_wrapper , wrap_function_wrapper
2222from newrelic .core .graphql_utils import graphql_statement
2323from collections import deque
2424from copy import copy
2525
2626
27+ GRAPHQL_IGNORED_FIELDS = frozenset (("id" , "__typename" ))
28+ GRAPHQL_INTROSPECTION_FIELDS = frozenset (("__schema" , "__type" ))
29+
30+
2731def graphql_version ():
2832 from graphql import __version__ as version
2933 return tuple (int (v ) for v in version .split ("." ))
@@ -45,7 +49,7 @@ def ignore_graphql_duplicate_exception(exc, val, tb):
4549 _ , _ , fullnames , message = parse_exc_info ((None , val .original_error , None ))
4650 fullname = fullnames [0 ]
4751 for error in transaction ._errors :
48- if error .type == fullname and error . message == message :
52+ if error .message == message :
4953 return True
5054
5155 return None # Follow original exception matching rules
@@ -76,39 +80,48 @@ def bind_operation_v2(exe_context, operation, root_value):
7680
7781
7882def wrap_execute_operation (wrapped , instance , args , kwargs ):
83+ transaction = current_transaction ()
7984 trace = current_trace ()
8085
81- if trace :
86+ if not transaction :
87+ return wrapped (* args , ** kwargs )
88+
89+ try :
90+ operation = bind_operation_v3 (* args , ** kwargs )
91+ except TypeError :
92+ operation = bind_operation_v2 (* args , ** kwargs )
93+
94+ operation_name = operation .name
95+ if hasattr (operation_name , "value" ):
96+ operation_name = operation_name .value
97+ trace .operation_name = operation_name = operation_name or "<anonymous>"
98+
99+ operation_type = operation .operation
100+ if hasattr (operation_type , "name" ):
101+ operation_type = operation_type .name .lower ()
102+ trace .operation_type = operation_type = operation_type or "<unknown>"
103+
104+ if operation .selection_set is not None :
105+ fields = operation .selection_set .selections
82106 try :
83- operation = bind_operation_v3 (* args , ** kwargs )
84- except TypeError :
85- operation = bind_operation_v2 (* args , ** kwargs )
86-
87- operation_name = operation .name
88- if hasattr (operation_name , "value" ):
89- operation_name = operation_name .value
90- trace .operation_name = operation_name
91-
92- operation_type = operation .operation
93- if hasattr (operation_type , "name" ):
94- operation_type = operation_type .name
95- trace .operation_type = operation_type .lower ()
96-
97- if operation .selection_set is not None :
98- fields = operation .selection_set .selections
99- try :
100- deepest_path = traverse_deepest_path (fields )
101- except :
102- deepest_path = []
103- trace .deepest_path = "." .join (deepest_path )
107+ deepest_path = traverse_deepest_path (fields )
108+ except :
109+ deepest_path = []
110+ trace .deepest_path = deepest_path = "." .join (deepest_path ) or "<unknown>"
104111
105- return wrapped (* args , ** kwargs )
112+ transaction .set_transaction_name (callable_name (wrapped ), "GraphQL" , priority = 11 )
113+
114+ result = wrapped (* args , ** kwargs )
115+ transaction_name = "%s/%s/%s" % (operation_type , operation_name , deepest_path )
116+ transaction .set_transaction_name (transaction_name , "GraphQL" , priority = 14 )
117+
118+ return result
106119
107120
108121def traverse_deepest_path (fields ):
109122 inputs = deque (((field , deque (), 0 ) for field in fields ))
110123 deepest_path_len = 0
111- deepest_path = None
124+ deepest_path = []
112125
113126 while inputs :
114127 field , current_path , current_path_len = inputs .pop ()
@@ -117,16 +130,23 @@ def traverse_deepest_path(fields):
117130 if hasattr (field_name , "value" ):
118131 field_name = field_name .value
119132
120- current_path .append (field_name )
121- current_path_len += 1
122-
123- if field .selection_set is None or len (field .selection_set .selections ) == 0 :
124- if deepest_path_len < current_path_len :
125- deepest_path = current_path
126- deepest_path_len = current_path_len
127- else :
128- for inner_field in field .selection_set .selections :
129- inputs .append ((inner_field , copy (current_path ), current_path_len ))
133+ if field_name in GRAPHQL_INTROSPECTION_FIELDS :
134+ # If an introspection query is identified, ignore the transaction completely.
135+ # This matches the logic used in Apollo server for identifying introspection queries.
136+ ignore_transaction ()
137+ return []
138+ elif field_name not in GRAPHQL_IGNORED_FIELDS :
139+ # Only add to the current path for non-ignored values.
140+ current_path .append (field_name )
141+ current_path_len += 1
142+
143+ if field .selection_set is None or len (field .selection_set .selections ) == 0 :
144+ if deepest_path_len < current_path_len :
145+ deepest_path = current_path
146+ deepest_path_len = current_path_len
147+ else :
148+ for inner_field in field .selection_set .selections :
149+ inputs .append ((inner_field , copy (current_path ), current_path_len ))
130150
131151 return deepest_path
132152
@@ -137,12 +157,25 @@ def bind_get_middleware_resolvers(middlewares):
137157
138158def wrap_get_middleware_resolvers (wrapped , instance , args , kwargs ):
139159 middlewares = bind_get_middleware_resolvers (* args , ** kwargs )
140- middlewares = [FunctionTraceWrapper ( ErrorTraceWrapper ( m , ignore = ignore_graphql_duplicate_exception ) ) if not hasattr (m , "_nr_wrapped" ) else m for m in middlewares ]
160+ middlewares = [wrap_middleware ( m ) if not hasattr (m , "_nr_wrapped" ) else m for m in middlewares ]
141161 for m in middlewares :
142162 m ._nr_wrapped = True
143163
144164 return wrapped (middlewares )
145165
166+ @function_wrapper
167+ def wrap_middleware (wrapped , instance , args , kwargs ):
168+ transaction = current_transaction ()
169+ if transaction is None :
170+ return wrapped (* args , ** kwargs )
171+
172+ name = callable_name (wrapped )
173+ transaction .set_transaction_name (name , 'GraphQL' , priority = 12 )
174+ with FunctionTrace (name ):
175+ with ErrorTrace (ignore = ignore_graphql_duplicate_exception ):
176+ return wrapped (* args , ** kwargs )
177+
178+
146179
147180def bind_get_field_resolver (field_resolver ):
148181 return field_resolver
@@ -182,14 +215,11 @@ def wrap_executor_execute(wrapped, instance, args, kwargs):
182215@function_wrapper
183216def wrap_resolver (wrapped , instance , args , kwargs ):
184217 transaction = current_transaction ()
185- # Prevent double wrapping using _nr_wrapped attr
186218 if transaction is None :
187219 return wrapped (* args , ** kwargs )
188-
189- transaction .set_transaction_name (callable_name (wrapped ), priority = 2 )
190- with FunctionTrace (callable_name (wrapped )):
191- with ErrorTrace (ignore = ignore_graphql_duplicate_exception ):
192- return wrapped (* args , ** kwargs )
220+ transaction .set_transaction_name (callable_name (wrapped ), "GraphQL" , priority = 13 )
221+ with ErrorTrace (ignore = ignore_graphql_duplicate_exception ):
222+ return wrapped (* args , ** kwargs )
193223
194224
195225def wrap_error_handler (wrapped , instance , args , kwargs ):
@@ -198,6 +228,13 @@ def wrap_error_handler(wrapped, instance, args, kwargs):
198228
199229
200230def wrap_validate (wrapped , instance , args , kwargs ):
231+ transaction = current_transaction ()
232+ if transaction is None :
233+ return wrapped (* args , ** kwargs )
234+
235+ transaction .set_transaction_name (callable_name (wrapped ),"GraphQL" , priority = 10 )
236+
237+ # Run and collect errors
201238 errors = wrapped (* args , ** kwargs )
202239
203240 # Raise errors and immediately catch them so we can record them
@@ -209,6 +246,15 @@ def wrap_validate(wrapped, instance, args, kwargs):
209246
210247 return errors
211248
249+ def wrap_parse (wrapped , instance , args , kwargs ):
250+ transaction = current_transaction ()
251+ if transaction is None :
252+ return wrapped (* args , ** kwargs )
253+ transaction .set_transaction_name (callable_name (wrapped ), "GraphQL" , priority = 10 )
254+
255+ with ErrorTrace (ignore = ignore_graphql_duplicate_exception ):
256+ return wrapped (* args , ** kwargs )
257+
212258
213259def bind_resolve_field_v3 (parent_type , source , field_nodes , path ):
214260 return parent_type , field_nodes , path
@@ -233,13 +279,14 @@ def wrap_resolve_field(wrapped, instance, args, kwargs):
233279 field_name = field_asts [0 ].name .value
234280
235281 with GraphQLResolverTrace (field_name ) as trace :
236- trace ._add_agent_attribute ("graphql.field.parentType" , parent_type .name )
237- if isinstance (field_path , list ):
238- trace ._add_agent_attribute ("graphql.field.path" , field_path [0 ])
239- else :
240- trace ._add_agent_attribute ("graphql.field.path" , field_path .key )
282+ with ErrorTrace (ignore = ignore_graphql_duplicate_exception ):
283+ trace ._add_agent_attribute ("graphql.field.parentType" , parent_type .name )
284+ if isinstance (field_path , list ):
285+ trace ._add_agent_attribute ("graphql.field.path" , field_path [0 ])
286+ else :
287+ trace ._add_agent_attribute ("graphql.field.path" , field_path .key )
241288
242- return wrapped (* args , ** kwargs )
289+ return wrapped (* args , ** kwargs )
243290
244291
245292def bind_graphql_impl_query (schema , source , * args , ** kwargs ):
@@ -280,10 +327,14 @@ def wrap_graphql_impl(wrapped, instance, args, kwargs):
280327 if hasattr (query , "body" ):
281328 query = query .body
282329
330+ transaction .set_transaction_name (callable_name (wrapped ), "GraphQL" , priority = 10 )
331+
283332 with GraphQLOperationTrace () as trace :
284333 trace .statement = graphql_statement (query )
285334 with ErrorTrace (ignore = ignore_graphql_duplicate_exception ):
286- return wrapped (* args , ** kwargs )
335+ result = wrapped (* args , ** kwargs )
336+ #transaction.set_transaction_name(transaction_name, "GraphQL", priority=14)
337+ return result
287338
288339
289340def instrument_graphql_execute (module ):
@@ -336,8 +387,13 @@ def instrument_graphql_error_located_error(module):
336387def instrument_graphql_validate (module ):
337388 wrap_function_wrapper (module , "validate" , wrap_validate )
338389
390+
339391def instrument_graphql (module ):
340392 if hasattr (module , "graphql_impl" ):
341393 wrap_function_wrapper (module , "graphql_impl" , wrap_graphql_impl )
342394 if hasattr (module , "execute_graphql" ):
343395 wrap_function_wrapper (module , "execute_graphql" , wrap_graphql_impl )
396+
397+
398+ def instrument_graphql_parser (module ):
399+ wrap_function_wrapper (module , "parse" , wrap_parse )
0 commit comments