Repository URL to install this package:
|
Version:
2.68.0.50 ▾
|
import sys
import threading
import logging
import functools
from newrelic.packages import six
from newrelic.agent import (FunctionWrapper, callable_name,
wrap_error_trace, FunctionTrace, wrap_function_trace, wrap_in_function,
wrap_transaction_name, wrap_post_function, current_transaction,
WSGIApplicationWrapper, ignore_status_code, insert_html_snippet,
verify_body_exists, BackgroundTask, register_application,
wrap_function_wrapper, FunctionTraceWrapper, function_wrapper,
extra_settings, global_settings)
_logger = logging.getLogger(__name__)
_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()]
def _setting_set(value):
return set(value.split())
_settings_types = {
'browser_monitoring.auto_instrument': _setting_boolean,
'instrumentation.templates.inclusion_tag' : _setting_set,
'instrumentation.background_task.startup_timeout': float,
'instrumentation.scripts.django_admin' : _setting_set,
}
_settings_defaults = {
'browser_monitoring.auto_instrument': True,
'instrumentation.templates.inclusion_tag': set(),
'instrumentation.background_task.startup_timeout': 10.0,
'instrumentation.scripts.django_admin' : set(),
}
django_settings = extra_settings('import-hook:django',
types=_settings_types, defaults=_settings_defaults)
def should_ignore(exc, value, tb):
from django.http import Http404
if isinstance(value, Http404):
if ignore_status_code(404):
return True
# Response middleware for automatically inserting RUM header and
# footer into HTML response returned by application
def browser_timing_middleware(request, response):
# Don't do anything if receive a streaming response which
# was introduced in Django 1.5. Need to avoid this as there
# will be no 'content' attribute. Alternatively there may be
# a 'content' attribute which flattens the stream, which if
# we access, will break the streaming and/or buffer what is
# potentially a very large response in memory contrary to
# what user wanted by explicitly using a streaming response
# object in the first place. To preserve streaming but still
# do RUM insertion, need to move to a WSGI middleware and
# deal with how to update the content length.
if hasattr(response, 'streaming_content'):
return response
# Need to be running within a valid web transaction.
transaction = current_transaction()
if not transaction:
return response
# Only insert RUM JavaScript headers and footers if enabled
# in configuration and not already likely inserted.
if not transaction.settings.browser_monitoring.enabled:
return response
if transaction.autorum_disabled:
return response
if not django_settings.browser_monitoring.auto_instrument:
return response
if transaction.rum_header_generated:
return response
# Only possible if the content type is one of the allowed
# values. Normally this is just text/html, but optionally
# could be defined to be list of further types. For example
# a user may want to also perform insertion for
# 'application/xhtml+xml'.
ctype = response.get('Content-Type', '').lower().split(';')[0]
if ctype not in transaction.settings.browser_monitoring.content_type:
return response
# Don't risk it if content encoding already set.
if response.has_header('Content-Encoding'):
return response
# Don't risk it if content is actually within an attachment.
cdisposition = response.get('Content-Disposition', '').lower()
if cdisposition.split(';')[0].strip().lower() == 'attachment':
return response
# No point continuing if header is empty. This can occur if
# RUM is not enabled within the UI. It is assumed at this
# point that if header is not empty, then footer will not be
# empty. We don't want to generate the footer just yet as
# want to do that as late as possible so that application
# server time in footer is as accurate as possible. In
# particular, if the response content is generated on demand
# then the flattening of the response could take some time
# and we want to track that. We thus generate footer below
# at point of insertion.
header = transaction.browser_timing_header()
if not header:
return response
def html_to_be_inserted():
return six.b(header) + six.b(transaction.browser_timing_footer())
# Make sure we flatten any content first as it could be
# stored as a list of strings in the response object. We
# assign it back to the response object to avoid having
# multiple copies of the string in memory at the same time
# as we progress through steps below.
result = insert_html_snippet(response.content, html_to_be_inserted)
if result is not None:
if transaction.settings.debug.log_autorum_middleware:
_logger.debug('RUM insertion from Django middleware '
'triggered. Bytes added was %r.',
len(result) - len(response.content))
response.content = result
if response.get('Content-Length', None):
response['Content-Length'] = str(len(response.content))
return response
def register_browser_timing_middleware(middleware):
# Inserts our middleware for inserting the RUM header and
# footer into the list of middleware. Must check for certain
# types of middleware which modify content as must always
# come before them. Otherwise is added last so that comes
# after any caching middleware. If don't do that then the
# inserted header and footer will end up being cached and
# then when served up from cache we would add a second
# header and footer, something we don't want.
content_type_modifying_middleware = [
'django.middleware.gzip:GZipMiddleware.process_response'
]
for i in range(len(middleware)):
function = middleware[i]
name = callable_name(function)
if name in content_type_modifying_middleware:
middleware.insert(i, browser_timing_middleware)
break
else:
middleware.append(browser_timing_middleware)
# Template tag functions for manually inserting RUM header and
# footer into HTML response. A template tag library for
# 'newrelic' will be automatically inserted into set of tag
# libraries when performing step to instrument the middleware.
def newrelic_browser_timing_header():
from django.utils.safestring import mark_safe
transaction = current_transaction()
return transaction and mark_safe(transaction.browser_timing_header()) or ''
def newrelic_browser_timing_footer():
from django.utils.safestring import mark_safe
transaction = current_transaction()
return transaction and mark_safe(transaction.browser_timing_footer()) or ''
# Addition of instrumentation for middleware. Can only do this
# after Django itself has constructed the list of middleware. We
# also insert the RUM middleware into the response middleware.
middleware_instrumentation_lock = threading.Lock()
def wrap_leading_middleware(middleware):
# Wrapper to be applied to middleware executed prior to the
# view handler being executed. Records the time spent in the
# middleware as separate function node and also attempts to
# name the web transaction after the name of the middleware
# with success being determined by the priority.
def wrapper(wrapped):
# The middleware if a class method would already be
# bound at this point, so is safe to determine the name
# when it is being wrapped rather than on each
# invocation.
name = callable_name(wrapped)
def wrapper(wrapped, instance, args, kwargs):
transaction = current_transaction()
if transaction is None:
return wrapped(*args, **kwargs)
before = (transaction.name, transaction.group)
with FunctionTrace(transaction, name=name):
try:
return wrapped(*args, **kwargs)
finally:
# We want to name the transaction after this
# middleware but only if the transaction wasn't
# named from within the middleware itself explicity.
after = (transaction.name, transaction.group)
if before == after:
transaction.set_transaction_name(name, priority=2)
return FunctionWrapper(wrapped, wrapper)
for wrapped in middleware:
yield wrapper(wrapped)
def wrap_view_middleware(middleware):
# XXX This is no longer being used. The changes to strip the
# wrapper from the view handler when passed into the function
# urlresolvers.reverse() solves most of the problems. To back
# that up, the object wrapper now proxies various special
# methods so that comparisons like '==' will work. The object
# wrapper can even be used as a standin for the wrapped object
# when used as a key in a dictionary and will correctly match
# the original wrapped object.
# Wrapper to be applied to view middleware. Records the time
# spent in the middleware as separate function node and also
# attempts to name the web transaction after the name of the
# middleware with success being determined by the priority.
# This wrapper is special in that it must strip the wrapper
# from the view handler when being passed to the view
# middleware to avoid issues where middleware wants to do
# comparisons between the passed middleware and some other
# value. It is believed that the view handler should never
# actually be called from the view middleware so not an
# issue that no longer wrapped at this point.
def wrapper(wrapped):
# The middleware if a class method would already be
# bound at this point, so is safe to determine the name
# when it is being wrapped rather than on each
# invocation.
name = callable_name(wrapped)
def wrapper(wrapped, instance, args, kwargs):
transaction = current_transaction()
def _wrapped(request, view_func, view_args, view_kwargs):
# This strips the view handler wrapper before call.
if hasattr(view_func, '_nr_last_object'):
view_func = view_func._nr_last_object
return wrapped(request, view_func, view_args, view_kwargs)
if transaction is None:
return _wrapped(*args, **kwargs)
before = (transaction.name, transaction.group)
with FunctionTrace(transaction, name=name):
try:
return _wrapped(*args, **kwargs)
finally:
# We want to name the transaction after this
# middleware but only if the transaction wasn't
# named from within the middleware itself explicity.
after = (transaction.name, transaction.group)
if before == after:
transaction.set_transaction_name(name, priority=2)
return FunctionWrapper(wrapped, wrapper)
for wrapped in middleware:
yield wrapper(wrapped)
def wrap_trailing_middleware(middleware):
# Wrapper to be applied to trailing middleware executed
# after the view handler. Records the time spent in the
# middleware as separate function node. Transaction is never
# named after these middleware.
def wrapper(wrapped):
# The middleware if a class method would already be
# bound at this point, so is safe to determine the name
# when it is being wrapped rather than on each
# invocation.
name = callable_name(wrapped)
def wrapper(wrapped, instance, args, kwargs):
transaction = current_transaction()
if transaction is None:
return wrapped(*args, **kwargs)
with FunctionTrace(transaction, name=name):
return wrapped(*args, **kwargs)
return FunctionWrapper(wrapped, wrapper)
for wrapped in middleware:
yield wrapper(wrapped)
def insert_and_wrap_middleware(handler, *args, **kwargs):
# Use lock to control access by single thread but also as
# flag to indicate if done the initialisation. Lock will be
# None if have already done this.
global middleware_instrumentation_lock
if not middleware_instrumentation_lock:
return
lock = middleware_instrumentation_lock
lock.acquire()
# Check again in case two threads grab lock at same time.
if not middleware_instrumentation_lock:
lock.release()
return
# Set lock to None so we know have done the initialisation.
middleware_instrumentation_lock = None
try:
# For response middleware, need to add in middleware for
# automatically inserting RUM header and footer. This is
# done first which means it gets wrapped and timed as
# well. Will therefore show in traces however that may
# be beneficial as highlights we are doing some magic
# and can see if it is taking too long on large
# responses.
if hasattr(handler, '_response_middleware'):
register_browser_timing_middleware(handler._response_middleware)
# Now wrap the middleware to undertake timing and name
# the web transaction. The naming is done as lower
# priority than that for view handler so view handler
# name always takes precedence.
if hasattr(handler, '_request_middleware'):
handler._request_middleware = list(
wrap_leading_middleware(
handler._request_middleware))
if hasattr(handler, '_view_middleware'):
handler._view_middleware = list(
wrap_leading_middleware(
handler._view_middleware))
if hasattr(handler, '_template_response_middleware'):
handler._template_response_middleware = list(
wrap_trailing_middleware(
handler._template_response_middleware))
if hasattr(handler, '_response_middleware'):
handler._response_middleware = list(
wrap_trailing_middleware(
handler._response_middleware))
if hasattr(handler, '_exception_middleware'):
handler._exception_middleware = list(
wrap_trailing_middleware(
handler._exception_middleware))
finally:
lock.release()
# Post import hooks for modules.
def instrument_django_core_handlers_base(module):
# Attach a post function to load_middleware() method of
# BaseHandler to trigger insertion of browser timing
# middleware and wrapping of middleware for timing etc.
wrap_post_function(module, 'BaseHandler.load_middleware',
insert_and_wrap_middleware)
def wrap_handle_uncaught_exception(middleware):
# Wrapper to be applied to handler called when exceptions
# propagate up to top level from middleware. Records the
# time spent in the handler as separate function node. Names
# the web transaction after the name of the handler if not
# already named at higher priority and capture further
# errors in the handler.
name = callable_name(middleware)
def wrapper(wrapped, instance, args, kwargs):
transaction = current_transaction()
if transaction is None:
return wrapped(*args, **kwargs)
def _wrapped(request, resolver, exc_info):
transaction.set_transaction_name(name, priority=1)
transaction.record_exception(*exc_info)
try:
return wrapped(request, resolver, exc_info)
except: # Catch all
transaction.record_exception(*sys.exc_info())
raise
with FunctionTrace(transaction, name=name):
return _wrapped(*args, **kwargs)
return FunctionWrapper(middleware, wrapper)
def instrument_django_core_handlers_wsgi(module):
# Wrap the WSGI application entry point. If this is also
# wrapped from the WSGI script file or by the WSGI hosting
# mechanism then those will take precedence.
import django
framework = ('Django', django.get_version())
module.WSGIHandler.__call__ = WSGIApplicationWrapper(
module.WSGIHandler.__call__, framework=framework)
# Wrap handle_uncaught_exception() of WSGIHandler so that
# can capture exception details of any exception which
# wasn't caught and dealt with by an exception middleware.
# The handle_uncaught_exception() function produces a 500
# error response page and otherwise suppresses the
# exception, so last chance to do this as exception will not
# propogate up to the WSGI application.
module.WSGIHandler.handle_uncaught_exception = (
wrap_handle_uncaught_exception(
module.WSGIHandler.handle_uncaught_exception))
def wrap_view_handler(wrapped, priority=3):
# Ensure we don't wrap the view handler more than once. This
# looks like it may occur in cases where the resolver is
# called recursively. We flag that view handler was wrapped
# using the '_nr_django_view_handler' attribute.
if hasattr(wrapped, '_nr_django_view_handler'):
return wrapped
name = callable_name(wrapped)
def wrapper(wrapped, instance, args, kwargs):
transaction = current_transaction()
if transaction is None:
return wrapped(*args, **kwargs)
transaction.set_transaction_name(name, priority=priority)
with FunctionTrace(transaction, name=name):
try:
return wrapped(*args, **kwargs)
except: # Catch all
transaction.record_exception(ignore_errors=should_ignore)
raise
result = FunctionWrapper(wrapped, wrapper)
result._nr_django_view_handler = True
return result
def wrap_url_resolver(wrapped):
# Wrap URL resolver. If resolver returns valid result then
# wrap the view handler returned. The type of the result
# changes across Django versions so need to check and adapt
# as necessary. For a 404 then a user supplied 404 handler
# or the default 404 handler should get later invoked and
# transaction should be named after that.
name = callable_name(wrapped)
def wrapper(wrapped, instance, args, kwargs):
transaction = current_transaction()
if transaction is None:
return wrapped(*args, **kwargs)
if hasattr(transaction, '_nr_django_url_resolver'):
return wrapped(*args, **kwargs)
# Tag the transaction so we know when we are in the top
# level call to the URL resolver as don't want to show
# the inner ones as would be one for each url pattern.
transaction._nr_django_url_resolver = True
def _wrapped(path):
# XXX This can raise a Resolver404. If this is not dealt
# with, is this the source of our unnamed 404 requests.
with FunctionTrace(transaction, name=name, label=path):
result = wrapped(path)
if type(result) == type(()):
callback, callback_args, callback_kwargs = result
result = (wrap_view_handler(callback, priority=5),
callback_args, callback_kwargs)
else:
result.func = wrap_view_handler(result.func, priority=5)
return result
try:
return _wrapped(*args, **kwargs)
finally:
del transaction._nr_django_url_resolver
return FunctionWrapper(wrapped, wrapper)
def wrap_url_resolver_nnn(wrapped, priority=1):
# Wrapper to be applied to the URL resolver for errors.
name = callable_name(wrapped)
def wrapper(wrapped, instance, args, kwargs):
transaction = current_transaction()
if transaction is None:
return wrapped(*args, **kwargs)
with FunctionTrace(transaction, name=name):
callback, param_dict = wrapped(*args, **kwargs)
return (wrap_view_handler(callback, priority=priority),
param_dict)
return FunctionWrapper(wrapped, wrapper)
def wrap_url_reverse(wrapped):
# Wrap the URL resolver reverse lookup. Where the view
# handler is passed in we need to strip any instrumentation
# wrapper to ensure that it doesn't interfere with the
# lookup process. Technically this may now not be required
# as we have improved the proxying in the object wrapper,
# but do it just to avoid any potential for problems.
def wrapper(wrapped, instance, args, kwargs):
def execute(viewname, *args, **kwargs):
if hasattr(viewname, '_nr_last_object'):
viewname = viewname._nr_last_object
return wrapped(viewname, *args, **kwargs)
return execute(*args, **kwargs)
return FunctionWrapper(wrapped, wrapper)
def instrument_django_core_urlresolvers(module):
# Wrap method which maps a string version of a function
# name as used in urls.py pattern so can capture any
# exception which is raised during that process.
# Normally Django captures import errors at this point
# and then reraises a ViewDoesNotExist exception with
# details of the original error and traceback being
# lost. We thus intercept it here so can capture that
# traceback which is otherwise lost. Although we ignore
# a Http404 exception here, it probably is never the
# case that one can be raised by get_callable().
wrap_error_trace(module, 'get_callable', ignore_errors=should_ignore)
# Wrap methods which resolves a request to a view handler.
# This can be called against a resolver initialised against
# a custom URL conf associated with a specific request, or a
# resolver which uses the default URL conf.
module.RegexURLResolver.resolve = wrap_url_resolver(
module.RegexURLResolver.resolve)
# Wrap methods which resolve error handlers. For 403 and 404
# we give these higher naming priority over any prior
# middleware or view handler to give them visibility. For a
# 500, which will be triggered for unhandled exception, we
# leave any original name derived from a middleware or view
# handler in place so error details identify the correct
# transaction.
if hasattr(module.RegexURLResolver, 'resolve403'):
module.RegexURLResolver.resolve403 = wrap_url_resolver_nnn(
module.RegexURLResolver.resolve403, priority=3)
if hasattr(module.RegexURLResolver, 'resolve404'):
module.RegexURLResolver.resolve404 = wrap_url_resolver_nnn(
module.RegexURLResolver.resolve404, priority=3)
if hasattr(module.RegexURLResolver, 'resolve500'):
module.RegexURLResolver.resolve500 = wrap_url_resolver_nnn(
module.RegexURLResolver.resolve500, priority=1)
if hasattr(module.RegexURLResolver, 'resolve_error_handler'):
module.RegexURLResolver.resolve_error_handler = wrap_url_resolver_nnn(
module.RegexURLResolver.resolve_error_handler, priority=1)
# Wrap function for performing reverse URL lookup to strip any
# instrumentation wrapper when view handler is passed in.
module.reverse = wrap_url_reverse(module.reverse)
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')
# Django 1.8 no longer has module.libraries. As automatic way is not
# preferred we can just skip this now.
if not hasattr(module, 'libraries'):
return
# 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 wrap_template_block(wrapped):
name = callable_name(wrapped)
def wrapper(wrapped, instance, args, kwargs):
transaction = current_transaction()
if transaction is None:
return wrapped(*args, **kwargs)
with FunctionTrace(transaction, name=instance.name,
group='Template/Block'):
return wrapped(*args, **kwargs)
return FunctionWrapper(wrapped, wrapper)
def instrument_django_template_loader_tags(module):
# Wrap template block node for timing, naming the node after
# the block name as defined in the template rather than
# function name.
module.BlockNode.render = wrap_template_block(module.BlockNode.render)
def instrument_django_core_servers_basehttp(module):
# Allow 'runserver' to be used with Django <= 1.3. To do
# this we wrap the WSGI application argument on the way in
# so that the run() method gets the wrapped instance.
#
# Although this works, if anyone wants to use it and make
# it reliable, they may need to first need to patch Django
# as explained in the ticket:
#
# https://code.djangoproject.com/ticket/16241
#
# as the Django 'runserver' is not WSGI compliant due to a
# bug in its handling of errors when writing response.
#
# The way the agent now uses a weakref dictionary for the
# transaction object may be enough to ensure the prior
# transaction is cleaned up properly when it is deleted,
# but not absolutely sure that will always work. Thus is
# still a risk of error on subsequent request saying that
# there is an active transaction.
#
# TODO Later versions of Django use the wsgiref server
# instead which will likely need to be dealt with via
# instrumentation of the wsgiref module or some other means.
import django
framework = ('Django', django.get_version())
def wrap_wsgi_application_entry_point(server, application, **kwargs):
return ((server, WSGIApplicationWrapper(application,
framework='Django'),), kwargs)
# XXX Because of risk of people still trying to use the
# inbuilt Django development server and since the code is
# not going to be changed, could just patch it to fix
# problem and the instrumentation we need.
if (not hasattr(module, 'simple_server') and
hasattr(module.ServerHandler, 'run')):
# Patch the server to make it work properly.
def run(self, application):
try:
self.setup_environ()
self.result = application(self.environ, self.start_response)
self.finish_response()
except Exception:
self.handle_error()
finally:
self.close()
def close(self):
if self.result is not None:
try:
self.request_handler.log_request(
self.status.split(' ',1)[0], self.bytes_sent)
finally:
try:
if hasattr(self.result,'close'):
self.result.close()
finally:
self.result = None
self.headers = None
self.status = None
self.environ = None
self.bytes_sent = 0
self.headers_sent = False
# Leaving this out for now to see whether weakref solves
# the problem.
#module.ServerHandler.run = run
#module.ServerHandler.close = close
# Now wrap it with our instrumentation.
wrap_in_function(module, 'ServerHandler.run',
wrap_wsgi_application_entry_point)
def instrument_django_contrib_staticfiles_views(module):
if not hasattr(module.serve, '_nr_django_view_handler'):
module.serve = wrap_view_handler(module.serve, priority=3)
def instrument_django_contrib_staticfiles_handlers(module):
wrap_transaction_name(module, 'StaticFilesHandler.serve')
def instrument_django_views_debug(module):
# Wrap methods for handling errors when Django debug
# enabled. For 404 we give this higher naming priority over
# any prior middleware or view handler to give them
# visibility. For a 500, which will be triggered for
# unhandled exception, we leave any original name derived
# from a middleware or view handler in place so error
# details identify the correct transaction.
module.technical_404_response = wrap_view_handler(
module.technical_404_response, priority=3)
module.technical_500_response = wrap_view_handler(
module.technical_500_response, priority=1)
def wrap_view_dispatch(wrapped):
# Wrapper to be applied to dispatcher for class based views.
def wrapper(wrapped, instance, args, kwargs):
transaction = current_transaction()
if transaction is None:
return wrapped(*args, **kwargs)
def _args(request, *args, **kwargs):
return request
view = instance
request = _args(*args, **kwargs)
# We can't intercept the delegated view handler when it
# is looked up by the dispatch() method so we need to
# duplicate the lookup mechanism.
if request.method.lower() in view.http_method_names:
handler = getattr(view, request.method.lower(),
view.http_method_not_allowed)
else:
handler = view.http_method_not_allowed
name = callable_name(handler)
# The priority to be used when naming the transaction is
# bit tricky. If the transaction name is already that of
# the class based view, but not the method, then we want
# the name of the method to override. This can occur
# where the class based view was registered directly in
# urls.py as the view handler. In this case we use the
# priority of 5, matching what would be used by the view
# handler so that it can override the transaction name.
#
# If however the transaction name is unrelated, we
# preferably don't want it overridden. This can happen
# where the class based view was invoked explicitly
# within an existing view handler. In this case we use
# the priority of 4 so it will not override the view
# handler name where used as the transaction name.
priority = 4
if transaction.group == 'Function':
if transaction.name == callable_name(view):
priority = 5
transaction.set_transaction_name(name, priority=priority)
with FunctionTrace(transaction, name=name):
return wrapped(*args, **kwargs)
return FunctionWrapper(wrapped, wrapper)
def instrument_django_views_generic_base(module):
module.View.dispatch = wrap_view_dispatch(module.View.dispatch)
def instrument_django_http_multipartparser(module):
wrap_function_trace(module, 'MultiPartParser.parse')
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 _nr_wrapper_BaseCommand___init___(wrapped, instance, args, kwargs):
instance.handle = FunctionTraceWrapper(instance.handle)
if hasattr(instance, 'handle_noargs'):
instance.handle_noargs = FunctionTraceWrapper(instance.handle_noargs)
return wrapped(*args, **kwargs)
def _nr_wrapper_BaseCommand_run_from_argv_(wrapped, instance, args, kwargs):
def _args(argv, *args, **kwargs):
return argv
_argv = _args(*args, **kwargs)
subcommand = _argv[1]
commands = django_settings.instrumentation.scripts.django_admin
startup_timeout = \
django_settings.instrumentation.background_task.startup_timeout
if subcommand not in commands:
return wrapped(*args, **kwargs)
application = register_application(timeout=startup_timeout)
with BackgroundTask(application, subcommand, 'Django'):
return wrapped(*args, **kwargs)
def instrument_django_core_management_base(module):
wrap_function_wrapper(module, 'BaseCommand.__init__',
_nr_wrapper_BaseCommand___init___)
wrap_function_wrapper(module, 'BaseCommand.run_from_argv',
_nr_wrapper_BaseCommand_run_from_argv_)
@function_wrapper
def _nr_wrapper_django_inclusion_tag_wrapper_(wrapped, instance,
args, kwargs):
transaction = current_transaction()
if transaction is None:
return wrapped(*args, **kwargs)
name = hasattr(wrapped, '__name__') and wrapped.__name__
if name is None:
return wrapped(*args, **kwargs)
qualname = callable_name(wrapped)
tags = django_settings.instrumentation.templates.inclusion_tag
if '*' not in tags and name not in tags and qualname not in tags:
return wrapped(*args, **kwargs)
with FunctionTrace(transaction, name, group='Template/Tag'):
return wrapped(*args, **kwargs)
@function_wrapper
def _nr_wrapper_django_inclusion_tag_decorator_(wrapped, instance,
args, kwargs):
def _bind_params(func, *args, **kwargs):
return func, args, kwargs
func, _args, _kwargs = _bind_params(*args, **kwargs)
func = _nr_wrapper_django_inclusion_tag_wrapper_(func)
return wrapped(func, *_args, **_kwargs)
def _nr_wrapper_django_template_base_Library_inclusion_tag_(wrapped,
instance, args, kwargs):
return _nr_wrapper_django_inclusion_tag_decorator_(
wrapped(*args, **kwargs))
@function_wrapper
def _nr_wrapper_django_template_base_InclusionNode_render_(wrapped,
instance, args, kwargs):
transaction = current_transaction()
if transaction is None:
return wrapped(*args, **kwargs)
if wrapped.__self__ is None:
return wrapped(*args, **kwargs)
file_name = getattr(wrapped.__self__, '_nr_file_name', None)
if file_name is None:
return wrapped(*args, **kwargs)
name = wrapped.__self__._nr_file_name
with FunctionTrace(transaction, name, 'Template/Include'):
return wrapped(*args, **kwargs)
def _nr_wrapper_django_template_base_generic_tag_compiler_(wrapped, instance,
args, kwargs):
if wrapped.__code__.co_argcount > 6:
# Django > 1.3.
def _bind_params(parser, token, params, varargs, varkw, defaults,
name, takes_context, node_class, *args, **kwargs):
return node_class
else:
# Django <= 1.3.
def _bind_params(params, defaults, name, node_class, parser, token,
*args, **kwargs):
return node_class
node_class = _bind_params(*args, **kwargs)
if node_class.__name__ == 'InclusionNode':
result = wrapped(*args, **kwargs)
result.render = (
_nr_wrapper_django_template_base_InclusionNode_render_(
result.render))
return result
return wrapped(*args, **kwargs)
def _nr_wrapper_django_template_base_Library_tag_(wrapped, instance,
args, kwargs):
def _bind_params(name=None, compile_function=None, *args, **kwargs):
return compile_function
compile_function = _bind_params(*args, **kwargs)
if not callable(compile_function):
return wrapped(*args, **kwargs)
def _get_node_class(compile_function):
node_class = None
# Django >= 1.4 uses functools.partial
if isinstance(compile_function, functools.partial):
node_class = compile_function.keywords.get('node_class')
# Django < 1.4 uses their home-grown "curry" function,
# not functools.partial.
if (hasattr(compile_function, 'func_closure')
and hasattr(compile_function, '__name__')
and compile_function.__name__ == '_curried'):
# compile_function here is generic_tag_compiler(), which has been
# curried. To get node_class, we first get the function obj, args,
# and kwargs of the curried function from the cells in
# compile_function.func_closure. But, the order of the cells
# is not consistent from platform to platform, so we need to map
# them to the variables in compile_function.__code__.co_freevars.
cells = dict(zip(compile_function.__code__.co_freevars,
(c.cell_contents for c in compile_function.func_closure)))
# node_class is the 4th arg passed to generic_tag_compiler()
if 'args' in cells and len(cells['args']) > 3:
node_class = cells['args'][3]
return node_class
node_class = _get_node_class(compile_function)
if node_class is None or node_class.__name__ != 'InclusionNode':
return wrapped(*args, **kwargs)
# Climb stack to find the file_name of the include template.
# While you only have to go up 1 frame when using python with
# extensions, pure python requires going up 2 frames.
file_name = None
stack_levels = 2
for i in range(1, stack_levels + 1):
frame = sys._getframe(i)
if ('generic_tag_compiler' in frame.f_code.co_names
and 'file_name' in frame.f_code.co_freevars):
file_name = frame.f_locals.get('file_name')
if file_name is None:
return wrapped(*args, **kwargs)
if isinstance(file_name, module_django_template_base.Template):
file_name = file_name.name
node_class._nr_file_name = file_name
return wrapped(*args, **kwargs)
def instrument_django_template_base(module):
global module_django_template_base
module_django_template_base = module
settings = global_settings()
if 'django.instrumentation.inclusion-tags.r1' in settings.feature_flag:
if hasattr(module, 'generic_tag_compiler'):
wrap_function_wrapper(module, 'generic_tag_compiler',
_nr_wrapper_django_template_base_generic_tag_compiler_)
if hasattr(module, 'Library'):
wrap_function_wrapper(module, 'Library.tag',
_nr_wrapper_django_template_base_Library_tag_)
wrap_function_wrapper(module, 'Library.inclusion_tag',
_nr_wrapper_django_template_base_Library_inclusion_tag_)