Source code for applicationinsights.django.middleware


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)