[Kimchi-devel] [PATCH] [Wok 3/5] Log user requests

Aline Manera alinefm at linux.vnet.ibm.com
Fri Feb 26 13:40:08 UTC 2016



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?

>           # 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',




More information about the Kimchi-devel mailing list