1import datetime 2import re 3import uuid 4import applicationinsights 5 6class WSGIApplication(object): 7 """ This class represents a WSGI wrapper that enables request telemetry for existing WSGI applications. The request 8 telemetry will be sent to Application Insights service using the supplied instrumentation key. 9 10 .. code:: python 11 12 from applicationinsights.requests import WSGIApplication 13 from paste.httpserver import serve 14 from pyramid.response import Response 15 from pyramid.view import view_config 16 17 @view_config() 18 def hello(request): 19 return Response('Hello') 20 21 if __name__ == '__main__': 22 from pyramid.config import Configurator 23 config = Configurator() 24 config.scan() 25 app = config.make_wsgi_app() 26 27 # Enable Application Insights middleware 28 app = WSGIApplication('<YOUR INSTRUMENTATION KEY GOES HERE>', app, common_properties={'service': 'hello_world_service'}) 29 30 serve(app, host='0.0.0.0') 31 """ 32 def __init__(self, instrumentation_key, wsgi_application, *args, **kwargs): 33 """ 34 Initialize a new instance of the class. 35 36 Args: 37 instrumentation_key (str). the instrumentation key to use while sending telemetry to the service.\n 38 wsgi_application (func). the WSGI application that we're wrapping. 39 """ 40 if not instrumentation_key: 41 raise Exception('Instrumentation key was required but not provided') 42 if not wsgi_application: 43 raise Exception('WSGI application was required but not provided') 44 telemetry_channel = kwargs.pop('telemetry_channel', None) 45 if not telemetry_channel: 46 sender = applicationinsights.channel.AsynchronousSender() 47 queue = applicationinsights.channel.AsynchronousQueue(sender) 48 telemetry_channel = applicationinsights.channel.TelemetryChannel(None, queue) 49 self.client = applicationinsights.TelemetryClient(instrumentation_key, telemetry_channel) 50 self.client.context.device.type = "PC" 51 self._wsgi_application = wsgi_application 52 self._common_properties = kwargs.pop('common_properties', {}) 53 54 def flush(self): 55 """Flushes the queued up telemetry to the service. 56 """ 57 self.client.flush() 58 59 def __call__(self, environ, start_response): 60 """Callable implementation for WSGI middleware. 61 62 Args: 63 environ (dict). a dictionary containing all WSGI environment properties for this request.\n 64 start_response (func). a function used to store the status, HTTP headers to be sent to the client and optional exception information. 65 66 Returns: 67 (obj). the response to send back to the client. 68 """ 69 start_time = datetime.datetime.utcnow() 70 name = environ.get('PATH_INFO') or '/' 71 closure = {'status': '200 OK'} 72 http_method = environ.get('REQUEST_METHOD', 'GET') 73 74 self.client.context.operation.id = str(uuid.uuid4()) 75 # operation.parent_id ought to be the request id (not the operation id, but we don't have it yet) 76 self.client.context.operation.name = http_method + ' ' + name 77 78 def status_interceptor(status_string, headers_array, exc_info=None): 79 closure['status'] = status_string 80 start_response(status_string, headers_array, exc_info) 81 82 for part in self._wsgi_application(environ, status_interceptor): 83 yield part 84 85 success = True 86 response_match = re.match(r'\s*(?P<code>\d+)', closure['status']) 87 if response_match: 88 response_code = response_match.group('code') 89 if int(response_code) >= 400: 90 success = False 91 else: 92 response_code = closure['status'] 93 success = False 94 95 url = name 96 query_string = environ.get('QUERY_STRING') 97 if query_string: 98 url += '?' + query_string 99 100 scheme = environ.get('wsgi.url_scheme', 'http') 101 host = environ.get('HTTP_HOST', environ.get('SERVER_NAME', 'unknown')) 102 103 url = scheme + '://' + host + url 104 105 end_time = datetime.datetime.utcnow() 106 duration = int((end_time - start_time).total_seconds() * 1000) 107 108 self.client.track_request(name, url, success, start_time.isoformat() + 'Z', duration, response_code, http_method, self._common_properties) 109