1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276
|
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
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)
|