# HG changeset patch # User Thierry Florac # Date 1537285123 -7200 # Node ID 1cc388b5cf690bda84016ddd555226a06c646f9b Version 0.1.0 diff -r 000000000000 -r 1cc388b5cf69 .hgignore --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.hgignore Tue Sep 18 17:38:43 2018 +0200 @@ -0,0 +1,19 @@ + +syntax: regexp +^develop-eggs$ +syntax: regexp +^parts$ +syntax: regexp +^bin$ +syntax: regexp +^\.installed\.cfg$ +syntax: regexp +^\.settings$ +syntax: regexp +^build$ +syntax: regexp +^dist$ +syntax: regexp +^\.idea$ +syntax: regexp +.*\.pyc$ diff -r 000000000000 -r 1cc388b5cf69 LICENSE --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/LICENSE Tue Sep 18 17:38:43 2018 +0200 @@ -0,0 +1,42 @@ +Zope Public License (ZPL) Version 2.1 +===================================== + +A copyright notice accompanies this license document that identifies +the copyright holders. + +This license has been certified as open source. It has also been designated +as GPL compatible by the Free Software Foundation (FSF). + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + 1. Redistributions in source code must retain the accompanying copyright + notice, this list of conditions, and the following disclaimer. + 2. Redistributions in binary form must reproduce the accompanying copyright + notice, this list of conditions, and the following disclaimer in the + documentation and/or other materials provided with the distribution. + 3. Names of the copyright holders must not be used to endorse or promote + products derived from this software without prior written permission + from the copyright holders. + 4. The right to distribute this software or to use it for any purpose does + not give you the right to use Servicemarks (sm) or Trademarks (tm) of the + copyright holders. Use of them is covered by separate agreement with the + copyright holders. + 5. If any files are modified, you must cause the modified files to carry + prominent notices stating that you changed the files and the date of any + change. + + +Disclaimer +========== + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY EXPRESSED +OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO +EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. diff -r 000000000000 -r 1cc388b5cf69 MANIFEST.in --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/MANIFEST.in Tue Sep 18 17:38:43 2018 +0200 @@ -0,0 +1,5 @@ +include *.txt +recursive-include docs * +recursive-include src * +global-exclude *.pyc +global-exclude *.*~ diff -r 000000000000 -r 1cc388b5cf69 bootstrap.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bootstrap.py Tue Sep 18 17:38:43 2018 +0200 @@ -0,0 +1,210 @@ +############################################################################## +# +# Copyright (c) 2006 Zope Foundation and Contributors. +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# +############################################################################## +"""Bootstrap a buildout-based project + +Simply run this script in a directory containing a buildout.cfg. +The script accepts buildout command-line options, so you can +use the -c option to specify an alternate configuration file. +""" + +import os +import shutil +import sys +import tempfile + +from optparse import OptionParser + +__version__ = '2015-07-01' +# See zc.buildout's changelog if this version is up to date. + +tmpeggs = tempfile.mkdtemp(prefix='bootstrap-') + +usage = '''\ +[DESIRED PYTHON FOR BUILDOUT] bootstrap.py [options] + +Bootstraps a buildout-based project. + +Simply run this script in a directory containing a buildout.cfg, using the +Python that you want bin/buildout to use. + +Note that by using --find-links to point to local resources, you can keep +this script from going over the network. +''' + +parser = OptionParser(usage=usage) +parser.add_option("--version", + action="store_true", default=False, + help=("Return bootstrap.py version.")) +parser.add_option("-t", "--accept-buildout-test-releases", + dest='accept_buildout_test_releases', + action="store_true", default=False, + help=("Normally, if you do not specify a --buildout-version, " + "the bootstrap script and buildout gets the newest " + "*final* versions of zc.buildout and its recipes and " + "extensions for you. If you use this flag, " + "bootstrap and buildout will get the newest releases " + "even if they are alphas or betas.")) +parser.add_option("-c", "--config-file", + help=("Specify the path to the buildout configuration " + "file to be used.")) +parser.add_option("-f", "--find-links", + help=("Specify a URL to search for buildout releases")) +parser.add_option("--allow-site-packages", + action="store_true", default=False, + help=("Let bootstrap.py use existing site packages")) +parser.add_option("--buildout-version", + help="Use a specific zc.buildout version") +parser.add_option("--setuptools-version", + help="Use a specific setuptools version") +parser.add_option("--setuptools-to-dir", + help=("Allow for re-use of existing directory of " + "setuptools versions")) + +options, args = parser.parse_args() +if options.version: + print("bootstrap.py version %s" % __version__) + sys.exit(0) + + +###################################################################### +# load/install setuptools + +try: + from urllib.request import urlopen +except ImportError: + from urllib2 import urlopen + +ez = {} +if os.path.exists('ez_setup.py'): + exec(open('ez_setup.py').read(), ez) +else: + exec(urlopen('https://bootstrap.pypa.io/ez_setup.py').read(), ez) + +if not options.allow_site_packages: + # ez_setup imports site, which adds site packages + # this will remove them from the path to ensure that incompatible versions + # of setuptools are not in the path + import site + # inside a virtualenv, there is no 'getsitepackages'. + # We can't remove these reliably + if hasattr(site, 'getsitepackages'): + for sitepackage_path in site.getsitepackages(): + # Strip all site-packages directories from sys.path that + # are not sys.prefix; this is because on Windows + # sys.prefix is a site-package directory. + if sitepackage_path != sys.prefix: + sys.path[:] = [x for x in sys.path + if sitepackage_path not in x] + +setup_args = dict(to_dir=tmpeggs, download_delay=0) + +if options.setuptools_version is not None: + setup_args['version'] = options.setuptools_version +if options.setuptools_to_dir is not None: + setup_args['to_dir'] = options.setuptools_to_dir + +ez['use_setuptools'](**setup_args) +import setuptools +import pkg_resources + +# This does not (always?) update the default working set. We will +# do it. +for path in sys.path: + if path not in pkg_resources.working_set.entries: + pkg_resources.working_set.add_entry(path) + +###################################################################### +# Install buildout + +ws = pkg_resources.working_set + +setuptools_path = ws.find( + pkg_resources.Requirement.parse('setuptools')).location + +# Fix sys.path here as easy_install.pth added before PYTHONPATH +cmd = [sys.executable, '-c', + 'import sys; sys.path[0:0] = [%r]; ' % setuptools_path + + 'from setuptools.command.easy_install import main; main()', + '-mZqNxd', tmpeggs] + +find_links = os.environ.get( + 'bootstrap-testing-find-links', + options.find_links or + ('http://downloads.buildout.org/' + if options.accept_buildout_test_releases else None) + ) +if find_links: + cmd.extend(['-f', find_links]) + +requirement = 'zc.buildout' +version = options.buildout_version +if version is None and not options.accept_buildout_test_releases: + # Figure out the most recent final version of zc.buildout. + import setuptools.package_index + _final_parts = '*final-', '*final' + + def _final_version(parsed_version): + try: + return not parsed_version.is_prerelease + except AttributeError: + # Older setuptools + for part in parsed_version: + if (part[:1] == '*') and (part not in _final_parts): + return False + return True + + index = setuptools.package_index.PackageIndex( + search_path=[setuptools_path]) + if find_links: + index.add_find_links((find_links,)) + req = pkg_resources.Requirement.parse(requirement) + if index.obtain(req) is not None: + best = [] + bestv = None + for dist in index[req.project_name]: + distv = dist.parsed_version + if _final_version(distv): + if bestv is None or distv > bestv: + best = [dist] + bestv = distv + elif distv == bestv: + best.append(dist) + if best: + best.sort() + version = best[-1].version +if version: + requirement = '=='.join((requirement, version)) +cmd.append(requirement) + +import subprocess +if subprocess.call(cmd) != 0: + raise Exception( + "Failed to execute command:\n%s" % repr(cmd)[1:-1]) + +###################################################################### +# Import and run buildout + +ws.add_entry(tmpeggs) +ws.require(requirement) +import zc.buildout.buildout + +if not [a for a in args if '=' not in a]: + args.append('bootstrap') + +# if -c was provided, we push it back into args for buildout' main function +if options.config_file is not None: + args[0:0] = ['-c', options.config_file] + +zc.buildout.buildout.main(args) +shutil.rmtree(tmpeggs) diff -r 000000000000 -r 1cc388b5cf69 buildout.cfg --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/buildout.cfg Tue Sep 18 17:38:43 2018 +0200 @@ -0,0 +1,51 @@ +[buildout] +eggs-directory = /var/local/env/pyams/eggs +extends = http://download.ztfy.org/pyams/pyams-dev.cfg +find-links = http://download.ztfy.org/eggs + +socket-timeout = 3 +show-picked-versions = true +newest = false + +versions = versions +newest = false +#allow-picked-versions = false + +src = src +develop = + . + +parts = + package + i18n + pyflakes + test + +[package] +recipe = zc.recipe.egg +eggs = + +[i18n] +recipe = zc.recipe.egg +eggs = + babel + lingua + +[pyflakes] +recipe = zc.recipe.egg +eggs = pyflakes +scripts = pyflakes +entry-points = pyflakes=pyflakes.scripts.pyflakes:main +initialization = if not sys.argv[1:]: sys.argv[1:] = ["${buildout:src}"] + +[pyflakesrun] +recipe = collective.recipe.cmd +on_install = true +cmds = ${buildout:develop}/bin/${pyflakes:scripts} + +[test] +recipe = zc.recipe.testrunner +eggs = pyams_apm [test] + +[versions] +pyams_apm = 0.1.0 diff -r 000000000000 -r 1cc388b5cf69 docs/HISTORY.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/docs/HISTORY.txt Tue Sep 18 17:38:43 2018 +0200 @@ -0,0 +1,6 @@ +History +======= + +0.1.0 +----- + - first preview release diff -r 000000000000 -r 1cc388b5cf69 docs/README.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/docs/README.txt Tue Sep 18 17:38:43 2018 +0200 @@ -0,0 +1,3 @@ + +PyAMS APM +========= diff -r 000000000000 -r 1cc388b5cf69 setup.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/setup.py Tue Sep 18 17:38:43 2018 +0200 @@ -0,0 +1,63 @@ +# +# Copyright (c) 2008-2015 Thierry Florac +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# + +""" +This module contains pyams_apm package +""" +import os +from setuptools import setup, find_packages + +DOCS = os.path.join(os.path.dirname(__file__), + 'docs') + +README = os.path.join(DOCS, 'README.txt') +HISTORY = os.path.join(DOCS, 'HISTORY.txt') + +version = '0.1.0' +long_description = open(README).read() + '\n\n' + open(HISTORY).read() + +tests_require = [] + +setup(name='pyams_apm', + version=version, + description="PyAMS Elasticsearch APM integration package", + long_description=long_description, + classifiers=[ + "License :: OSI Approved :: Zope Public License", + "Development Status :: 4 - Beta", + "Programming Language :: Python", + "Framework :: Pyramid", + "Topic :: Software Development :: Libraries :: Python Modules", + ], + keywords='Pyramid PyAMS Elasticsearch APM', + author='Thierry Florac', + author_email='tflorac@ulthar.net', + url='http://hg.ztfy.org/pyams/pyams_apm', + license='ZPL', + packages=find_packages('src'), + package_dir={'': 'src'}, + namespace_packages=[], + include_package_data=True, + package_data={'': ['*.zcml', '*.txt', '*.pt', '*.pot', '*.po', '*.mo', '*.png', '*.gif', '*.jpeg', '*.jpg', + '*.css', '*.js']}, + zip_safe=False, + # uncomment this to be able to run tests with setup.py + test_suite="pyams_content.tests.test_utilsdocs.test_suite", + tests_require=tests_require, + extras_require=dict(test=tests_require), + install_requires=[ + 'setuptools', + # -*- Extra requirements: -*- + 'elastic-apm', + 'pyramid' + ], + entry_points={}) diff -r 000000000000 -r 1cc388b5cf69 src/pyams_apm.egg-info/PKG-INFO --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_apm.egg-info/PKG-INFO Tue Sep 18 17:38:43 2018 +0200 @@ -0,0 +1,28 @@ +Metadata-Version: 1.1 +Name: pyams-apm +Version: 0.1.0 +Summary: PyAMS Elasticsearch APM integration package +Home-page: http://hg.ztfy.org/pyams/pyams_apm +Author: Thierry Florac +Author-email: tflorac@ulthar.net +License: ZPL +Description-Content-Type: UNKNOWN +Description: + PyAMS APM + ========= + + + History + ======= + + 0.1.0 + ----- + - first preview release + +Keywords: Pyramid PyAMS Elasticsearch APM +Platform: UNKNOWN +Classifier: License :: OSI Approved :: Zope Public License +Classifier: Development Status :: 4 - Beta +Classifier: Programming Language :: Python +Classifier: Framework :: Pyramid +Classifier: Topic :: Software Development :: Libraries :: Python Modules diff -r 000000000000 -r 1cc388b5cf69 src/pyams_apm.egg-info/SOURCES.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_apm.egg-info/SOURCES.txt Tue Sep 18 17:38:43 2018 +0200 @@ -0,0 +1,16 @@ +MANIFEST.in +setup.py +docs/HISTORY.txt +docs/README.txt +src/pyams_apm/__init__.py +src/pyams_apm/include.py +src/pyams_apm/tween.py +src/pyams_apm.egg-info/PKG-INFO +src/pyams_apm.egg-info/SOURCES.txt +src/pyams_apm.egg-info/dependency_links.txt +src/pyams_apm.egg-info/namespace_packages.txt +src/pyams_apm.egg-info/not-zip-safe +src/pyams_apm.egg-info/requires.txt +src/pyams_apm.egg-info/top_level.txt +src/pyams_apm/packages/__init__.py +src/pyams_apm/packages/chameleon.py \ No newline at end of file diff -r 000000000000 -r 1cc388b5cf69 src/pyams_apm.egg-info/dependency_links.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_apm.egg-info/dependency_links.txt Tue Sep 18 17:38:43 2018 +0200 @@ -0,0 +1,1 @@ + diff -r 000000000000 -r 1cc388b5cf69 src/pyams_apm.egg-info/namespace_packages.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_apm.egg-info/namespace_packages.txt Tue Sep 18 17:38:43 2018 +0200 @@ -0,0 +1,1 @@ + diff -r 000000000000 -r 1cc388b5cf69 src/pyams_apm.egg-info/not-zip-safe --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_apm.egg-info/not-zip-safe Tue Sep 18 17:38:43 2018 +0200 @@ -0,0 +1,1 @@ + diff -r 000000000000 -r 1cc388b5cf69 src/pyams_apm.egg-info/requires.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_apm.egg-info/requires.txt Tue Sep 18 17:38:43 2018 +0200 @@ -0,0 +1,5 @@ +setuptools +elastic-apm +pyramid + +[test] diff -r 000000000000 -r 1cc388b5cf69 src/pyams_apm.egg-info/top_level.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_apm.egg-info/top_level.txt Tue Sep 18 17:38:43 2018 +0200 @@ -0,0 +1,1 @@ +pyams_apm diff -r 000000000000 -r 1cc388b5cf69 src/pyams_apm/__init__.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_apm/__init__.py Tue Sep 18 17:38:43 2018 +0200 @@ -0,0 +1,26 @@ +# +# Copyright (c) 2008-2018 Thierry Florac +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# + +__docformat__ = 'restructuredtext' + + +# import standard library + +# import interfaces + +# import packages + + +def includeme(config): + """pyams_apm features include""" + from .include import include_package + include_package(config) diff -r 000000000000 -r 1cc388b5cf69 src/pyams_apm/include.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_apm/include.py Tue Sep 18 17:38:43 2018 +0200 @@ -0,0 +1,41 @@ +# +# Copyright (c) 2008-2018 Thierry Florac +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# + +__docformat__ = 'restructuredtext' + + +# import standard library + +# import interfaces +from pyramid.interfaces import IApplicationCreated + +# import packages +import elasticapm +from elasticapm.instrumentation import register +from pyramid.events import subscriber + + +@subscriber(IApplicationCreated) +def handle_apm_application(event): + register.register('pyams_apm.packages.chameleon.ChameleonCookingInstrumentation') + register.register('pyams_apm.packages.chameleon.ChameleonRenderingInstrumentation') + elasticapm.instrument() + + +def include_package(config): + """Pyramid package include""" + + # add APM tween + config.add_tween('pyams_apm.tween.elastic_apm_tween_factory') + + # scan package + config.scan() diff -r 000000000000 -r 1cc388b5cf69 src/pyams_apm/packages/__init__.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_apm/packages/__init__.py Tue Sep 18 17:38:43 2018 +0200 @@ -0,0 +1,20 @@ +# +# Copyright (c) 2008-2018 Thierry Florac +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# + +__docformat__ = 'restructuredtext' + + +# import standard library + +# import interfaces + +# import packages diff -r 000000000000 -r 1cc388b5cf69 src/pyams_apm/packages/chameleon.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_apm/packages/chameleon.py Tue Sep 18 17:38:43 2018 +0200 @@ -0,0 +1,44 @@ +# +# Copyright (c) 2008-2018 Thierry Florac +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# + +__docformat__ = 'restructuredtext' + + +# import standard library + +# import interfaces + +# import packages +from elasticapm.instrumentation.packages.base import AbstractInstrumentedModule +from elasticapm.traces import capture_span + + +class ChameleonCookingInstrumentation(AbstractInstrumentedModule): + name = "chameleon" + + instrument_list = [("chameleon.template", "BaseTemplate.cook")] + + def call(self, module, method, wrapped, instance, args, kwargs): + with capture_span('COOK', "template.chameleon.cook", + {'filename': instance.filename}, leaf=True): + return wrapped(*args, **kwargs) + + +class ChameleonRenderingInstrumentation(AbstractInstrumentedModule): + name = "chameleon" + + instrument_list = [("chameleon.template", "BaseTemplate.render")] + + def call(self, module, method, wrapped, instance, args, kwargs): + with capture_span('RENDER', "template.chameleon.render", + {'filename': instance.filename}, leaf=True): + return wrapped(*args, **kwargs) diff -r 000000000000 -r 1cc388b5cf69 src/pyams_apm/tween.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_apm/tween.py Tue Sep 18 17:38:43 2018 +0200 @@ -0,0 +1,116 @@ +# +# Copyright (c) 2008-2018 Thierry Florac +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# + +__docformat__ = 'restructuredtext' + + +# import standard library +import pkg_resources +from pkg_resources import DistributionNotFound + +import sys + +# import interfaces + +# import packages +import elasticapm +from elasticapm.utils import compat, get_url_dict +from pyramid.settings import asbool +from pyramid.compat import reraise + + +def list_from_setting(config, setting): + value = config.get(setting) + if not value: + return None + return value.split() + + +def get_data_from_request(request): + data = { + "headers": dict(**request.headers), + "method": request.method, + "socket": { + "remote_address": request.remote_addr, + "encrypted": request.scheme == 'https' + }, + "cookies": dict(**request.cookies), + "url": get_url_dict(request.url) + } + # remove Cookie header since the same data is in request["cookies"] as well + data["headers"].pop("Cookie", None) + return data + + +def get_data_from_response(response): + data = {"status_code": response.status_int} + if response.headers: + data["headers"] = { + key: ";".join(response.headers.getall(key)) + for key in compat.iterkeys(response.headers) + } + return data + + +class elastic_apm_tween_factory(object): + """Elasticsearch APM tween factory""" + + def __init__(self, handler, registry): + self.handler = handler + self.registry = registry + config = registry.settings + service_version = config.get("elasticapm.service_version") + if service_version: + try: + service_version = pkg_resources.get_distribution(service_version).version + except DistributionNotFound: + pass + self.client = elasticapm.Client( + server_url=config.get("elasticapm.server_url"), + server_timeout=config.get("elasticapm.server_timeout"), + name=config.get("elasticapm.name"), + framework_name="Pyramid", + framework_version=pkg_resources.get_distribution("pyramid").version, + service_name=config.get("elasticapm.service_name"), + service_version=service_version, + secret_token=config.get("elasticapm.secret_token"), + include_paths=list_from_setting(config, "elasticapm.include_paths"), + exclude_paths=list_from_setting(config, "elasticapm.exclude_paths"), + debug=asbool(config.get('elasticapm.debug')) + ) + + def __call__(self, request): + self.client.begin_transaction('request') + try: + response = self.handler(request) + transaction_result = response.status[0] + "xx" + elasticapm.set_context(lambda: get_data_from_response(response), "response") + return response + except Exception: + transaction_result = '5xx' + self.client.capture_exception( + context={ + "request": get_data_from_request(request) + }, + handled=False, # indicate that this exception bubbled all the way up to the user + ) + reraise(*sys.exc_info()) + finally: + try: + view_name = request.view_name + except AttributeError: + view_name = '' + transaction_name = request.matched_route.pattern if request.matched_route else view_name + # prepend request method + transaction_name = " ".join((request.method, transaction_name)) if transaction_name else "" + elasticapm.set_context(lambda: get_data_from_request(request), "request") + self.client.end_transaction(transaction_name, transaction_result)