def _nr_stack_context_wrap_wrapper_(wrapped, instance, args, kwargs): def _args(fn, *args, **kwargs): return fn transaction = retrieve_current_transaction() if not transaction: return wrapped(*args, **kwargs) request = retrieve_transaction_request(transaction) if request is None: return wrapped(*args, **kwargs) fn = _args(*args, **kwargs) # We need to filter out certain out types of wrapped functions. # Tornado 3.1 doesn't use _StackContextWrapper and checks for a # '_wrapped' attribute instead which makes this a bit more fragile. if hasattr(module_stack_context, '_StackContextWrapper'): if (fn is None or fn.__class__ is module_stack_context._StackContextWrapper): return fn else: if fn is None or hasattr(fn, '_wrapped'): return fn fn = FunctionWrapper(fn, _nr_stack_context_wrap_wrapped_(request)) return wrapped(fn)
def template_generate_wrapper(wrapped, instance, args, kwargs): transaction = retrieve_current_transaction() if transaction is None: return wrapped(*args, **kwargs) with FunctionTrace(transaction, name=instance.name, group='Template/Render'): return wrapped(*args, **kwargs)
def _nr_wrapper_Application___call__wsgi_(wrapped, instance, args, kwargs): # This variant of the Application.__call__() wrapper is used when it # is believed that we are being called via the WSGI application # adapter. That is, someone is trying to use the blocking subset of # the Tornado ASYNC APIs in a WSGI application. It is required that # there is already a current active transaction at this point. transaction = retrieve_current_transaction() with FunctionTrace(transaction, name='Request/Process', group='Python/Tornado'): return wrapped(*args, **kwargs)
def _nr_wrapper_Application___call__body_(wrapped, instance, args, kwargs): # This variant of the Application.__call__() wrapper is used when it # is believed that we are being called for a HTTP request where # there is request content. There should already be a transaction # associated with the Tornado request object and also a current # active transaction. transaction = retrieve_current_transaction() with FunctionTrace(transaction, name='Request/Process', group='Python/Tornado'): return wrapped(*args, **kwargs)
def _nr_wrapper_RequestHandler__handle_request_exception_( wrapped, instance, args, kwargs): # The RequestHandler._handle_request_exception() method is called # with the details of any unhandled exception. It is believed to # always be called in the context of an except block and so we can # safely use sys.exc_info() to get the actual details. transaction = retrieve_current_transaction() if transaction is not None: record_exception(transaction, sys.exc_info()) return wrapped(*args, **kwargs)
def _nr_wrapper_Task_start_(wrapped, instance, args, kwargs): # This wrapper is used around the start() method of the Task class. # The Task class executes a callback and we track that as a function # trace, naming it after the function the Task holds. transaction = retrieve_current_transaction() if transaction is None: return wrapped(*args, **kwargs) name = callable_name(instance.func) with FunctionTrace(transaction, name): return wrapped(*args, **kwargs)
def _nr_wrapper_Application___call___(wrapped, instance, args, kwargs): # The Application.__call__() method can be called in a number of # different circumstances. # # The first is that it is being called in the context of a WSGI # application via the WSGI adapter. There should be no transaction # associated with the Tornado request object, but there would be # a current active transaction. # # The second is that it is called for a HTTP request by the Tornado # HTTP server where there is no request content. This would occur # from the HTTPConnection._on_headers() method. There should be no # transaction associated with the Tornado request object and also # no current active transaction. # # The third and final one is where it can be called for a HTTP # request by the Tornado HTTP server where there is request content. # This would occur from the HTTPConnection._on_request_body() # method. There should be a transaction associated with the request # object and also a current active transaction. # # The key method that __call__() in turn calls is the _execute() # method of the target RequestHandler. def _params(request, *args, **kwargs): return request request = _params(*args, **kwargs) # Check first for the case where we are called via a WSGI adapter. # The presumption here is that there can be no ASYNC callbacks. transaction = retrieve_request_transaction(request) if transaction is None and retrieve_current_transaction(): return _nr_wrapper_Application___call__wsgi_(wrapped, instance, args, kwargs) # Now check for where we are being called on a HTTP request where # there is no request content. if transaction is None: return _nr_wrapper_Application___call__no_body_( wrapped, instance, args, kwargs) # Finally have case where being called on a HTTP request where there # is request content. return _nr_wrapper_Application___call__body_(wrapped, instance, args, kwargs)
def _nr_wrapper_WSGIContainer___call___(wrapped, instance, args, kwargs): # This wrapper is used around the call which processes the complete # WSGI application execution. We do not have to deal with WSGI # itererable responses as that is all handled within the wrapped # function. # # We have to deal with a few cases in this wrapper, made more # complicated based on whether WSGIContainer is used in conjunction # with a FallbackHandler or whether it is used directly with a # HTTPServer instance. # # If WSGIContainer is used directly with a HTTPServer instance, if # we are called when there is no request content, there will be no # active transaction. If we are called when there is request content # then there will be an active transaction. # # If WSGIContainer is used in conjunction with a FallbackHandler # then there will always be an active transaction whether or not # there is any request content. We treat this case the same is if # there was request content even though there may not have been. def _params(request, *args, **kwargs): return request request = _params(*args, **kwargs) # We need to check to see if an existing transaction object has # already been created for the request. If there isn't one but there # is an active transaction, something is not right and we don't do # anything. transaction = retrieve_request_transaction(request) if transaction is None and retrieve_current_transaction(): return wrapped(*args, **kwargs) # Now check for where we are being called on a HTTP request where # there is no request content. This will only be where used with # HTTPServer directly. if transaction is None: return _nr_wrapper_WSGIContainer___call__no_body_(wrapped, instance, args, kwargs) # Finally have case where being called on a HTTP request where there # is request content and used directly with HTTPServer or where being # used with FallbackHandler. return _nr_wrapper_WSGIContainer___call__body_(wrapped, instance, args, kwargs)
def __call__(self, environ, start_response): transaction = retrieve_current_transaction() if transaction is None: return self.wsgi_application(environ, start_response) name = callable_name(self.wsgi_application) with FunctionTrace(transaction, name='Application', group='Python/WSGI'): with FunctionTrace(transaction, name=name): result = self.wsgi_application(environ, start_response) return _WSGIApplicationIterable(transaction, result)
def _nr_wrapper_RequestHandler__generate_headers_(wrapped, instance, args, kwargs): # The RequestHandler._generate_headers() method is where the # response headers are injected into the response being written back # to the client. We process the response headers before being sent # to capture any details from them, but we also inject our own # additional headers for support cross process transactions etc. transaction = retrieve_current_transaction() if transaction is None: return wrapped(*args, **kwargs) # Thread utilization doesn't make sense in the context of Tornado # so we need to stop anything being generated for it. transaction._thread_utilization_start = None # The HTTPHeaders class with get_all() only started to be used in # Tornado 3.0. For older versions have to fall back to combining the # dictionary and list of headers. The get_status() method only got # added in Tornado 1.2. if hasattr(instance, 'get_status'): status = '%d ???' % instance.get_status() else: status = '%d ???' % instance._status_code try: response_headers = instance._headers.get_all() except AttributeError: try: response_headers = itertools.chain(instance._headers.items(), instance._list_headers) except AttributeError: response_headers = itertools.chain(instance._headers.items(), instance._headers) additional_headers = transaction.process_response(status, response_headers, *args) for name, value in additional_headers: instance.add_header(name, value) return wrapped(*args, **kwargs)
def _nr_wrapper_RequestHandler__execute_(wrapped, instance, args, kwargs): # Prior to Tornado 3.1, the calling of the handler request method # was performed from within RequestHandler._execute(). Any prepare() # method was called immediately and could not be a coroutine. For # later versions of Tornado, if the prepare() method is a coroutine # and the future cannot be completed immediately, then the handler # request method will be called from _execute_method() instead when # prepare() completes. handler = instance request = handler.request # Check to see if we are being called within the context of any sort # of transaction. If we aren't, then we don't bother doing anything and # just call the wrapped function. transaction = retrieve_current_transaction() if transaction is None: return wrapped(*args, **kwargs) # If the method isn't one of the supported ones, then we expect the # wrapped method to raise an exception for HTTPError(405). Name the # transaction after the wrapped method first so it is used if that # occurs. name = callable_name(wrapped) transaction.set_transaction_name(name) if request.method not in handler.SUPPORTED_METHODS: return wrapped(*args, **kwargs) # Otherwise we name the transaction after the handler function that # should end up being executed for the request. We don't create a # function trace node at this point as that is handled by the fact # that we wrapped the exposed methods from the wrapper for the # constructor of the request handler. name = callable_name(getattr(handler, request.method.lower())) transaction.set_transaction_name(name) # Call the original RequestHandler._execute(). So long as the # prepare() method is not a coroutine which doesn't complete # straight away, then the actual handler function handler should # also be called at this point. return wrapped(*args, **kwargs)
def _nr_wrapper_IOLoop_add_callback_(wrapped, instance, args, kwargs): transaction = retrieve_current_transaction() if transaction is None: return wrapped(*args, **kwargs) name = callable_name(wrapped) def _args(callback, *args, **kwargs): return callback callback = _args(*args, **kwargs) params = dict(callback=callable_name(callback)) with FunctionTrace(transaction, name, params=params, terminal=True): return wrapped(*args, **kwargs)
def _nr_wrapper_RequestHandler_render_(wrapped, instance, args, kwargs): # Intended to track time spent rendering response content, but # adding in the wrapper causes Tornado's calculation of where # templates are stored to fail as it walks the stack and looks at # where the calling code was stored and assumes templates exist in # that directory if an absolute path is not provided. Thus not being # used for now. transaction = retrieve_current_transaction() if transaction is None: return wrapped(*args, **kwargs) name = callable_name(wrapped) with FunctionTrace(transaction, name=name): return wrapped(*args, **kwargs)
def _nr_wrapper_gen_Runner___init___(wrapped, instance, args, kwargs): # This wrapper intercepts the initialisation function of the Runner # class and wraps the generator. transaction = retrieve_current_transaction() if transaction is None: return wrapped(*args, **kwargs) def _params(gen, *args, **kwargs): return gen, args, kwargs generator, _args, _kwargs = _params(*args, **kwargs) generator = _nr_wrapper_gen_coroutine_generator_(generator) return wrapped(generator, *_args, **_kwargs)
def _nr_stack_context_function_wrapper_(wrapped, instance, args, kwargs): # We can come in here with either an active transaction # or a request with a transaction bound to it. If there # is an active transaction then call the wrapped function # within function trace and return immediately. transaction = retrieve_current_transaction() if transaction is not None: name = callable_name(wrapped) with FunctionTrace(transaction, name=name): return wrapped(*args, **kwargs) # For the case of a request with a transaction bound to # it, we need to resume the transaction and then call it. # As we resumed the transaction, need to handle # suspending or finalizing it. transaction = resume_request_monitoring(request) if transaction is None: return wrapped(*args, **kwargs) try: name = callable_name(wrapped) with FunctionTrace(transaction, name=name): return wrapped(*args, **kwargs) except: # Catch all. record_exception(transaction, sys.exc_info()) raise finally: if not request_finished(request): suspend_request_monitoring(request, name='Callback/Wait') elif not request.connection.stream.writing(): finalize_request_monitoring(request) else: suspend_request_monitoring(request, name='Request/Output')
def _nr_wrapper_WSGIContainer___call__body_(wrapped, instance, args, kwargs): # This variant of the WSGIContainer.__call__() wrapper is used when # being used with HTTPServer directly and it is believed that we are # being called for a HTTP request where there is request content. # This would also be used where FallbackHandler was being used. # There should already be a transaction associated with the Tornado # request object and also a current active transaction. def _params(request, *args, **kwargs): return request request = _params(*args, **kwargs) transaction = retrieve_current_transaction() transaction.set_transaction_name(request.uri, 'Uri', priority=1) with FunctionTrace(transaction, name='Request/Process', group='Python/Tornado'): return wrapped(*args, **kwargs)
def _nr_wrapper_gen_coroutine_function_(wrapped, instance, args, kwargs): # This wrapper is applied outside of any instance of the gen.engine # and gen.coroutine decorators. We record the call as a function # trace, flagging it as a special coroutine instance. This also # works on conjunction with the wrapper on the Runner class, which # applies a wrapper to the generator when passed to the generator. # When this wrapped function is called, if the result of the # function that gen.engine or gen.coroutine calls is a generator, # the Runner is created and run. If running it, it will always call # into the generator at least once. On that first time the wrapper # for the generator will see the active transaction and can keep a # reference to it to allow the transaction to be reinstated across # calls of the generator to yield an item. transaction = retrieve_current_transaction() if transaction is None: return wrapped(*args, **kwargs) name = callable_name(wrapped) with FunctionTrace(transaction, '%s (coroutine)' % name): return wrapped(*args, **kwargs)
def _nr_wrapper_RequestHandler_on_connection_close(wrapped, instance, args, kwargs): # The RequestHandler.on_connection_close() method is called when the # client closes the connection prematurely before the request had # been completed. The callback itself wasn't registered at a point # where there was any request tracking so there shouldn't be any # active transaction when this is called. We track the call of the # wrapped method and then finalize the whole transaction. transaction = retrieve_current_transaction() if transaction: return wrapped(*args, **kwargs) handler = instance request = handler.request transaction = resume_request_monitoring(request) if transaction is None: return wrapped(*args, **kwargs) name = callable_name(wrapped) try: with FunctionTrace(transaction, name): result = wrapped(*args, **kwargs) except: # Catch all finalize_request_monitoring(request, *sys.exc_info()) raise else: finalize_request_monitoring(request) return result
def _nr_wrapper_gen_coroutine_generator_(generator): # This wrapper is applied around the generator returned by any # function which was wrapped by gen.engine or gen.coroutine. This is # to allows us to track separately each call back into the # generator. The first time we are called we should be in the # context of an active transaction. We need to cache that so we can # reinstate the transaction each time we return back into the # generator. We also reach back to the active node of the transaction # to get the name to be used for each function trace created when # the generator is re-entered. try: value = None exc = None active_transaction = retrieve_current_transaction() transaction = None request = None name = None # Cache the request object associated with the active # transaction. If there is a request, then calculate the name # for tracking each call into the generator from the current # active node. That node should be the function trace node added # when our wrapper around gen.engine or gen.coroutine was # called. request = (active_transaction is not None and retrieve_transaction_request(active_transaction)) if request is not None: active_node = active_transaction.active_node() if hasattr(active_node, 'name'): active_name = active_node.name if active_name.endswith(' (coroutine)'): name = active_name.replace(' (coroutine)', ' (yield)') while True: # The first time in the loop we should already have # inherited an active transaction. On subsequent times # however there may not be, in which case we need to resume # the transaction associated with the request. We need to # remember if we resumed the transaction as we need to # then make sure we suspend it again as the caller isn't # going to do that for us. suspend = None if name is not None: if retrieve_current_transaction() is None: transaction = resume_request_monitoring(request) suspend = transaction else: transaction = active_transaction # The following code sits between the consumer of the # generator and the generator itself. It will add a function # trace around each call into the generator to yield a # value. Annotate the function trace with the location of # the code within the generator which will be executed. try: params = {} gi_frame = generator.gi_frame params['filename'] = gi_frame.f_code.co_filename params['lineno'] = gi_frame.f_lineno with FunctionTrace(transaction, name, params=params): try: if exc is not None: yielded = generator.throw(*exc) exc = None else: yielded = generator.send(value) except (GeneratorReturn, StopIteration): raise except: # Catch all. # We need to record exceptions at this point # as the call back into the generator could # have been triggered by a future direct from # the main loop. There isn't therefore anywhere # else it can be captured. if transaction is not None: record_exception(transaction, sys.exc_info()) raise finally: if suspend is not None: suspend_request_monitoring(request, name='Callback/Wait') # XXX This could present a problem if we are yielding a # future as the future will be scheduled outside of the # context of the active transaction if we had to do a # suspend of the transaction since we resumed it. We can't # do the suspend after the yield as it is during the yield # that control is returned back to the main loop. try: value = yield yielded except Exception: exc = sys.exc_info() finally: generator.close()
def _nr_wrapper_HTTPConnection__finish_request(wrapped, instance, args, kwargs): # Normally called when the request is all complete meaning that we # have to finalize our own transaction. We may actually enter here # with the transaction already being the current one. connection = instance request = connection._request transaction = retrieve_request_transaction(request) # The wrapped function could be called more than once. If it is then # the transaction should already have been completed. In this case # the transaction should be None. To be safe also check whether the # request itself was already flagged as finished. If transaction was # the same as the current transaction the following check would have # just marked it as finished again, but this first check will cover # where the current transaction is for some reason different. if transaction is None: return wrapped(*args, **kwargs) if request_finished(request): return wrapped(*args, **kwargs) # Deal now with the possibility that the transaction is already the # current active transaction. if transaction == retrieve_current_transaction(): finish_request_monitoring(request) return wrapped(*args, **kwargs) # If we enter here with an active transaction and it isn't the one # we expect, then not sure what we should be doing, so simply # return. This should hopefully never occur. if retrieve_current_transaction() is not None: return wrapped(*args, **kwargs) # Not the current active transaction and so we need to try and # resume the transaction associated with the request. transaction = resume_request_monitoring(request) if transaction is None: return wrapped(*args, **kwargs) finish_request_monitoring(request) try: result = wrapped(*args, **kwargs) except: # Catch all # There should never be an error from wrapped function but # in case there is, try finalizing transaction. finalize_request_monitoring(request, *sys.exc_info()) raise finalize_request_monitoring(request) return result
def _nr_wrapper_HTTPConnection__on_headers_(wrapped, instance, args, kwargs): # This is the first point at which we should ever be called for a # request. It is called when the request headers have been read in. # The next phase would be to read in any request content but we # can't tell whether that will happen or not at this point. We do # need to setup a callback when connection is closed due to client # disconnecting. assert instance is not None connection = instance # Check to see if we are being called within the context of any sort # of transaction. This should never occur, but if we are, then we # don't bother doing anything and just call the wrapped function # immediately as can't be sure what else to do. transaction = retrieve_current_transaction() if transaction is not None: _logger.error( 'Runtime instrumentation error. Starting a new ' 'Tornado web request but there is a transaction active ' 'already. Report this issue to New Relic support.\n%s', ''.join(traceback.format_stack()[:-1])) last = last_transaction_activation() if last is not None: _logger.info( 'The currently active transaction was possibly ' 'initiated or resumed from %r.', last) return wrapped(*args, **kwargs) # Execute the wrapped function as we are only going to do something # after it has been called. result = wrapped(*args, **kwargs) # Check to see if the connection has already been closed or the # request finished. The connection can be closed where request # content length was too big. if connection.stream.closed(): return result if connection._request_finished: return result # Check to see if we have already associated a transaction with the # request. This would happen if there was actually no request # content and so the application was called immediately. We can # return straight away in that case. request = connection._request if request is None: return result transaction = retrieve_request_transaction(request) if transaction is not None: return result # If we get here it is because there was request content which first # had to be read. No transaction should have been created as yet. # Create the transaction but if it is None then it means recording # of transactions is not enabled then do not need to do anything. transaction = initiate_request_monitoring(request) if transaction is None: return result # Add a callback variable to the connection object so that we can # be notified when the connection is closed before all the request # content has been read. This will be invoked from the method # BaseIOStream._maybe_run_close_callback(). def _close(): transaction = resume_request_monitoring(request) if transaction is None: return # Force a function trace to record the fact that the socket # connection was closed due to client disconnection. with FunctionTrace(transaction, name='Request/Close', group='Python/Tornado'): pass # We finish up the transaction and nothing else should occur. finalize_request_monitoring(request) connection.stream._nr_close_callback = _close # Name transaction initially after the wrapped function so that if # the connection is dropped before all the request content is read, # then we don't get metric grouping issues with it being named after # the URL. name = callable_name(wrapped) transaction.set_transaction_name(name) # Now suspend monitoring of current transaction until next callback. suspend_request_monitoring(request, name='Request/Input') return result
def _nr_wrapper_RequestHandler_finish_(wrapped, instance, args, kwargs): # The RequestHandler.finish() method will either be called explicitly # by the user, but called also be called automatically by Tornado. # It is possible that it can be called twice so it is necessary to # protect against that. handler = instance request = handler.request # Bail out out if we think the request as a whole has been completed. if request_finished(request): return wrapped(*args, **kwargs) # Call wrapped method straight away if request object it is being # called on is not even associated with a transaction. If we were in # a running transaction we still want to record the call though. # This will occur when calling finish on another request, but the # target request wasn't monitored. transaction = retrieve_request_transaction(request) active_transaction = retrieve_current_transaction() if transaction is None: if active_transaction is not None: name = callable_name(wrapped) with FunctionTrace(active_transaction, name): return wrapped(*args, **kwargs) else: return wrapped(*args, **kwargs) # If we have an active transaction, we we need to consider two # possiblities. The first is where the current running transaction # doesn't match that bound to the request. For this case it would be # where from within one transaction there is an attempt to call # finish() on a distinct web request which was being monitored. The # second is where finish() is being called for the current request. if active_transaction is not None: if transaction != active_transaction: # For this case we need to suspend the current running # transaction and call ourselves again. When it returns # we need to restore things back the way they were. # We still trace the call in the running transaction # though so the fact that it called finish on another # request is apparent. name = callable_name(wrapped) with FunctionTrace(active_transaction, name): try: active_transaction.drop_transaction() return _nr_wrapper_RequestHandler_finish_( wrapped, instance, args, kwargs) finally: active_transaction.save_transaction() else: # For this case we just trace the call. name = callable_name(wrapped) with FunctionTrace(active_transaction, name): return wrapped(*args, **kwargs) # Attempt to resume the transaction, calling the wrapped method # straight away if there isn't one. Otherwise trace the call. transaction = resume_request_monitoring(request) if transaction is None: return wrapped(*args, **kwargs) try: name = callable_name(wrapped) with FunctionTrace(transaction, name): result = wrapped(*args, **kwargs) except: # Catch all finalize_request_monitoring(request, *sys.exc_info()) raise else: if not request.connection.stream.writing(): finalize_request_monitoring(request) else: suspend_request_monitoring(request, name='Request/Output') return result