[PATCH V2] [Wok 0/3] User Request Log improvements

Changes in V2: * updated logs.md * added test cases IMPORTANT: test will fail if old log entries are present in the system. Lucio Correia (3): Include user IP address in request log messages Log time in ISO format Add test cases for user logs docs/API/logs.md | 1 + src/wok/control/base.py | 10 ++++++---- src/wok/reqlogger.py | 32 ++++++++++++++++++++++++-------- src/wok/root.py | 6 ++++-- tests/test_api.py | 34 ++++++++++++++++++++++++++++++++++ 5 files changed, 69 insertions(+), 14 deletions(-) -- 1.9.1

* Allow advanced log searches by IP address * Add RECORD_TEMPLATE_DICT to avoid versioning errors with older log entries, which don't have IP address. * Remove duplicated comment line Signed-off-by: Lucio Correia <luciojhc@linux.vnet.ibm.com> --- docs/API/logs.md | 1 + src/wok/control/base.py | 10 ++++++---- src/wok/reqlogger.py | 17 ++++++++++++++--- src/wok/root.py | 6 ++++-- 4 files changed, 25 insertions(+), 9 deletions(-) diff --git a/docs/API/logs.md b/docs/API/logs.md index f808e1a..fcfb3b2 100644 --- a/docs/API/logs.md +++ b/docs/API/logs.md @@ -13,6 +13,7 @@ * req: Filter entries by type of request: "DELETE", "POST", "PUT". "GET" requests are not logged. * user: Filter entries by user that performed the request. + * ip: Filter entries by user IP address, i.e. 127.0.0.1 * date: Filter entries by date of record in the format "YYYY-MM-DD" * download: Generate text file for download of search results. diff --git a/src/wok/control/base.py b/src/wok/control/base.py index 4b459ed..419179a 100644 --- a/src/wok/control/base.py +++ b/src/wok/control/base.py @@ -146,7 +146,8 @@ class Resource(object): msg, app=get_plugin_from_request(), req=method, - user=cherrypy.session.get(USER_NAME, 'N/A') + user=cherrypy.session.get(USER_NAME, 'N/A'), + ip=cherrypy.request.remote.ip ).log() if destructive is False or \ @@ -227,7 +228,8 @@ class Resource(object): msg, app=get_plugin_from_request(), req=method, - user=cherrypy.session.get(USER_NAME, 'N/A') + user=cherrypy.session.get(USER_NAME, 'N/A'), + ip=cherrypy.request.remote.ip ).log() return result @@ -374,7 +376,6 @@ class Collection(object): except Exception as e: # In case of errors when fetching a resource info, pass and # log the error, so, other resources are returned - # log the error, so, other resources are returned. # Encoding error message as ident is also encoded value. # This has to be done to avoid unicode error, # as combination of encoded and unicode value results into @@ -455,7 +456,8 @@ class Collection(object): msg, app=get_plugin_from_request(), req=method, - user=cherrypy.session.get(USER_NAME, 'N/A') + user=cherrypy.session.get(USER_NAME, 'N/A'), + ip=cherrypy.request.remote.ip ).log() return result diff --git a/src/wok/reqlogger.py b/src/wok/reqlogger.py index bbd1bd0..2ec62b8 100644 --- a/src/wok/reqlogger.py +++ b/src/wok/reqlogger.py @@ -34,10 +34,20 @@ from wok.utils import ascii_dict, remove_old_files # Log search setup -FILTER_FIELDS = ['app', 'date', 'download', 'req', 'user', 'time'] +FILTER_FIELDS = ['app', 'date', 'download', 'ip', 'req', 'user', 'time'] LOG_DOWNLOAD_URI = "/data/logs/%s" LOG_DOWNLOAD_TIMEOUT = 6 -LOG_FORMAT = "[%(date)s %(time)s] %(req)-6s %(app)-11s %(user)s: %(message)s\n" +LOG_FORMAT = "[%(date)s %(time)s] %(req)-6s %(app)-11s %(ip)-15s %(user)s: " \ + "%(message)s\n" +RECORD_TEMPLATE_DICT = { + 'date': '', + 'time': '', + 'req': '', + 'app': '', + 'ip': '', + 'user': '', + 'message': '', +} SECONDS_PER_HOUR = 360 # Log handler setup @@ -86,7 +96,8 @@ class RequestParser(object): with fd: for record in sortedList: - asciiRecord = ascii_dict(record) + asciiRecord = RECORD_TEMPLATE_DICT + asciiRecord.update(ascii_dict(record)) fd.write(LOG_FORMAT % asciiRecord) fd.close() diff --git a/src/wok/root.py b/src/wok/root.py index 29ea657..902e203 100644 --- a/src/wok/root.py +++ b/src/wok/root.py @@ -172,7 +172,8 @@ class WokRoot(Root): msg, app=get_plugin_from_request(), req=method, - user=cherrypy.session.get(auth.USER_NAME, 'N/A') + user=cherrypy.session.get(auth.USER_NAME, 'N/A'), + ip=cherrypy.request.remote.ip ).log() return json.dumps(user_info) @@ -187,7 +188,8 @@ class WokRoot(Root): msg, app=get_plugin_from_request(), req=method, - user=params['username'] + user=params['username'], + ip=cherrypy.request.remote.ip ).log() auth.logout() -- 1.9.1

