From 8c48b2d808613f3b3bc55f2e8f28686255b4bf21 Mon Sep 17 00:00:00 2001 From: Benjamin Dauvergne Date: Mon, 27 Mar 2017 10:21:50 +0200 Subject: [PATCH 1/2] add a RequestFilter to augment logs with contextual informations (#10411) Fields added are: - ip - path - user_id - user_name - user_email - user_display_name - user_uuid - session_id (md5 of the real session id) - request_id ( id(request) ) - tenant --- tests/test_logs.py | 58 +++++++++++++++++++++++++++++++++++++++++++++++++ wcs/qommon/logger.py | 40 ++++++++++++++++++++++++++-------- wcs/qommon/publisher.py | 3 ++- 3 files changed, 91 insertions(+), 10 deletions(-) create mode 100644 tests/test_logs.py diff --git a/tests/test_logs.py b/tests/test_logs.py new file mode 100644 index 0000000..6ac75a4 --- /dev/null +++ b/tests/test_logs.py @@ -0,0 +1,58 @@ +from utilities import create_temporary_pub + +from wcs.qommon.http_request import HTTPRequest +from qommon import get_logger +from quixote import get_session +from wcs.qommon.sessions import Session + + +def test_request_filter(caplog): + pub = create_temporary_pub(sql_mode=False) + pub.cfg['debug'] = {'logger': True} + pub.write_cfg() + + user = pub.user_class() + user.name = 'coucou' + user.email = 'foo@localhost' + user.name_identifiers = ['1234'] + user.store() + + req = HTTPRequest(None, { + 'SERVER_NAME': 'example.net', + 'SCRIPT_NAME': '/coin', + 'REMOTE_ADDR': '10.0.0.0', + }) + pub._set_request(req) + pub.set_config(req) + session = Session(id='1') + session.store() + req.session = session + logger = get_logger() + + logger.info('coucou') + record = caplog.records[0] + assert record.ip == '10.0.0.0' + assert record.tenant == 'example.net' + assert record.path == '/coin' + assert record.request_id + assert record.user_id == 'unlogged' + assert record.user_email == '-' + assert record.user_display_name == '-' + assert record.user_uuid == '-' + assert record.session_id != '[nosession]' + + req._user = () + get_session().set_user(user.id) + get_session().store() + assert get_session().get_session_id() + logger.info('coucou') + record = caplog.records[1] + assert record.ip == '10.0.0.0' + assert record.tenant == 'example.net' + assert record.path == '/coin' + assert record.request_id + assert record.user_id == '1' + assert record.user_email == 'foo@localhost' + assert record.user_display_name == 'coucou' + assert record.user_uuid == '1234' + assert record.session_id != '[nosession]' diff --git a/wcs/qommon/logger.py b/wcs/qommon/logger.py index 18c28cc..388a70a 100644 --- a/wcs/qommon/logger.py +++ b/wcs/qommon/logger.py @@ -16,6 +16,7 @@ import logging import os +import hashlib from quixote import get_publisher, get_session, get_request @@ -40,12 +41,25 @@ class BotFilter(logging.Filter): return False -class Formatter(logging.Formatter): - def format(self, record): +class RequestFilter(logging.Filter): + def filter(self, record): request = get_request() + + record.application = 'wcs' + record.tenant = '-' + record.ip = '-' + record.path = '-' + record.request_id = '-' + record.user_id = 'unlogged' + record.user_email = '-' + record.user_display_name = '-' + record.user_uuid = '-' + record.session_id = '[nosession]' if request: - record.address = request.get_environ('REMOTE_ADDR', '-') + record.tenant = request.get_server() + record.ip = request.get_environ('REMOTE_ADDR', '-') record.path = request.get_path() + record.request_id = id(request) user = request.user if user: @@ -53,20 +67,28 @@ class Formatter(logging.Formatter): user_id = user else: user_id = user.id + record.user_display_name = user.name or '-' + record.user_email = user.email or '-' + uuid = None + if user.name_identifiers: + uuid = record.user_uuid = user.name_identifiers[0] + record.user = user.email or uuid or user_id if type(user_id) is str and user_id.startswith('anonymous-'): user_id = 'anonymous' else: user_id = 'unlogged' if BotFilter.is_bot(): user_id = 'bot' + record.user = 'bot' record.user_id = user_id - else: - record.address = '-' - record.path = '-' - record.user_id = 'unlogged' - record.session_id = (get_request() and get_session() and \ - get_session().get_session_id()) or '[nosession]' + if get_session() and get_session().get_session_id(): + # do not disseminate the real session_id + record.session_id = hashlib.md5(str(get_session().get_session_id())).hexdigest() + return True + +class Formatter(logging.Formatter): + def format(self, record): return logging.Formatter.format(self, record) \ .replace('\n', '\n ') diff --git a/wcs/qommon/publisher.py b/wcs/qommon/publisher.py index 8a131e8..e281366 100644 --- a/wcs/qommon/publisher.py +++ b/wcs/qommon/publisher.py @@ -949,13 +949,14 @@ class QommonPublisher(Publisher, object): self._app_logger = logging.getLogger(self.APP_NAME + self.app_dir) if not self._app_logger.filters: self._app_logger.addFilter(logger.BotFilter()) + self._app_logger.addFilter(logger.RequestFilter()) logfile = self.get_app_logger_filename() if not self._app_logger.handlers: hdlr = logging.handlers.RotatingFileHandler(logfile, 'a', 2**20, 100) # max size = 1M formatter = logger.Formatter( - '%(asctime)s %(levelname)s %(address)s '\ + '%(asctime)s %(levelname)s %(ip)s '\ '%(session_id)s %(path)s %(user_id)s - %(message)s') hdlr.setFormatter(formatter) self._app_logger.addHandler(hdlr) -- 2.1.4