From 12622d5847ca8de2a08593837585b395ebc0cd21 Mon Sep 17 00:00:00 2001 From: kevgliss Date: Tue, 10 Apr 2018 15:55:02 -0700 Subject: [PATCH] Adding metrics for request timings. (#1190) --- lemur/__init__.py | 40 +++++++++++++++++++++--------- lemur/decorators.py | 56 ------------------------------------------ lemur/extensions.py | 3 +++ lemur/factory.py | 4 ++- requirements-tests.txt | 8 +++--- requirements.in | 1 + requirements.txt | 7 +++--- 7 files changed, 43 insertions(+), 76 deletions(-) delete mode 100644 lemur/decorators.py diff --git a/lemur/__init__.py b/lemur/__init__.py index 6f29733c..c3661f4e 100644 --- a/lemur/__init__.py +++ b/lemur/__init__.py @@ -8,7 +8,8 @@ """ -from __future__ import absolute_import, division, print_function +import time +from flask import g, request from lemur import factory from lemur.extensions import metrics @@ -73,17 +74,6 @@ def configure_hook(app): """ from flask import jsonify from werkzeug.exceptions import HTTPException - from lemur.decorators import crossdomain - if app.config.get('CORS'): - @app.after_request - @crossdomain(origin=u"http://localhost:3000", methods=['PUT', 'HEAD', 'GET', 'POST', 'OPTIONS', 'DELETE']) - def after(response): - return response - - @app.after_request - def log_status(response): - metrics.send('status_code_{}'.format(response.status_code), 'counter', 1) - return response @app.errorhandler(Exception) def handle_error(e): @@ -93,3 +83,29 @@ def configure_hook(app): app.logger.exception(e) return jsonify(error=str(e)), code + + @app.before_request + def before_request(): + g.request_start_time = time.time() + + @app.after_request + def after_request(response): + # Return early if we don't have the start time + if not hasattr(g, 'request_start_time'): + return response + + # Get elapsed time in milliseconds + elapsed = time.time() - g.request_start_time + elapsed = int(round(1000 * elapsed)) + + # Collect request/response tags + tags = { + 'endpoint': request.endpoint, + 'request_method': request.method.lower(), + 'status_code': response.status_code + } + + # Record our response time metric + metrics.send('response_time', 'TIMER', elapsed, metric_tags=tags) + metrics.send('status_code_{}'.format(response.status_code), 'counter', 1) + return response diff --git a/lemur/decorators.py b/lemur/decorators.py deleted file mode 100644 index 7b93ec70..00000000 --- a/lemur/decorators.py +++ /dev/null @@ -1,56 +0,0 @@ -""" -.. module: lemur.decorators - :copyright: (c) 2015 by Netflix Inc., see AUTHORS for more - :license: Apache, see LICENSE for more details. -""" -from builtins import str - -from datetime import timedelta -from flask import make_response, request, current_app - -from functools import update_wrapper - - -# this is only used for dev -def crossdomain(origin=None, methods=None, headers=None, - max_age=21600, attach_to_all=True, - automatic_options=True): # pragma: no cover - if methods is not None: - methods = ', '.join(sorted(x.upper() for x in methods)) - - if headers is not None and not isinstance(headers, str): - headers = ', '.join(x.upper() for x in headers) - - if not isinstance(origin, str): - origin = ', '.join(origin) - - if isinstance(max_age, timedelta): - max_age = max_age.total_seconds() - - def get_methods(): - if methods is not None: - return methods - - options_resp = current_app.make_default_options_response() - return options_resp.headers['allow'] - - def decorator(f): - def wrapped_function(*args, **kwargs): - if automatic_options and request.method == 'OPTIONS': - resp = current_app.make_default_options_response() - else: - resp = make_response(f(*args, **kwargs)) - if not attach_to_all and request.method != 'OPTIONS': - return resp - - h = resp.headers - h['Access-Control-Allow-Origin'] = origin - h['Access-Control-Allow-Methods'] = get_methods() - h['Access-Control-Max-Age'] = str(max_age) - h['Access-Control-Allow-Headers'] = "Origin, X-Requested-With, Content-Type, Accept, Authorization " - h['Access-Control-Allow-Credentials'] = 'true' - return resp - - f.provide_automatic_options = False - return update_wrapper(wrapped_function, f) - return decorator diff --git a/lemur/extensions.py b/lemur/extensions.py index b5a18569..76abcab1 100644 --- a/lemur/extensions.py +++ b/lemur/extensions.py @@ -26,3 +26,6 @@ sentry = Sentry() from blinker import Namespace signals = Namespace() + +from flask_cors import CORS +cors = CORS() diff --git a/lemur/factory.py b/lemur/factory.py index 107fb70f..93c18e71 100644 --- a/lemur/factory.py +++ b/lemur/factory.py @@ -21,7 +21,7 @@ from flask import Flask from lemur.certificates.hooks import activate_debug_dump from lemur.common.health import mod as health -from lemur.extensions import db, migrate, principal, smtp_mail, metrics, sentry +from lemur.extensions import db, migrate, principal, smtp_mail, metrics, sentry, cors DEFAULT_BLUEPRINTS = ( @@ -124,6 +124,8 @@ def configure_extensions(app): smtp_mail.init_app(app) metrics.init_app(app) sentry.init_app(app) + app.config['CORS_HEADERS'] = 'Content-Type' + cors.init_app(app, resources=r'/api/*', headers='Content-Type', origin='*', supports_credentials=True) def configure_blueprints(app, blueprints): diff --git a/requirements-tests.txt b/requirements-tests.txt index 983aeb0e..9b359d72 100644 --- a/requirements-tests.txt +++ b/requirements-tests.txt @@ -7,9 +7,9 @@ asn1crypto==0.24.0 # via cryptography attrs==17.4.0 # via pytest aws-xray-sdk==0.95 # via moto -boto3==1.7.2 # via moto +boto3==1.7.3 # via moto boto==2.48.0 # via moto -botocore==1.10.2 # via boto3, moto, s3transfer +botocore==1.10.3 # via boto3, moto, s3transfer certifi==2018.1.18 # via requests cffi==1.11.5 # via cryptography chardet==3.0.4 # via requests @@ -42,10 +42,10 @@ pyaml==17.12.1 # via moto pycparser==2.18 # via cffi pyflakes==1.6.0 pytest-flask==0.10.0 -pytest-mock==1.8.0 +pytest-mock==1.9.0 pytest==3.5.0 python-dateutil==2.6.1 # via botocore, faker, freezegun, moto -pytz==2018.3 # via moto +pytz==2018.4 # via moto pyyaml==3.12 # via pyaml requests-mock==1.4.0 requests==2.18.4 # via aws-xray-sdk, docker, moto, requests-mock diff --git a/requirements.in b/requirements.in index 4dad196d..90a48170 100644 --- a/requirements.in +++ b/requirements.in @@ -11,6 +11,7 @@ Flask-RESTful==0.3.6 Flask-Script==2.0.6 Flask-SQLAlchemy Flask==0.12 +Flask-Cors future gunicorn inflection diff --git a/requirements.txt b/requirements.txt index dafb2bdf..1018354f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -12,13 +12,14 @@ arrow==0.12.1 asn1crypto==0.24.0 # via cryptography bcrypt==3.1.4 # via flask-bcrypt, paramiko blinker==1.4 # via flask-mail, flask-principal, raven -boto3==1.7.2 -botocore==1.10.2 # via boto3, s3transfer +boto3==1.7.3 +botocore==1.10.3 # via boto3, s3transfer cffi==1.11.5 # via bcrypt, cryptography, pynacl click==6.7 # via flask cryptography==2.2.2 docutils==0.14 # via botocore flask-bcrypt==0.7.1 +flask-cors==3.0.3 flask-mail==0.9.1 flask-migrate==2.1.1 flask-principal==0.4.0 @@ -55,7 +56,7 @@ pyrfc3339==1.0 # via acme python-dateutil==2.6.1 # via alembic, arrow, botocore python-editor==1.0.3 # via alembic python-ldap==3.0.0 -pytz==2018.3 # via acme, flask-restful, pyrfc3339 +pytz==2018.4 # via acme, flask-restful, pyrfc3339 raven[flask]==6.6.0 requests[security]==2.11.1 retrying==1.3.3