Signed-off-by: Lucio Correia <luciojhc@linux.vnet.ibm.com> --- src/wok/reqlogger.py | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/wok/reqlogger.py b/src/wok/reqlogger.py index 2ec62b8..6f32c44 100644 --- a/src/wok/reqlogger.py +++ b/src/wok/reqlogger.py @@ -23,9 +23,9 @@ import json import logging import logging.handlers import os.path +import time from cherrypy.process.plugins import BackgroundTask -from datetime import datetime from tempfile import NamedTemporaryFile from wok.config import config, get_log_download_path @@ -37,11 +37,12 @@ from wok.utils import ascii_dict, remove_old_files FILTER_FIELDS = ['app', 'date', 'download', 'ip', 'req', 'user', 'time'] LOG_DOWNLOAD_URI = "/data/logs/%s" LOG_DOWNLOAD_TIMEOUT = 6 -LOG_FORMAT = "[%(date)s %(time)s] %(req)-6s %(app)-11s %(ip)-15s %(user)s: " \ - "%(message)s\n" +LOG_FORMAT = "[%(date)s %(time)s %(zone)s] %(req)-6s %(app)-11s %(ip)-15s " \ + "%(user)s: %(message)s\n" RECORD_TEMPLATE_DICT = { 'date': '', 'time': '', + 'zone': '', 'req': '', 'app': '', 'ip': '', @@ -49,6 +50,9 @@ RECORD_TEMPLATE_DICT = { 'message': '', } SECONDS_PER_HOUR = 360 +TS_DATE_FORMAT = "%Y-%m-%d" +TS_TIME_FORMAT = "%H:%M:%S" +TS_ZONE_FORMAT = "%Z" # Log handler setup REQUEST_LOG_FILE = "wok-req.log" @@ -180,10 +184,11 @@ class RequestRecord(object): self.message = message self.kwargs = kwargs - # register timestamp - timestamp = datetime.today() - self.kwargs['date'] = timestamp.strftime('%Y-%m-%d') - self.kwargs['time'] = timestamp.strftime('%H:%M:%S') + # register timestamp in local time + timestamp = time.localtime() + self.kwargs['date'] = time.strftime(TS_DATE_FORMAT, timestamp) + self.kwargs['time'] = time.strftime(TS_TIME_FORMAT, timestamp) + self.kwargs['zone'] = time.strftime(TS_ZONE_FORMAT, timestamp) def __str__(self): info = json.JSONEncoder().encode(self.kwargs) -- 1.9.1

Signed-off-by: Lucio Correia <luciojhc@linux.vnet.ibm.com> --- tests/test_api.py | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/tests/test_api.py b/tests/test_api.py index e9c8714..79f9af5 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -55,3 +55,37 @@ class APITests(unittest.TestCase): conf = json.loads(resp) keys = ["auth", "ssl_port", "websockets_port", "version"] self.assertEquals(sorted(keys), sorted(conf.keys())) + + def test_user_log(self): + # Login and logout to make sure there there are entries in user log + hdrs = {'AUTHORIZATION': '', + 'Content-Type': 'application/json', + 'Accept': 'application/json'} + + user, pw = utils.fake_user.items()[0] + req = json.dumps({'username': user, 'password': pw}) + resp = self.request('/login', req, 'POST', hdrs) + + resp = self.request('/logout', '{}', 'POST', hdrs) + self.assertEquals(200, resp.status) + + # Test user logs JSON response + resp = self.request('/logs?app=wok&download=True').read() + conf = json.loads(resp) + self.assertIn('records', conf) + self.assertIn('uri', conf) + + # Test download URL + uri = conf.get('uri') + self.assertTrue(len(uri) > 0) + + # Test each record key + records = conf.get('records', []) + self.assertGreaterEqual(records, 1) + for record in records: + keys = [u'zone', u'ip', u'app', u'req', u'user', u'time', u'date', + u'message'] + self.assertEquals(sorted(keys), sorted(record.keys())) + + # Test search by app + self.assertEquals(record['app'], 'wok') -- 1.9.1
participants (2)
-
Aline Manera
-
Lucio Correia