def instrument_django_template(module): # Wrap methods for rendering of Django templates. The name # of the method changed in between Django versions so need # to check for which one we have. The name of the function # trace node is taken from the name of the template. This # should be a relative path with the template loader # uniquely associating it with a specific template library. # Therefore do not need to worry about making it absolute as # meaning should be known in the context of the specific # Django site. def template_name(template, *args): return template.name if hasattr(module.Template, '_render'): wrap_function_trace(module, 'Template._render', name=template_name, group='Template/Render') else: wrap_function_trace(module, 'Template.render', name=template_name, group='Template/Render') # Register template tags used for manual insertion of RUM # header and footer. # # TODO This can now be installed as a separate tag library # so should possibly look at deprecating this automatic # way of doing things. library = module.Library() library.simple_tag(newrelic_browser_timing_header) library.simple_tag(newrelic_browser_timing_footer) module.libraries['django.templatetags.newrelic'] = library
def instrument_starlette_exceptions(module): wrap_function_trace(module, "ExceptionMiddleware.__call__") wrap_function_wrapper(module, "ExceptionMiddleware.http_exception", wrap_exception_handler) wrap_function_wrapper(module, "ExceptionMiddleware.add_exception_handler", wrap_add_exception_handler)
def instrument_pymongo_mongo_client(module): # Must name function explicitly as pymongo overrides the # __getattr__() method in a way that breaks introspection. rollup = ('Datastore/all', 'Datastore/MongoDB/all') wrap_function_trace(module, 'MongoClient.__init__', name='%s:MongoClient.__init__' % module.__name__, terminal=True, rollup=rollup)
def hook_motor(): for class_name, methods in _motor_classes.items(): thing = getattr(motor.motor_asyncio, class_name) if thing is not None: for method in methods: if hasattr(thing, method): wrap_function_trace(motor.motor_asyncio, '%s.%s' % (class_name, method), name='motor: %s.%s' % (class_name, method))
def instrument_starlette_middleware_errors(module): wrap_function_trace(module, "ServerErrorMiddleware.__call__") wrap_function_wrapper(module, "ServerErrorMiddleware.__init__", wrap_server_error_handler) wrap_function_wrapper(module, "ServerErrorMiddleware.error_response", wrap_exception_handler) wrap_function_wrapper(module, "ServerErrorMiddleware.debug_response", wrap_exception_handler)
def instrument_tornado_httpserver(module): if hasattr(module, 'HTTPConnection'): # The HTTPConnection class only existed prior to Tornado 4.0. wrap_function_wrapper(module, 'HTTPConnection._on_headers', _nr_wrapper_HTTPConnection__on_headers_) wrap_function_wrapper(module, 'HTTPConnection._on_request_body', _nr_wrapper_HTTPConnection__on_request_body_) wrap_function_wrapper(module, 'HTTPConnection._finish_request', _nr_wrapper_HTTPConnection__finish_request) if hasattr(module.HTTPRequest, '_parse_mime_body'): wrap_function_trace(module, 'HTTPRequest._parse_mime_body')
def instrument_flask_app(module): wrap_wsgi_application(module, 'Flask.wsgi_app', framework=framework_details()) wrap_function_wrapper(module, 'Flask.add_url_rule', _nr_wrapper_Flask_add_url_rule_input_) if hasattr(module.Flask, 'endpoint'): wrap_function_wrapper(module, 'Flask.endpoint', _nr_wrapper_Flask_endpoint_) wrap_function_wrapper(module, 'Flask.handle_http_exception', _nr_wrapper_Flask_handle_http_exception_) # Use the same wrapper for initial user exception processing and # fallback for unhandled exceptions. if hasattr(module.Flask, 'handle_user_exception'): wrap_function_wrapper(module, 'Flask.handle_user_exception', _nr_wrapper_Flask_handle_exception_) wrap_function_wrapper(module, 'Flask.handle_exception', _nr_wrapper_Flask_handle_exception_) # The _register_error_handler() method was only introduced in # Flask version 0.7.0. if hasattr(module.Flask, '_register_error_handler'): wrap_function_wrapper(module, 'Flask._register_error_handler', _nr_wrapper_Flask__register_error_handler_) # Different before/after methods were added in different versions. # Check for the presence of everything before patching. if hasattr(module.Flask, 'try_trigger_before_first_request_functions'): wrap_function_wrapper(module, 'Flask.try_trigger_before_first_request_functions', _nr_wrapper_Flask_try_trigger_before_first_request_functions_) wrap_function_wrapper(module, 'Flask.before_first_request', _nr_wrapper_Flask_before_first_request_) if hasattr(module.Flask, 'preprocess_request'): wrap_function_trace(module, 'Flask.preprocess_request') wrap_function_wrapper(module, 'Flask.before_request', _nr_wrapper_Flask_before_request_) if hasattr(module.Flask, 'process_response'): wrap_function_trace(module, 'Flask.process_response') wrap_function_wrapper(module, 'Flask.after_request', _nr_wrapper_Flask_after_request_) if hasattr(module.Flask, 'do_teardown_request'): wrap_function_trace(module, 'Flask.do_teardown_request') wrap_function_wrapper(module, 'Flask.teardown_request', _nr_wrapper_Flask_teardown_request_) if hasattr(module.Flask, 'do_teardown_appcontext'): wrap_function_trace(module, 'Flask.do_teardown_appcontext') wrap_function_wrapper(module, 'Flask.teardown_appcontext', _nr_wrapper_Flask_teardown_appcontext_)
def instrument_bottle(module): global module_bottle module_bottle = module framework_details = ('Bottle', getattr(module, '__version__')) if hasattr(module.Bottle, 'wsgi'): # version >= 0.9 wrap_wsgi_application(module, 'Bottle.wsgi', framework=framework_details) elif hasattr(module.Bottle, '__call__'): # version < 0.9 wrap_wsgi_application(module, 'Bottle.__call__', framework=framework_details) if (hasattr(module, 'Route') and hasattr(module.Route, '_make_callback')): # version >= 0.10 wrap_out_function(module, 'Route._make_callback', output_wrapper_Route_make_callback) elif hasattr(module.Bottle, '_match'): # version >= 0.9 wrap_out_function(module, 'Bottle._match', output_wrapper_Bottle_match) elif hasattr(module.Bottle, 'match_url'): # version < 0.9 wrap_out_function(module, 'Bottle.match_url', output_wrapper_Bottle_match) wrap_object_attribute(module, 'Bottle.error_handler', proxy_Bottle_error_handler) if hasattr(module, 'auth_basic'): wrap_function_wrapper(module, 'auth_basic', wrapper_auth_basic) if hasattr(module, 'SimpleTemplate'): wrap_function_trace(module, 'SimpleTemplate.render') if hasattr(module, 'MakoTemplate'): wrap_function_trace(module, 'MakoTemplate.render') if hasattr(module, 'CheetahTemplate'): wrap_function_trace(module, 'CheetahTemplate.render') if hasattr(module, 'Jinja2Template'): wrap_function_trace(module, 'Jinja2Template.render') if hasattr(module, 'SimpleTALTemplate'): wrap_function_trace(module, 'SimpleTALTemplate.render')
def instrument_tornado_ioloop(module): wrap_function_trace(module, 'IOLoop.add_handler') wrap_function_trace(module, 'IOLoop.add_timeout') wrap_function_wrapper(module, 'IOLoop.add_callback', _nr_wrapper_IOLoop_add_callback_) if hasattr(module.IOLoop, 'add_future'): wrap_function_wrapper(module, 'IOLoop.add_future', _nr_wrapper_IOLoop_add_future_) if hasattr(module, 'PollIOLoop'): wrap_function_trace(module, 'PollIOLoop.add_handler') wrap_function_trace(module, 'PollIOLoop.add_timeout') wrap_function_wrapper(module, 'PollIOLoop.add_callback', _nr_wrapper_IOLoop_add_callback_) wrap_function_trace(module, 'PollIOLoop.add_callback_from_signal')
def hook_discord(): # The normal New Relic API doesn't work here, let's replace the existing `Command.invoke` function with a version # that wraps it in a background task transaction async def _command_invoke(self, *args, **kwargs): # If there's already a running transaction, don't start a new one. New Relic doesn't handle coroutines very # well. transaction = current_transaction() if transaction: return await self._invoke(*args, **kwargs) with newrelic.agent.BackgroundTask(application, name='command: %s' % self.qualified_name): await self._invoke(*args, **kwargs) commands.Command._invoke = commands.Command.invoke commands.Command.invoke = _command_invoke # We can just hook these the simple way wrap_function_trace(asyncio, 'wait_for', name='asyncio: wait_for')
def instrument_weberror_reporter(module): def smtp_url(reporter, *args, **kwargs): return 'smtp://' + reporter.smtp_server wrap_external_trace(module, 'EmailReporter.report', 'weberror', smtp_url) wrap_function_trace(module, 'EmailReporter.report') wrap_function_trace(module, 'LogReporter.report') wrap_function_trace(module, 'FileReporter.report')
def _nr_wrapper_RequestHandler___init___(wrapped, instance, args, kwargs): # In this case we are actually wrapping the instance method on an # actual instance of a handler class rather than the class itself. # This is so we can wrap any derived version of this method when # it has been overridden in a handler class. wrap_function_wrapper(instance, 'on_connection_close', _nr_wrapper_RequestHandler_on_connection_close) wrap_function_trace(instance, 'prepare') if hasattr(instance, 'on_finish'): wrap_function_trace(instance, 'on_finish') handler = instance for name in handler.SUPPORTED_METHODS: name = name.lower() if hasattr(handler, name): wrap_function_trace(instance, name) return wrapped(*args, **kwargs)
def instrument_gearman_client(module): wrap_function_trace(module, 'GearmanClient.submit_job') wrap_function_trace(module, 'GearmanClient.submit_multiple_jobs') wrap_function_trace(module, 'GearmanClient.submit_multiple_requests') wrap_function_trace(module, 'GearmanClient.wait_until_jobs_accepted') wrap_function_trace(module, 'GearmanClient.wait_until_jobs_completed') wrap_function_trace(module, 'GearmanClient.get_job_status') wrap_function_trace(module, 'GearmanClient.get_job_statuses') wrap_function_trace(module, 'GearmanClient.wait_until_job_statuses_received')
def instrument_weberror_errormiddleware(module): wrap_function_trace(module, 'handle_exception')
def instrument_tornado_simple_httpclient(module): wrap_function_trace(module, 'SimpleAsyncHTTPClient.fetch')
def instrument_flask_templating(module): wrap_function_trace(module, 'render_template') wrap_function_trace(module, 'render_template_string')
def instrument_cherrypy__cpreqbody(module): wrap_function_trace(module, 'process_multipart') wrap_function_trace(module, 'process_multipart_form_data')
def instrument_tornado_httpserver(module): def on_headers_wrapper(wrapped, instance, args, kwargs): assert instance is not None connection = instance # Check to see if we are being called within the context of any # sort of transaction. If we are, then we don't bother doing # anything and just call the wrapped function. This should not # really ever occur but check anyway. transaction = current_transaction() if transaction: return wrapped(*args, **kwargs) # Execute the wrapped function as we are only going to do # something after it has been called. The function doesn't # return anything. 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 if connection._request_finished: return # Check to see if have already associated a transaction with # the request, because if we have, even if not finished, then # do not need to do anything. request = connection._request if request is None: return if hasattr(request, '_nr_transaction'): return # Always use the default application specified in the agent # configuration. application = application_instance() # We need to fake up a WSGI like environ dictionary with the # key bits of information we need. environ = request_environment(application, request) # Now start recording the actual web transaction. Bail out # though if turns out that recording transactions is not # enabled. transaction = WebTransaction(application, environ) if not transaction.enabled: return transaction.__enter__() request._nr_transaction = transaction request._nr_wait_function_trace = None request._nr_request_finished = False # Add a callback variable to the connection object so we can # be notified when the connection is closed before all content # has been read. def _close(): transaction.save_transaction() try: if request._nr_wait_function_trace: request._nr_wait_function_trace.__exit__(None, None, None) finally: request._nr_wait_function_trace = None transaction.__exit__(None, None, None) request._nr_transaction = None connection.stream._nr_close_callback = _close # Name transaction initially after the wrapped function so # that if connection dropped before request content read, # then don't get metric grouping issues with it being named # after the URL. name = callable_name(wrapped) transaction.set_transaction_name(name) # We need to add a reference to the request object in to the # transaction object as only able to stash the transaction # in a deferred. Need to use a weakref to avoid an object # cycle which may prevent cleanup of transaction. transaction._nr_current_request = weakref.ref(request) try: request._nr_wait_function_trace = FunctionTrace( transaction, name='Request/Input', group='Python/Tornado') request._nr_wait_function_trace.__enter__() transaction.drop_transaction() except: # Catch all # If an error occurs assume that transaction should be # exited. Technically don't believe this should ever occur # unless our code here has an error. connection.stream._nr_close_callback = None _logger.exception('Unexpected exception raised by Tornado ' 'HTTPConnection._on_headers().') transaction.__exit__(*sys.exc_info()) request._nr_transaction = None raise module.HTTPConnection._on_headers = ObjectWrapper( module.HTTPConnection._on_headers, None, on_headers_wrapper) def on_request_body_wrapper(wrapped, instance, args, kwargs): 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 # If no transaction associated with the request we can call # through straight away. There should also be a current function # trace node. if not hasattr(request, '_nr_transaction'): return wrapped(*args, **kwargs) if not request._nr_transaction: return wrapped(*args, **kwargs) if not request._nr_wait_function_trace: return wrapped(*args, **kwargs) # Restore the transaction. transaction = request._nr_transaction transaction.save_transaction() # Exit the function trace node. This should correspond to the # reading of the request input. try: request._nr_wait_function_trace.__exit__(None, None, None) finally: request._nr_wait_function_trace = None try: # Now call the orginal wrapped function. It will in turn # call the application which will ensure the transaction # started here is popped off. return wrapped(*args, **kwargs) except: # Catch all # If an error occurs assume that transaction should be # exited. Technically don't believe this should ever occur # unless our code here has an error. _logger.exception('Unexpected exception raised by Tornado ' 'HTTPConnection._on_request_body().') transaction.__exit__(*sys.exc_info()) request._nr_transaction = None raise module.HTTPConnection._on_request_body = ObjectWrapper( module.HTTPConnection._on_request_body, None, on_request_body_wrapper) def finish_request_wrapper(wrapped, instance, args, kwargs): assert instance is not None request = instance._request transaction = current_transaction() if transaction: request._nr_request_finished = True try: result = wrapped(*args, **kwargs) if (hasattr(request, '_nr_wait_function_trace') and request._nr_wait_function_trace): request._nr_wait_function_trace.__exit__(None, None, None) finally: request._nr_wait_function_trace = None return result else: if not hasattr(request, '_nr_transaction'): return wrapped(*args, **kwargs) transaction = request._nr_transaction if transaction is None: return wrapped(*args, **kwargs) transaction.save_transaction() request._nr_request_finished = True try: result = wrapped(*args, **kwargs) if request._nr_wait_function_trace: request._nr_wait_function_trace.__exit__(None, None, None) transaction.__exit__(None, None, None) except: # Catch all transaction.__exit__(*sys.exc_info()) raise finally: request._nr_wait_function_trace = None request._nr_transaction = None return result module.HTTPConnection._finish_request = ObjectWrapper( module.HTTPConnection._finish_request, None, finish_request_wrapper) def finish_wrapper(wrapped, instance, args, kwargs): assert instance is not None request = instance # Call finish() method straight away if request object it is # being called on is not even associated with a transaction. transaction = getattr(request, '_nr_transaction', None) if not transaction: return wrapped(*args, **kwargs) # Do we have a running transaction. When we do 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. running_transaction = current_transaction() if running_transaction: if transaction != running_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. try: running_transaction.drop_transaction() return finish_wrapper(wrapped, instance, args, kwargs) finally: running_transaction.save_transaction() else: # For this case we just trace the call. with FunctionTrace(transaction, name='Request/Finish', group='Python/Tornado'): return wrapped(*args, **kwargs) # No current running transaction. If we aren't in a wait state # we call finish() straight away. if not request._nr_wait_function_trace: return wrapped(*args, **kwargs) # Now handle the special case where finish() was called while in # the wait state. We need to restore the transaction for the # request and then call finish(). When it returns we need to # either end the transaction or go into a new wait state where # we wait on output to be sent. transaction.save_transaction() try: complete = True request._nr_wait_function_trace.__exit__(None, None, None) with FunctionTrace(transaction, name='Request/Finish', group='Python/Tornado'): result = wrapped(*args, **kwargs) if not request.connection.stream.writing(): transaction.__exit__(None, None, None) else: request._nr_wait_function_trace = FunctionTrace( transaction, name='Request/Output', group='Python/Tornado') request._nr_wait_function_trace.__enter__() transaction.drop_transaction() complete = False return result except: # Catch all transaction.__exit__(*sys.exc_info()) raise finally: if complete: request._nr_wait_function_trace = None request._nr_transaction = None module.HTTPRequest.finish = ObjectWrapper( module.HTTPRequest.finish, None, finish_wrapper) if hasattr(module.HTTPRequest, '_parse_mime_body'): wrap_function_trace(module.HTTPRequest, '_parse_mime_body')
def instrument_tornado_ioloop(module): wrap_function_trace(module, 'IOLoop.add_handler') wrap_function_trace(module, 'IOLoop.add_timeout') wrap_function_trace(module, 'IOLoop.add_callback') if hasattr(module.IOLoop, 'add_future'): wrap_function_trace(module, 'IOLoop.add_future') if hasattr(module, 'PollIOLoop'): wrap_function_trace(module, 'PollIOLoop.add_handler') wrap_function_trace(module, 'PollIOLoop.add_timeout') wrap_function_trace(module, 'PollIOLoop.add_callback') wrap_function_trace(module, 'PollIOLoop.add_callback_from_signal')
def instrument_cherrypy__cprequest(module): wrap_function_trace(module, 'Request.handle_error')
def instrument_flask_app(module): wrap_wsgi_application(module, "Flask.wsgi_app", framework=framework_details) wrap_function_wrapper(module, "Flask.add_url_rule", _nr_wrapper_Flask_add_url_rule_input_) if hasattr(module.Flask, "endpoint"): wrap_function_wrapper(module, "Flask.endpoint", _nr_wrapper_Flask_endpoint_) wrap_function_wrapper(module, "Flask.handle_http_exception", _nr_wrapper_Flask_handle_http_exception_) # Use the same wrapper for initial user exception processing and # fallback for unhandled exceptions. if hasattr(module.Flask, "handle_user_exception"): wrap_function_wrapper(module, "Flask.handle_user_exception", _nr_wrapper_Flask_handle_exception_) wrap_function_wrapper(module, "Flask.handle_exception", _nr_wrapper_Flask_handle_exception_) # The _register_error_handler() method was only introduced in # Flask version 0.7.0. if hasattr(module.Flask, "_register_error_handler"): wrap_function_wrapper( module, "Flask._register_error_handler", _nr_wrapper_Flask__register_error_handler_, ) # The method changed name to register_error_handler() in # Flask version 2.0.0. elif hasattr(module.Flask, "register_error_handler"): wrap_function_wrapper( module, "Flask.register_error_handler", _nr_wrapper_Flask_register_error_handler_, ) # Different before/after methods were added in different versions. # Check for the presence of everything before patching. if hasattr(module.Flask, "try_trigger_before_first_request_functions"): wrap_function_wrapper( module, "Flask.try_trigger_before_first_request_functions", _nr_wrapper_Flask_try_trigger_before_first_request_functions_, ) wrap_function_wrapper( module, "Flask.before_first_request", _nr_wrapper_Flask_before_first_request_, ) if hasattr(module.Flask, "preprocess_request"): wrap_function_trace(module, "Flask.preprocess_request") wrap_function_wrapper(module, "Flask.before_request", _nr_wrapper_Flask_before_request_) if hasattr(module.Flask, "process_response"): wrap_function_trace(module, "Flask.process_response") wrap_function_wrapper(module, "Flask.after_request", _nr_wrapper_Flask_after_request_) if hasattr(module.Flask, "do_teardown_request"): wrap_function_trace(module, "Flask.do_teardown_request") wrap_function_wrapper(module, "Flask.teardown_request", _nr_wrapper_Flask_teardown_request_) if hasattr(module.Flask, "do_teardown_appcontext"): wrap_function_trace(module, "Flask.do_teardown_appcontext") wrap_function_wrapper(module, "Flask.teardown_appcontext", _nr_wrapper_Flask_teardown_appcontext_)
def instrument_django_core_mail(module): wrap_function_trace(module, 'mail_admins') wrap_function_trace(module, 'mail_managers') wrap_function_trace(module, 'send_mail')
def instrument_django_core_mail_message(module): wrap_function_trace(module, 'EmailMessage.send')
def instrument_django_http_multipartparser(module): wrap_function_trace(module, 'MultiPartParser.parse')
def instrument_tornado_httputil(module): if hasattr(module, 'parse_body_arguments'): wrap_function_trace(module, 'parse_body_arguments') if hasattr(module, 'parse_multipart_form_data'): wrap_function_trace(module, 'parse_multipart_form_data')
def instrument_tornado_curl_httpclient(module): wrap_function_trace(module, 'CurlAsyncHTTPClient.fetch')
def instrument_tornado_httpserver(module): def on_headers_wrapper(wrapped, instance, args, kwargs): assert instance is not None connection = instance # Check to see if we are being called within the context of any # sort of transaction. If we are, then we don't bother doing # anything and just call the wrapped function. This should not # really ever occur but check anyway. transaction = current_transaction() if transaction: return wrapped(*args, **kwargs) # Execute the wrapped function as we are only going to do # something after it has been called. The function doesn't # return anything. 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 if connection._request_finished: return # Check to see if have already associated a transaction with # the request, because if we have, even if not finished, then # do not need to do anything. request = connection._request if request is None: return if hasattr(request, '_nr_transaction'): return # Always use the default application specified in the agent # configuration. application = application_instance() # We need to fake up a WSGI like environ dictionary with the # key bits of information we need. environ = request_environment(application, request) # Now start recording the actual web transaction. Bail out # though if turns out that recording transactions is not # enabled. transaction = WebTransaction(application, environ) if not transaction.enabled: return transaction.__enter__() request._nr_transaction = transaction request._nr_wait_function_trace = None request._nr_request_finished = False # Add a callback variable to the connection object so we can # be notified when the connection is closed before all content # has been read. def _close(): transaction.save_transaction() try: if request._nr_wait_function_trace: request._nr_wait_function_trace.__exit__(None, None, None) finally: request._nr_wait_function_trace = None transaction.__exit__(None, None, None) request._nr_transaction = None connection.stream._nr_close_callback = _close # Name transaction initially after the wrapped function so # that if connection dropped before request content read, # then don't get metric grouping issues with it being named # after the URL. name = callable_name(wrapped) transaction.set_transaction_name(name) # We need to add a reference to the request object in to the # transaction object as only able to stash the transaction # in a deferred. Need to use a weakref to avoid an object # cycle which may prevent cleanup of transaction. transaction._nr_current_request = weakref.ref(request) try: request._nr_wait_function_trace = FunctionTrace( transaction, name='Request/Input', group='Python/Tornado') request._nr_wait_function_trace.__enter__() transaction.drop_transaction() except: # Catch all # If an error occurs assume that transaction should be # exited. Technically don't believe this should ever occur # unless our code here has an error. connection.stream._nr_close_callback = None _logger.exception('Unexpected exception raised by Tornado ' 'HTTPConnection._on_headers().') transaction.__exit__(*sys.exc_info()) request._nr_transaction = None raise module.HTTPConnection._on_headers = ObjectWrapper( module.HTTPConnection._on_headers, None, on_headers_wrapper) def on_request_body_wrapper(wrapped, instance, args, kwargs): 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 # If no transaction associated with the request we can call # through straight away. There should also be a current function # trace node. if not hasattr(request, '_nr_transaction'): return wrapped(*args, **kwargs) if not request._nr_transaction: return wrapped(*args, **kwargs) if not request._nr_wait_function_trace: return wrapped(*args, **kwargs) # Restore the transaction. transaction = request._nr_transaction transaction.save_transaction() # Exit the function trace node. This should correspond to the # reading of the request input. try: request._nr_wait_function_trace.__exit__(None, None, None) finally: request._nr_wait_function_trace = None try: # Now call the orginal wrapped function. It will in turn # call the application which will ensure the transaction # started here is popped off. return wrapped(*args, **kwargs) except: # Catch all # If an error occurs assume that transaction should be # exited. Technically don't believe this should ever occur # unless our code here has an error. _logger.exception('Unexpected exception raised by Tornado ' 'HTTPConnection._on_request_body().') transaction.__exit__(*sys.exc_info()) request._nr_transaction = None raise module.HTTPConnection._on_request_body = ObjectWrapper( module.HTTPConnection._on_request_body, None, on_request_body_wrapper) def finish_request_wrapper(wrapped, instance, args, kwargs): assert instance is not None request = instance._request transaction = current_transaction() if transaction: request._nr_request_finished = True try: result = wrapped(*args, **kwargs) if (hasattr(request, '_nr_wait_function_trace') and request._nr_wait_function_trace): request._nr_wait_function_trace.__exit__(None, None, None) finally: request._nr_wait_function_trace = None return result else: if not hasattr(request, '_nr_transaction'): return wrapped(*args, **kwargs) transaction = request._nr_transaction if transaction is None: return wrapped(*args, **kwargs) transaction.save_transaction() request._nr_request_finished = True try: result = wrapped(*args, **kwargs) if request._nr_wait_function_trace: request._nr_wait_function_trace.__exit__(None, None, None) transaction.__exit__(None, None, None) except: # Catch all transaction.__exit__(*sys.exc_info()) raise finally: request._nr_wait_function_trace = None request._nr_transaction = None return result module.HTTPConnection._finish_request = ObjectWrapper( module.HTTPConnection._finish_request, None, finish_request_wrapper) def finish_wrapper(wrapped, instance, args, kwargs): assert instance is not None request = instance # Call finish() method straight away if request object it is # being called on is not even associated with a transaction. transaction = getattr(request, '_nr_transaction', None) if not transaction: return wrapped(*args, **kwargs) # Do we have a running transaction. When we do 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. running_transaction = current_transaction() if running_transaction: if transaction != running_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. try: running_transaction.drop_transaction() return finish_wrapper(wrapped, instance, args, kwargs) finally: running_transaction.save_transaction() else: # For this case we just trace the call. with FunctionTrace(transaction, name='Request/Finish', group='Python/Tornado'): return wrapped(*args, **kwargs) # No current running transaction. If we aren't in a wait state # we call finish() straight away. if not request._nr_wait_function_trace: return wrapped(*args, **kwargs) # Now handle the special case where finish() was called while in # the wait state. We need to restore the transaction for the # request and then call finish(). When it returns we need to # either end the transaction or go into a new wait state where # we wait on output to be sent. transaction.save_transaction() try: complete = True request._nr_wait_function_trace.__exit__(None, None, None) with FunctionTrace(transaction, name='Request/Finish', group='Python/Tornado'): result = wrapped(*args, **kwargs) if not request.connection.stream.writing(): transaction.__exit__(None, None, None) else: request._nr_wait_function_trace = FunctionTrace( transaction, name='Request/Output', group='Python/Tornado') request._nr_wait_function_trace.__enter__() transaction.drop_transaction() complete = False return result except: # Catch all transaction.__exit__(*sys.exc_info()) raise finally: if complete: request._nr_wait_function_trace = None request._nr_transaction = None module.HTTPRequest.finish = ObjectWrapper(module.HTTPRequest.finish, None, finish_wrapper) if hasattr(module.HTTPRequest, '_parse_mime_body'): wrap_function_trace(module.HTTPRequest, '_parse_mime_body')
def instrument_tornado_ioloop(module): wrap_function_trace(module, "IOLoop.add_handler") wrap_function_trace(module, "IOLoop.add_timeout") wrap_function_trace(module, "IOLoop.add_callback") if hasattr(module.IOLoop, "add_future"): wrap_function_trace(module, "IOLoop.add_future") if hasattr(module, "PollIOLoop"): wrap_function_trace(module, "PollIOLoop.add_handler") wrap_function_trace(module, "PollIOLoop.add_timeout") wrap_function_trace(module, "PollIOLoop.add_callback") wrap_function_trace(module, "PollIOLoop.add_callback_from_signal")