| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168 |
- #!/usr/bin/env python
- # Licensed to Cloudera, Inc. under one
- # or more contributor license agreements. See the NOTICE file
- # distributed with this work for additional information
- # regarding copyright ownership. Cloudera, Inc. licenses this file
- # to you under the Apache License, Version 2.0 (the
- # "License"); you may not use this file except in compliance
- # with the License. You may obtain a copy of the License at
- #
- # http://www.apache.org/licenses/LICENSE-2.0
- #
- # Unless required by applicable law or agreed to in writing, software
- # distributed under the License is distributed on an "AS IS" BASIS,
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- # See the License for the specific language governing permissions and
- # limitations under the License.
- """
- Decorators and methods related to access log.
- This assumes a single-threaded server.
- """
- import logging
- import re
- import threading
- import time
- import desktop.conf
- ACCESS_LOG = logging.getLogger('access')
- def access_log_level(lvl):
- """Decorator to set the access log level of a view function."""
- if lvl not in (logging.DEBUG, logging.WARN, logging.ERROR, logging.CRITICAL, logging.FATAL):
- raise ValueError('%s is not a valid logging level' % (lvl,))
- def deco_view(func):
- func.access_log_level = lvl
- return func
- return deco_view
- #
- # Keep most recent per user per app per view access info
- #
- # This is a dictionary (indexed by user)
- # of dictionary (indexed by app)
- # of dictionary (indexed by path)
- # of list (of AccessInfo) sorted by time most recent first
- #
- recent_access_map = { }
- _recent_access_map_lk = threading.Lock()
- _per_user_lk = { } # Indexed by username
- # Store a map of usernames and a dictionary of
- # their IP addresses and last access times
- last_access_map = { }
- # Max number of records per user per view to keep
- _USER_ACCESS_HISTORY_SIZE = desktop.conf.USER_ACCESS_HISTORY_SIZE.get()
- class AccessInfo(dict):
- """
- Represents details on a user access.
- In addition to the attributes specified in __init__, it may contain
- ``msg`` -- A message associated with the access
- ``app`` -- The top level package name of the view function, which
- need NOT be a valid Desktop application name
- """
- def __init__(self, request):
- self['username'] = request.user.username or '-anon-'
- if request.META.has_key('HTTP_X_FORWARDED_FOR'):
- self['remote_ip'] = request.META.get('HTTP_X_FORWARDED_FOR', '-')
- else:
- self['remote_ip'] = request.META.get('REMOTE_ADDR', '-')
- self['method'] = request.method
- self['path'] = request.path
- self['proto'] = request.META.get('SERVER_PROTOCOL', '-')
- self['agent'] = request.META.get('HTTP_USER_AGENT', '-')
- self['time'] = time.time()
- def log(self, level, msg=None):
- if msg is not None:
- self['msg'] = msg
- ACCESS_LOG.log(level,
- '%(remote_ip)s %(username)s - "%(method)s %(path)s %(proto)s" -- %(msg)s' %
- self)
- else:
- ACCESS_LOG.log(level,
- '%(remote_ip)s %(username)s - "%(method)s %(path)s %(proto)s"' % self)
- def add_to_access_history(self, app):
- """Record this user access to the recent access map"""
- self['app'] = app
- user = self['username']
- path = self['path']
- try:
- app_dict = recent_access_map[user]
- except KeyError:
- # Hold the global lock when modifying recent_access_map
- _recent_access_map_lk.acquire()
- try:
- app_dict = { }
- _per_user_lk[user] = threading.Lock()
- recent_access_map[user] = app_dict
- finally:
- _recent_access_map_lk.release()
- # Hold the per user lock when modifying adding the access record.
- # We could further break down the locking granularity but that seems silly.
- user_lk = _per_user_lk[user]
- user_lk.acquire()
- try:
- try:
- path_dict = app_dict[app]
- except KeyError:
- path_dict = { }
- app_dict[app] = path_dict
- try:
- view_access_list = path_dict[path]
- except KeyError:
- view_access_list = [ ]
- path_dict[path] = view_access_list
- # Most recent first
- view_access_list.insert(0, self)
- if len(view_access_list) > _USER_ACCESS_HISTORY_SIZE:
- view_access_list.pop()
- # Update the IP address and last access time of the user
- last_access_map[user] = {'ip':self['remote_ip'],
- 'time':self['time']}
- finally:
- user_lk.release()
- _MODULE_RE = re.compile('[^.]*')
- def log_page_hit(request, view_func, level=None):
- """Log the request to the access log"""
- if level is None:
- level = logging.INFO
- ai = AccessInfo(request)
- ai.log(level)
- # Find the app
- app_re_match = _MODULE_RE.match(view_func.__module__)
- app = app_re_match and app_re_match.group(0) or '-'
- ai.add_to_access_history(app)
- def access_log(request, msg=None, level=None):
- """
- access_log(request, msg=None, level=None) -> None
- Write to the access log. This could be a page hit, or general auditing information.
- """
- if level is None:
- level = logging.INFO
- ai = AccessInfo(request)
- ai.log(level, msg)
- def access_warn(request, msg=None):
- """Write to access log with a WARN log level"""
- ai = AccessInfo(request)
- ai.log(logging.WARN, msg)
|