Repository URL to install this package:
|
Version:
2.68.0.50 ▾
|
import weakref
import traceback
import contextlib
from newrelic.agent import (FunctionTrace, WebTransaction,
application as application_instance, ignore_status_code, extra_settings,
current_transaction)
_boolean_states = {
'1': True, 'yes': True, 'true': True, 'on': True,
'0': False, 'no': False, 'false': False, 'off': False
}
def _setting_boolean(value):
if value.lower() not in _boolean_states:
raise ValueError('Not a boolean: %s' % value)
return _boolean_states[value.lower()]
_settings_types = {
'debug.transaction_monitoring': _setting_boolean,
'instrumentation.metrics.async_wait_rollup': _setting_boolean,
}
_settings_defaults = {
'debug.transaction_monitoring': False,
'instrumentation.metrics.async_wait_rollup': True,
}
tornado_settings = extra_settings('import-hook:tornado',
types=_settings_types, defaults=_settings_defaults)
_last_transaction_activation = None
def last_transaction_activation():
return _last_transaction_activation
def record_exception(transaction, exc_info):
# Record the details of any exception ignoring status codes which
# have been configured to be ignored.
import tornado.web
exc = exc_info[0]
value = exc_info[1]
if exc is tornado.web.HTTPError:
if ignore_status_code(value.status_code):
return
transaction.record_exception(*exc_info)
def request_environment(application, request):
# This creates a WSGI environ dictionary from a Tornado request.
result = getattr(request, '_nr_request_environ', None)
if result is not None:
return result
# We don't bother if the agent hasn't as yet been registered.
settings = application.settings
if not settings:
return {}
request._nr_request_environ = result = {}
result['REQUEST_URI'] = request.uri
result['QUERY_STRING'] = request.query
value = request.headers.get('X-NewRelic-ID')
if value:
result['HTTP_X_NEWRELIC_ID'] = value
value = request.headers.get('X-NewRelic-Transaction')
if value:
result['HTTP_X_NEWRELIC_TRANSACTION'] = value
value = request.headers.get('X-Request-Start')
if value:
result['HTTP_X_REQUEST_START'] = value
value = request.headers.get('X-Queue-Start')
if value:
result['HTTP_X_QUEUE_START'] = value
for key in settings.include_environ:
if key == 'REQUEST_METHOD':
result[key] = request.method
elif key == 'HTTP_USER_AGENT':
value = request.headers.get('User-Agent')
if value:
result[key] = value
elif key == 'HTTP_REFERER':
value = request.headers.get('Referer')
if value:
result[key] = value
elif key == 'CONTENT_TYPE':
value = request.headers.get('Content-Type')
if value:
result[key] = value
elif key == 'CONTENT_LENGTH':
value = request.headers.get('Content-Length')
if value:
result[key] = value
return result
def retrieve_current_transaction():
# Retrieves the current transaction regardless of whether it has
# been stopped or ignored. We can't just return the current
# transaction gated by whether the transaction has been stopped or
# is being ignored as that would screw things up because of all the
# funny checks we need to do against the transaction when resuming a
# transaction and so on.
return current_transaction(active_only=False)
def retrieve_transaction_request(transaction):
# Retrieves any request already associated with the transaction.
if hasattr(transaction, '_nr_current_request'):
if transaction._nr_current_request is not None:
return transaction._nr_current_request()
def retrieve_request_transaction(request):
# Retrieves any transaction already associated with the request.
return getattr(request, '_nr_transaction', None)
def request_finished(request):
# Returns whether the request is in the process of being finished.
# If we haven't started tracking of the request returns None.
return getattr(request, '_nr_request_finished', None)
def request_caller_context(request):
# Returns the most recently specified named caller context.
if not hasattr(request, '_nr_caller_context'):
return
if not request._nr_caller_context:
return
return request._nr_caller_context[-1]
@contextlib.contextmanager
def _nr_wrapper_named_caller_context_(request, name):
if not hasattr(request, '_nr_caller_context'):
request._nr_caller_context = []
request._nr_caller_context.append(name)
yield
request._nr_caller_context.pop(-1)
def initiate_request_monitoring(request):
# Creates a new transaction and associates it with the request.
# We 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)
# We now start recording the actual web transaction. Bail out though
# if it turns out that recording of transactions is not enabled.
transaction = WebTransaction(application, environ)
if not transaction.enabled:
return
if tornado_settings.debug.transaction_monitoring:
global _last_transaction_activation
_last_transaction_activation = ''.join(traceback.format_stack()[:-1])
transaction.__enter__()
request._nr_transaction = transaction
request._nr_wait_function_trace = None
request._nr_request_finished = False
# We also need to add a reference to the request object in to the
# transaction object so we can later access it in a deferred. We
# need to use a weakref to avoid an object cycle which may prevent
# cleanup of the transaction.
transaction._nr_current_request = weakref.ref(request)
# Record framework information for generation of framework metrics.
import tornado
if hasattr(tornado, 'version_info'):
version = '.'.join(map(str, tornado.version_info))
else:
version = None
transaction.add_framework_info('Tornado/ASYNC', version)
return transaction
def suspend_request_monitoring(request, name, group='Python/Tornado',
terminal=True, rollup=None):
# Suspend the monitoring of the transaction. We do this because
# we can't rely on thread local data to separate transactions for
# requests. We thus have to move it out of the way.
transaction = retrieve_request_transaction(request)
if transaction is None:
_logger.error('Runtime instrumentation error. Suspending the '
'Tornado transaction but there was no transaction cached '
'against the request object. Report this issue to New Relic '
'support.\n%s', ''.join(traceback.format_stack()[:-1]))
return
# Create a function trace to track the time while monitoring of
# this transaction is suspended.
if request._nr_wait_function_trace:
_logger.error('Runtime instrumentation error. Suspending the '
'Tornado transaction when it has already been suspended. '
'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
if rollup is None:
if tornado_settings.instrumentation.metrics.async_wait_rollup:
rollup = 'Async Wait'
request._nr_wait_function_trace = FunctionTrace(transaction,
name=name, group=group, terminal=terminal, rollup=rollup)
request._nr_wait_function_trace.__enter__()
transaction.drop_transaction()
def resume_request_monitoring(request, required=False):
# Resume the monitoring of the transaction. This is moving the
# transaction stored against the request as the active one.
transaction = retrieve_request_transaction(request)
if transaction is None:
if not required:
return
_logger.error('Runtime instrumentation error. Resuming the '
'Tornado transaction but there was no transaction cached '
'against the request object. Report this issue to New Relic '
'support.\n%s', ''.join(traceback.format_stack()[:-1]))
return
# Now make the transaction stored against the request the current
# transaction.
if tornado_settings.debug.transaction_monitoring:
global _last_transaction_activation
_last_transaction_activation = ''.join(traceback.format_stack()[:-1])
transaction.save_transaction()
# Close out any active function trace used to track the time while
# monitoring of the transaction was suspended. Technically there
# should always be an active function trace but check and ignore
# it if there isn't for now.
try:
if request._nr_wait_function_trace:
request._nr_wait_function_trace.__exit__(None, None, None)
finally:
request._nr_wait_function_trace = None
return transaction
def finish_request_monitoring(request):
# Marks that the request should be finishing up.
request._nr_request_finished = True
def finalize_request_monitoring(request, exc=None, value=None, tb=None):
# Finalize monitoring of the transaction.
transaction = retrieve_request_transaction(request)
if transaction is None:
_logger.error('Runtime instrumentation error. Finalizing the '
'Tornado transaction but there was no transaction cached '
'against the request object. Report this issue to New Relic '
'support.\n%s', ''.join(traceback.format_stack()[:-1]))
return
# If all nodes hadn't been popped from the transaction stack then
# error messages will be logged by the transaction. We therefore do
# not need to check here.
#
# We must ensure we cleanup here even if __exit__() fails with an
# exception for some reason. This is especially the case for our own
# test harnesses.
try:
transaction.__exit__(exc, value, tb)
finally:
transaction._nr_current_request = None
request._nr_transaction = None
request._nr_wait_function_trace = None
request._nr_request_finished = True