[Kimchi-devel] [PATCH] [Wok 3/5] Log user requests
Jose Ricardo Ziviani
joserz at linux.vnet.ibm.com
Fri Feb 26 15:51:55 UTC 2016
On 26-02-2016 10:40, Aline Manera wrote:
>
>
> On 02/25/2016 05:47 PM, Lucio Correia wrote:
>> Signed-off-by: Lucio Correia <luciojhc at linux.vnet.ibm.com>
>> ---
>> src/wok/control/base.py | 81
>> +++++++++++++++++++++++++++++++++++++++++++++----
>> src/wok/root.py | 27 +++++++++++++++++
>> src/wok/server.py | 11 +++++++
>> src/wok/utils.py | 12 ++++++++
>> src/wokd.in | 4 +++
>> 5 files changed, 129 insertions(+), 6 deletions(-)
>>
>> diff --git a/src/wok/control/base.py b/src/wok/control/base.py
>> index 363fd60..f44771e 100644
>> --- a/src/wok/control/base.py
>> +++ b/src/wok/control/base.py
>> @@ -32,6 +32,15 @@ from wok.control.utils import validate_params
>> from wok.exception import InvalidOperation, InvalidParameter
>> from wok.exception import MissingParameter, NotFoundError
>> from wok.exception import OperationFailed, UnauthorizedError,
>> WokException
>> +from wok.reqlogger import RequestRecord
>> +from wok.utils import get_plugin_from_request
>> +
>> +
>> +LOG_DISABLED_METHODS = ['GET']
>> +
>> +# Default request log messages
>> +COLLECTION_DEFAULT_LOG = "request on collection"
>> +RESOURCE_DEFAULT_LOG = "request on resource"
>>
>>
>> class Resource(object):
>> @@ -58,6 +67,7 @@ class Resource(object):
>> self.model_args = (ident,)
>> self.role_key = None
>> self.admin_methods = []
>> + self.log_map = {}
>>
>> def _redirect(self, action_result, code=303):
>> uri_params = []
>> @@ -102,7 +112,8 @@ class Resource(object):
>> def _generate_action_handler_base(self, action_name, render_fn,
>> destructive=False,
>> action_args=None):
>> def wrapper(*args, **kwargs):
>> - validate_method(('POST'), self.role_key, self.admin_methods)
>> + method = 'POST'
>> + validate_method((method), self.role_key, self.admin_methods)
>> try:
>> self.lookup()
>> if not self.is_authorized():
>> @@ -137,6 +148,17 @@ class Resource(object):
>> raise cherrypy.HTTPError(500, e.message)
>> except WokException, e:
>> raise cherrypy.HTTPError(500, e.message)
>> + finally:
>> + params = {}
>> + if model_args:
>> + params = {'ident': str(model_args[0])}
>> +
>> + RequestRecord(
>> + self.getRequestMessage(method, action_name) %
>> params,
>> + app=get_plugin_from_request(),
>> + req=method,
>> + user=cherrypy.session.get(USER_NAME, 'N/A')
>> + ).log()
>>
>> wrapper.__name__ = action_name
>> wrapper.exposed = True
>> @@ -162,6 +184,18 @@ class Resource(object):
>> raise cherrypy.HTTPError(500, e.message)
>> except InvalidOperation, e:
>> raise cherrypy.HTTPError(400, e.message)
>> + finally:
>> + method = 'DELETE'
>> + params = {}
>> + if self.model_args:
>> + params = {'ident': str(self.model_args[0])}
>> +
>> + RequestRecord(
>> + self.getRequestMessage(method, 'default') % params,
>> + app=get_plugin_from_request(),
>> + req=method,
>> + user=cherrypy.session.get(USER_NAME, 'N/A')
>> + ).log()
>>
>> @cherrypy.expose
>> def index(self, *args, **kargs):
>> @@ -203,14 +237,23 @@ class Resource(object):
>> return user_name in users or len(set(user_groups) &
>> set(groups)) > 0
>>
>> def update(self, *args, **kargs):
>> + params = parse_request()
>> +
>> try:
>> update = getattr(self.model, model_fn(self, 'update'))
>> except AttributeError:
>> e = InvalidOperation('WOKAPI0003E', {'resource':
>> get_class_name(self)})
>> raise cherrypy.HTTPError(405, e.message)
>> + finally:
>> + method = 'PUT'
>> + RequestRecord(
>> + self.getRequestMessage(method) % params,
>> + app=get_plugin_from_request(),
>> + req=method,
>> + user=cherrypy.session.get(USER_NAME, 'N/A')
>> + ).log()
>>
>> - params = parse_request()
>> validate_params(params, self, 'update')
>>
>> args = list(self.model_args) + [params]
>> @@ -222,6 +265,13 @@ class Resource(object):
>> def get(self):
>> return wok.template.render(get_class_name(self), self.data)
>>
>> + def getRequestMessage(self, method, action='default'):
>> + """
>> + Provide customized user activity log message in inherited
>> classes
>> + through log_map attribute.
>> + """
>> + return self.log_map.get(method, {}).get(action,
>> RESOURCE_DEFAULT_LOG)
>> +
>> @property
>> def data(self):
>> """
>> @@ -273,6 +323,7 @@ class Collection(object):
>> self.model_args = []
>> self.role_key = None
>> self.admin_methods = []
>> + self.log_map = {}
>>
>> def create(self, params, *args):
>> try:
>> @@ -341,18 +392,28 @@ class Collection(object):
>> data = self.filter_data(resources, fields_filter)
>> return wok.template.render(get_class_name(self), data)
>>
>> + def getRequestMessage(self, method):
>> + """
>> + Provide customized user activity log message in inherited
>> classes
>> + through log_map attribute.
>> + """
>> + return self.log_map.get(method, {}).get('default',
>> + COLLECTION_DEFAULT_LOG)
>> +
>> @cherrypy.expose
>> def index(self, *args, **kwargs):
>> + params = {}
>> method = validate_method(('GET', 'POST'),
>> self.role_key, self.admin_methods)
>>
>> try:
>> if method == 'GET':
>> - filter_params = cherrypy.request.params
>> - validate_params(filter_params, self, 'get_list')
>> - return self.get(filter_params)
>> + params = cherrypy.request.params
>> + validate_params(params, self, 'get_list')
>> + return self.get(params)
>> elif method == 'POST':
>> - return self.create(parse_request(), *args)
>> + params = parse_request()
>> + return self.create(params, *args)
>> except InvalidOperation, e:
>> raise cherrypy.HTTPError(400, e.message)
>> except InvalidParameter, e:
>> @@ -365,6 +426,14 @@ class Collection(object):
>> raise cherrypy.HTTPError(500, e.message)
>> except WokException, e:
>> raise cherrypy.HTTPError(500, e.message)
>> + finally:
>> + if method not in LOG_DISABLED_METHODS:
>> + RequestRecord(
>> + self.getRequestMessage(method) % params,
>> + app=get_plugin_from_request(),
>> + req=method,
>> + user=cherrypy.session.get(USER_NAME, 'N/A')
>> + ).log()
>>
>>
>> class AsyncCollection(Collection):
>> diff --git a/src/wok/root.py b/src/wok/root.py
>> index 174a9b9..084613f 100644
>> --- a/src/wok/root.py
>> +++ b/src/wok/root.py
>> @@ -32,8 +32,17 @@ from wok.control import sub_nodes
>> from wok.control.base import Resource
>> from wok.control.utils import parse_request
>> from wok.exception import MissingParameter, OperationFailed
>> +from wok.reqlogger import RequestRecord
>> +from wok.utils import get_plugin_from_request
>>
>>
>> +ROOT_REQUESTS = {
>> + 'POST': {
>> + 'login': "User '%(username)s' login",
>> + 'logout': "User '%(username)s' logout",
>> + },
>> +}
>> +
>> class Root(Resource):
>> def __init__(self, model, dev_env=False):
>> super(Root, self).__init__(model)
>> @@ -129,6 +138,7 @@ class WokRoot(Root):
>> self.paths = wok_paths
>> self.domain = 'wok'
>> self.messages = messages
>> + self.log_map = ROOT_REQUESTS
>>
>> @cherrypy.expose
>> def login(self, *args):
>> @@ -144,10 +154,27 @@ class WokRoot(Root):
>> user_info = auth.login(username, password)
>> except OperationFailed:
>> raise cherrypy.HTTPError(401)
>> + finally:
>> + method = 'POST'
>> + RequestRecord(
>> + self.getRequestMessage(method, 'login') % params,
>> + app=get_plugin_from_request(),
>> + req=method,
>> + user=cherrypy.session.get(auth.USER_NAME, 'N/A')
>> + ).log()
>>
>> return json.dumps(user_info)
>>
>> @cherrypy.expose
>> def logout(self):
>> + method = 'POST'
>> + params = {'username': cherrypy.session.get(auth.USER_NAME,
>> 'N/A')}
>> + RequestRecord(
>> + self.getRequestMessage(method, 'logout') % params,
>> + app=get_plugin_from_request(),
>> + req=method,
>> + user=params['username']
>> + ).log()
>> +
>> auth.logout()
>> return '{}'
>> diff --git a/src/wok/server.py b/src/wok/server.py
>> index 75b41d5..e27124e 100644
>> --- a/src/wok/server.py
>> +++ b/src/wok/server.py
>> @@ -36,6 +36,7 @@ from wok.model import model
>> from wok.proxy import start_proxy, terminate_proxy
>> from wok.root import WokRoot
>> from wok.safewatchedfilehandler import SafeWatchedFileHandler
>> +from wok.reqlogger import MAX_FILE_SIZE, NUM_BACKUP_FILES,
>> WOK_REQUEST_LOGGER
>> from wok.utils import get_enabled_plugins, import_class
>>
>> LOGGING_LEVEL = {"debug": logging.DEBUG,
>> @@ -65,6 +66,7 @@ class Server(object):
>> start_proxy(options)
>>
>> make_dirs = [
>> + os.path.abspath(config.get_log_download_path()),
>> os.path.dirname(os.path.abspath(options.access_log)),
>> os.path.dirname(os.path.abspath(options.error_log)),
>> os.path.dirname(os.path.abspath(config.get_object_store()))
>> @@ -130,6 +132,15 @@ class Server(object):
>> # Add error log file to cherrypy configuration
>> cherrypy.log.error_log.addHandler(h)
>
>> + # Request logger setup
>> + h = logging.handlers.RotatingFileHandler(options.req_log, 'a',
>> + maxBytes=MAX_FILE_SIZE,
>> +
>> backupCount=NUM_BACKUP_FILES)
>> + h.setFormatter(logging.Formatter('%(message)s'))
>> + reqLogger = logging.getLogger(WOK_REQUEST_LOGGER)
>> + reqLogger.setLevel(logging.INFO)
>> + reqLogger.addHandler(h)
>> +
>
> There was a recent change on wok to use a thread safe logging (commit
> 3aad7ba93d0228650af8c95101addd561140cef7)
>
> Maybe it will be better to use it in this case too.
>
> Ziviani, as you did the patch about the logging, could you advice on that?
Hello!
Only the handler to "options.error_log" is required to use
SafeWatchedFileHandler because the web serial console writes to that in
a different process.
In this case it's not necessary (unless we need to use this req_log in
other processes as well.)
>
>> # only add logrotate if wok is installed
>> if paths.installed:
>>
>> diff --git a/src/wok/utils.py b/src/wok/utils.py
>> index e2f1d8e..7b1aa06 100644
>> --- a/src/wok/utils.py
>> +++ b/src/wok/utils.py
>> @@ -120,6 +120,18 @@ def get_all_tabs():
>> return tabs
>>
>>
>> +def get_plugin_from_request():
>> + """
>> + Returns name of plugin being requested. If no plugin, returns 'wok'.
>> + """
>> + script_name = cherrypy.request.script_name
>> + split = script_name.split('/')
>> + if len(split) > 2 and split[1] == 'plugins':
>> + return split[2]
>> +
>> + return 'wok'
>> +
>> +
>> def import_class(class_path):
>> module_name, class_name = class_path.rsplit('.', 1)
>> try:
>> diff --git a/src/wokd.in b/src/wokd.in
>> index 7255d3c..c91f07a 100644
>> --- a/src/wokd.in
>> +++ b/src/wokd.in
>> @@ -36,6 +36,7 @@ if not config.paths.installed:
>>
>> ACCESS_LOG = "wok-access.log"
>> ERROR_LOG = "wok-error.log"
>> +REQ_LOG = "wok-req.log"
>>
>>
>> def main(options):
>> @@ -76,6 +77,9 @@ def main(options):
>> parser.add_option('--error-log',
>> default=os.path.join(logDir, ERROR_LOG),
>> help="Error log file")
>> + parser.add_option('--req-log',
>> + default=os.path.join(logDir, REQ_LOG),
>> + help="User Request log file")
>> parser.add_option('--environment', default=runningEnv,
>> help="Running environment of wok server")
>> parser.add_option('--test', action='store_true',
>
> _______________________________________________
> Kimchi-devel mailing list
> Kimchi-devel at ovirt.org
> http://lists.ovirt.org/mailman/listinfo/kimchi-devel
>
--
Jose Ricardo Ziviani
-----------------------------
Software Engineer
Linux Technology Center - IBM
More information about the Kimchi-devel
mailing list