import datetime
import inspect
import sys
import time
import uuid
from django.http import Http404
import applicationinsights
from applicationinsights.channel import contracts
from . import common
try:
basestring # Python 2
except NameError: # Python 3
basestring = (str, )
# Pick a function to measure time; starting with 3.3, time.monotonic is available.
try:
TIME_FUNC = time.monotonic
except AttributeError:
TIME_FUNC = time.time
[docs]class ApplicationInsightsMiddleware(object):
"""This class is a Django middleware that automatically enables request and exception telemetry. Django versions
1.7 and newer are supported.
To enable, add this class to your settings.py file in MIDDLEWARE_CLASSES (pre-1.10) or MIDDLEWARE (1.10 and newer):
.. code:: python
# If on Django < 1.10
MIDDLEWARE_CLASSES = [
# ... or whatever is below for you ...
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
# ... or whatever is above for you ...
'applicationinsights.django.ApplicationInsightsMiddleware', # Add this middleware to the end
]
# If on Django >= 1.10
MIDDLEWARE = [
# ... or whatever is below for you ...
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
# ... or whatever is above for you ...
'applicationinsights.django.ApplicationInsightsMiddleware', # Add this middleware to the end
]
And then, add the following to your settings.py file:
.. code:: python
APPLICATION_INSIGHTS = {
# (required) Your Application Insights instrumentation key
'ikey': "00000000-0000-0000-0000-000000000000",
# (optional) By default, request names are logged as the request method
# and relative path of the URL. To log the fully-qualified view names
# instead, set this to True. Defaults to False.
'use_view_name': True,
# (optional) To log arguments passed into the views as custom properties,
# set this to True. Defaults to False.
'record_view_arguments': True,
# (optional) Exceptions are logged by default, to disable, set this to False.
'log_exceptions': False,
# (optional) Events are submitted to Application Insights asynchronously.
# send_interval specifies how often the queue is checked for items to submit.
# send_time specifies how long the sender waits for new input before recycling
# the background thread.
'send_interval': 1.0, # Check every second
'send_time': 3.0, # Wait up to 3 seconds for an event
# (optional, uncommon) If you must send to an endpoint other than the
# default endpoint, specify it here:
'endpoint': "https://dc.services.visualstudio.com/v2/track",
}
Once these are in place, each request will have an `appinsights` object placed on it.
This object will have the following properties:
* `client`: This is an instance of the :class:`applicationinsights.TelemetryClient` type, which will
submit telemetry to the same instrumentation key, and will parent each telemetry item to the current
request.
* `request`: This is the :class:`applicationinsights.channel.contracts.RequestData` instance for the
current request. You can modify properties on this object during the handling of the current request.
It will be submitted when the request has finished.
* `context`: This is the :class:`applicationinsights.channel.TelemetryContext` object for the current
ApplicationInsights sender.
These properties will be present even when `DEBUG` is `True`, but it may not submit telemetry unless
`debug_ikey` is set in `APPLICATION_INSIGHTS`, above.
"""
def __init__(self, get_response=None):
self.get_response = get_response
# Get configuration
self._settings = common.load_settings()
self._client = common.create_client(self._settings)
# Pre-1.10 handler
def process_request(self, request):
# Populate context object onto request
addon = RequestAddon(self._client)
data = addon.request
context = addon.context
request.appinsights = addon
# Basic request properties
data.start_time = datetime.datetime.utcnow().isoformat() + "Z"
data.http_method = request.method
data.url = request.build_absolute_uri()
data.name = "%s %s" % (request.method, request.path)
context.operation.name = data.name
context.operation.id = data.id
context.location.ip = request.META.get('REMOTE_ADDR', '')
context.user.user_agent = request.META.get('HTTP_USER_AGENT', '')
# User
if hasattr(request, 'user'):
if request.user is not None and not request.user.is_anonymous and request.user.is_authenticated:
context.user.account_id = request.user.get_short_name()
# Run and time the request
addon.start_stopwatch()
return None
# Pre-1.10 handler
def process_response(self, request, response):
if hasattr(request, 'appinsights'):
addon = request.appinsights
duration = addon.measure_duration()
data = addon.request
context = addon.context
# Fill in data from the response
data.duration = addon.measure_duration()
data.response_code = response.status_code
data.success = response.status_code < 400 or response.status_code == 401
# Submit and return
self._client.channel.write(data, context)
return response
# 1.10 and up...
def __call__(self, request):
self.process_request(request)
response = self.get_response(request)
self.process_response(request, response)
return response
def process_view(self, request, view_func, view_args, view_kwargs):
if not hasattr(request, "appinsights"):
return None
data = request.appinsights.request
context = request.appinsights.context
# Operation name is the method + url by default (set in __call__),
# If use_view_name is set, then we'll look up the name of the view.
if self._settings.use_view_name:
mod = inspect.getmodule(view_func)
if hasattr(view_func, "__name__"):
name = view_func.__name__
elif hasattr(view_func, "__class__") and hasattr(view_func.__class__, "__name__"):
name = view_func.__class__.__name__
else:
name = "<unknown>"
if mod:
opname = "%s %s.%s" % (data.http_method, mod.__name__, name)
else:
opname = "%s %s" % (data.http_method, name)
data.name = opname
context.operation.name = opname
# Populate the properties with view arguments
if self._settings.record_view_arguments:
for i, arg in enumerate(view_args):
data.properties['view_arg_' + str(i)] = arg_to_str(arg)
for k, v in view_kwargs.items():
data.properties['view_arg_' + k] = arg_to_str(v)
return None
def process_exception(self, request, exception):
if not self._settings.log_exceptions:
return None
if type(exception) is Http404:
return None
_, _, tb = sys.exc_info()
if tb is None or exception is None:
# No actual traceback or exception info, don't bother logging.
return None
client = applicationinsights.TelemetryClient(
self._client.context.instrumentation_key, self._client.channel)
if hasattr(request, 'appinsights'):
client.context.operation.parent_id = request.appinsights.request.id
client.track_exception(type(exception), exception, tb)
return None
def process_template_response(self, request, response):
if hasattr(request, 'appinsights') and hasattr(response, 'template_name'):
data = request.appinsights.request
data.properties['template_name'] = response.template_name
return response
class RequestAddon(object):
def __init__(self, client):
self._baseclient = client
self._client = None
self.request = contracts.RequestData()
self.request.id = str(uuid.uuid4())
self.context = applicationinsights.channel.TelemetryContext()
self.context.instrumentation_key = client.context.instrumentation_key
self.context.operation.id = self.request.id
self._process_start_time = None
@property
def client(self):
if self._client is None:
# Create a client that submits telemetry parented to the request.
self._client = applicationinsights.TelemetryClient(
self.context.instrumentation_key, self._baseclient.channel)
self._client.context.operation.parent_id = self.context.operation.id
return self._client
def start_stopwatch(self):
self._process_start_time = TIME_FUNC()
def measure_duration(self):
end_time = TIME_FUNC()
return ms_to_duration(int((end_time - self._process_start_time) * 1000))
def ms_to_duration(n):
duration_parts = []
for multiplier in [1000, 60, 60, 24]:
duration_parts.append(n % multiplier)
n //= multiplier
duration_parts.reverse()
duration = "%02d:%02d:%02d.%03d" % tuple(duration_parts)
if n:
duration = "%d.%s" % (n, duration)
return duration
def arg_to_str(arg):
if isinstance(arg, basestring):
return arg
if isinstance(arg, int):
return str(arg)
return repr(arg)