src/pyams_apm/tween.py
changeset 0 1cc388b5cf69
child 2 e78763bdfb3f
equal deleted inserted replaced
-1:000000000000 0:1cc388b5cf69
       
     1 #
       
     2 # Copyright (c) 2008-2018 Thierry Florac <tflorac AT ulthar.net>
       
     3 # All Rights Reserved.
       
     4 #
       
     5 # This software is subject to the provisions of the Zope Public License,
       
     6 # Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
       
     7 # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
       
     8 # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
       
     9 # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
       
    10 # FOR A PARTICULAR PURPOSE.
       
    11 #
       
    12 
       
    13 __docformat__ = 'restructuredtext'
       
    14 
       
    15 
       
    16 # import standard library
       
    17 import pkg_resources
       
    18 from pkg_resources import DistributionNotFound
       
    19 
       
    20 import sys
       
    21 
       
    22 # import interfaces
       
    23 
       
    24 # import packages
       
    25 import elasticapm
       
    26 from elasticapm.utils import compat, get_url_dict
       
    27 from pyramid.settings import asbool
       
    28 from pyramid.compat import reraise
       
    29 
       
    30 
       
    31 def list_from_setting(config, setting):
       
    32     value = config.get(setting)
       
    33     if not value:
       
    34         return None
       
    35     return value.split()
       
    36 
       
    37 
       
    38 def get_data_from_request(request):
       
    39     data = {
       
    40         "headers": dict(**request.headers),
       
    41         "method": request.method,
       
    42         "socket": {
       
    43             "remote_address": request.remote_addr,
       
    44             "encrypted": request.scheme == 'https'
       
    45         },
       
    46         "cookies": dict(**request.cookies),
       
    47         "url": get_url_dict(request.url)
       
    48     }
       
    49     # remove Cookie header since the same data is in request["cookies"] as well
       
    50     data["headers"].pop("Cookie", None)
       
    51     return data
       
    52 
       
    53 
       
    54 def get_data_from_response(response):
       
    55     data = {"status_code": response.status_int}
       
    56     if response.headers:
       
    57         data["headers"] = {
       
    58             key: ";".join(response.headers.getall(key))
       
    59             for key in compat.iterkeys(response.headers)
       
    60         }
       
    61     return data
       
    62 
       
    63 
       
    64 class elastic_apm_tween_factory(object):
       
    65     """Elasticsearch APM tween factory"""
       
    66 
       
    67     def __init__(self, handler, registry):
       
    68         self.handler = handler
       
    69         self.registry = registry
       
    70         config = registry.settings
       
    71         service_version = config.get("elasticapm.service_version")
       
    72         if service_version:
       
    73             try:
       
    74                 service_version = pkg_resources.get_distribution(service_version).version
       
    75             except DistributionNotFound:
       
    76                 pass
       
    77         self.client = elasticapm.Client(
       
    78             server_url=config.get("elasticapm.server_url"),
       
    79             server_timeout=config.get("elasticapm.server_timeout"),
       
    80             name=config.get("elasticapm.name"),
       
    81             framework_name="Pyramid",
       
    82             framework_version=pkg_resources.get_distribution("pyramid").version,
       
    83             service_name=config.get("elasticapm.service_name"),
       
    84             service_version=service_version,
       
    85             secret_token=config.get("elasticapm.secret_token"),
       
    86             include_paths=list_from_setting(config, "elasticapm.include_paths"),
       
    87             exclude_paths=list_from_setting(config, "elasticapm.exclude_paths"),
       
    88             debug=asbool(config.get('elasticapm.debug'))
       
    89         )
       
    90 
       
    91     def __call__(self, request):
       
    92         self.client.begin_transaction('request')
       
    93         try:
       
    94             response = self.handler(request)
       
    95             transaction_result = response.status[0] + "xx"
       
    96             elasticapm.set_context(lambda: get_data_from_response(response), "response")
       
    97             return response
       
    98         except Exception:
       
    99             transaction_result = '5xx'
       
   100             self.client.capture_exception(
       
   101                 context={
       
   102                     "request": get_data_from_request(request)
       
   103                 },
       
   104                 handled=False,  # indicate that this exception bubbled all the way up to the user
       
   105             )
       
   106             reraise(*sys.exc_info())
       
   107         finally:
       
   108             try:
       
   109                 view_name = request.view_name
       
   110             except AttributeError:
       
   111                 view_name = ''
       
   112             transaction_name = request.matched_route.pattern if request.matched_route else view_name
       
   113             # prepend request method
       
   114             transaction_name = " ".join((request.method, transaction_name)) if transaction_name else ""
       
   115             elasticapm.set_context(lambda: get_data_from_request(request), "request")
       
   116             self.client.end_transaction(transaction_name, transaction_result)