def _nr_wrapper_WSGIContainer___call__no_body_(wrapped, instance, args, kwargs): # This variant of the WSGIContainer.__call__() wrapper is used when # being used direct with HTTPServer and it is believed that we are # being called for a HTTP request where there is no request content. # There should be no transaction associated with the Tornado request # object and also no current active transaction. Create the # transaction but if it is None then it means recording of # transactions is not enabled then do not need to do anything. def _params(request, *args, **kwargs): return request request = _params(*args, **kwargs) transaction = initiate_request_monitoring(request) if transaction is None: return wrapped(*args, **kwargs) # Call the original method in a trace object to give better context # in transaction traces. It should only every return an exception is # situation where application was being shutdown so finalize the # transaction on any error. transaction.set_transaction_name(request.uri, 'Uri', priority=1) try: with FunctionTrace(transaction, name='Request/Process', group='Python/Tornado'): result = wrapped(*args, **kwargs) except: # Catch all finalize_request_monitoring(request, *sys.exc_info()) raise else: # In the case of the response completing immediately or an # exception occuring, then finish() should have been called on # the request already. We can't just exit the transaction in the # finish() call however as will need to still pop back up # through the above function trace. So if it has been flagged # that it is finished, which Tornado does by setting the request # object in the connection to None, then we exit the transaction # here. Otherwise we setup a function trace to track wait time # for deferred and suspend monitoring. 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') return result
def _nr_wrapper_HTTPConnection__on_request_body_(wrapped, instance, args, kwargs): # Called when there was a request body and it has now all been # read in and buffered ready to call the request handler. assert instance is not None connection = instance request = connection._request # Wipe out our temporary callback for being notified that the # connection is being closed before content is read. connection.stream._nr_close_callback = None # Restore any transaction which may have been suspended. transaction = resume_request_monitoring(request) if transaction is None: return wrapped(*args, **kwargs) # Now call the orginal wrapped function. 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) raise else: 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') return result
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_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__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