From 6a541e06628fa6a43f0b50e87e7a96ec982ed387 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= Date: Thu, 28 Sep 2017 13:28:12 +0200 Subject: [PATCH] Improved bugsnag reporting - Include release stage, which should be 'production' or 'development', and gets postfixed by '-debug' when running in debug mode. - Properly logging remote IP address when proxied through HAProxy; - Log user ID, email, username, roles, and capabilities; - Remove authentication tokens from logged session; - Log request data and JSON separately. - Added request endpoint. --- pillar/__init__.py | 9 ++++++-- pillar/bugsnag_extra.py | 51 +++++++++++++++++++++++++++++++++++++++++ pillar/config.py | 1 + 3 files changed, 59 insertions(+), 2 deletions(-) create mode 100644 pillar/bugsnag_extra.py diff --git a/pillar/__init__.py b/pillar/__init__.py index 72c343ad..c7085df9 100644 --- a/pillar/__init__.py +++ b/pillar/__init__.py @@ -183,9 +183,14 @@ class PillarServer(Eve): import bugsnag from bugsnag.handlers import BugsnagHandler + release_stage = self.config.get('BUGSNAG_RELEASE_STAGE', 'unconfigured') + if self.config.get('DEBUG'): + release_stage += '-debug' + bugsnag.configure( api_key=bugsnag_api_key, project_root="/data/git/pillar/pillar", + release_stage=release_stage ) bs_handler = BugsnagHandler() @@ -196,9 +201,9 @@ class PillarServer(Eve): # but it passes the app to the connect() call, which causes an # error. Since we only have one app, we can do without. from flask import got_request_exception - from bugsnag.flask import add_flask_request_to_notification + from . import bugsnag_extra - bugsnag.before_notify(add_flask_request_to_notification) + bugsnag.before_notify(bugsnag_extra.add_pillar_request_to_notification) got_request_exception.connect(self.__notify_bugsnag) self.log.info('Bugsnag setup complete') diff --git a/pillar/bugsnag_extra.py b/pillar/bugsnag_extra.py new file mode 100644 index 00000000..9c4faae2 --- /dev/null +++ b/pillar/bugsnag_extra.py @@ -0,0 +1,51 @@ +# Keys in the user's session dictionary that are removed before sending to Bugsnag. +SESSION_KEYS_TO_REMOVE = ('blender_id_oauth_token', 'user_id') + + +def add_pillar_request_to_notification(notification): + """Adds request metadata to the Bugsnag notifications. + + This basically copies bugsnag.flask.add_flask_request_to_notification, + but is altered to include Pillar-specific metadata. + """ + from flask import request, session + from bugsnag.wsgi import request_path + import pillar.auth + + if not request: + return + + notification.context = "%s %s" % (request.method, + request_path(request.environ)) + + if 'id' not in notification.user: + user: pillar.auth.UserClass = pillar.auth.current_user._get_current_object() + notification.set_user(id=user.user_id, + email=user.email, + name=user.username) + notification.user['roles'] = sorted(user.roles) + notification.user['capabilities'] = sorted(user.capabilities) + + session_dict = dict(session) + for key in SESSION_KEYS_TO_REMOVE: + try: + del session_dict[key] + except KeyError: + pass + notification.add_tab("session", session_dict) + notification.add_tab("environment", dict(request.environ)) + + remote_addr = request.remote_addr + forwarded_for = request.headers.get('X-Forwarded-For') + if forwarded_for: + remote_addr = f'{forwarded_for} (proxied via {remote_addr})' + + notification.add_tab("request", { + "url": request.base_url, + "headers": dict(request.headers), + "params": dict(request.form), + "data": {'request.data': request.data, + 'request.json': request.get_json()}, + "endpoint": request.endpoint, + "remote_addr": remote_addr, + }) diff --git a/pillar/config.py b/pillar/config.py index 5c2c1175..7c45258d 100644 --- a/pillar/config.py +++ b/pillar/config.py @@ -63,6 +63,7 @@ GOOGLE_SITE_VERIFICATION = '' ADMIN_USER_GROUP = '5596e975ea893b269af85c0e' SUBSCRIBER_USER_GROUP = '5596e975ea893b269af85c0f' BUGSNAG_API_KEY = '' +BUGSNAG_RELEASE_STAGE = 'development' ALGOLIA_USER = '-SECRET-' ALGOLIA_API_KEY = '-SECRET-'