Spaces:
Build error
Build error
Validify-testbot-1
/
botbuilder-python
/libraries
/botbuilder-applicationinsights
/botbuilder
/applicationinsights
/django
/middleware.py
# Copyright (c) Microsoft Corporation. All rights reserved. | |
# Licensed under the MIT License. | |
import datetime | |
import inspect | |
import sys | |
import time | |
import uuid | |
from applicationinsights.channel import TelemetryContext, contracts | |
from django.http import Http404 | |
from . import common | |
try: | |
basestring # Python 2 | |
except NameError: # Python 3 | |
basestring = (str,) # pylint: disable=invalid-name | |
# 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: | |
"""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 ... | |
'botbuilder.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 ... | |
'botbuilder.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): # pylint: disable=useless-return | |
# 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 | |
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.track(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(): # pylint: disable=invalid-name | |
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 isinstance(exception, Http404): | |
return None | |
_, _, tb = sys.exc_info() # pylint: disable=invalid-name | |
if tb is None or exception is None: | |
# No actual traceback or exception info, don't bother logging. | |
return None | |
client = common.get_telemetry_client_with_processor( | |
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: | |
def __init__(self, client): | |
self._baseclient = client | |
self._client = None | |
self.request = contracts.RequestData() | |
self.request.id = str(uuid.uuid4()) | |
self.context = TelemetryContext() | |
self.context.instrumentation_key = client.context.instrumentation_key | |
self.context.operation.id = self.request.id | |
self._process_start_time = None | |
def client(self): | |
if self._client is None: | |
# Create a client that submits telemetry parented to the request. | |
self._client = common.get_telemetry_client_with_processor( | |
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): # pylint: disable=invalid-name | |
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) | |