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

Aline Manera alinefm at linux.vnet.ibm.com
Fri Feb 26 13:41:46 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)
> +
>           # 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")

What is the proposal of this option? I don't think we can let the user 
change the log file directory as the cherrpy configuration depends on that.

>       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