Home | History | Annotate | Download | only in paste
      1 # (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
      2 # Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
      3 """
      4 Middleware for logging requests, using Apache combined log format
      5 """
      6 
      7 import logging
      8 import six
      9 import time
     10 from six.moves.urllib.parse import quote
     11 
     12 class TransLogger(object):
     13     """
     14     This logging middleware will log all requests as they go through.
     15     They are, by default, sent to a logger named ``'wsgi'`` at the
     16     INFO level.
     17 
     18     If ``setup_console_handler`` is true, then messages for the named
     19     logger will be sent to the console.
     20     """
     21 
     22     format = ('%(REMOTE_ADDR)s - %(REMOTE_USER)s [%(time)s] '
     23               '"%(REQUEST_METHOD)s %(REQUEST_URI)s %(HTTP_VERSION)s" '
     24               '%(status)s %(bytes)s "%(HTTP_REFERER)s" "%(HTTP_USER_AGENT)s"')
     25 
     26     def __init__(self, application,
     27                  logger=None,
     28                  format=None,
     29                  logging_level=logging.INFO,
     30                  logger_name='wsgi',
     31                  setup_console_handler=True,
     32                  set_logger_level=logging.DEBUG):
     33         if format is not None:
     34             self.format = format
     35         self.application = application
     36         self.logging_level = logging_level
     37         self.logger_name = logger_name
     38         if logger is None:
     39             self.logger = logging.getLogger(self.logger_name)
     40             if setup_console_handler:
     41                 console = logging.StreamHandler()
     42                 console.setLevel(logging.DEBUG)
     43                 # We need to control the exact format:
     44                 console.setFormatter(logging.Formatter('%(message)s'))
     45                 self.logger.addHandler(console)
     46                 self.logger.propagate = False
     47             if set_logger_level is not None:
     48                 self.logger.setLevel(set_logger_level)
     49         else:
     50             self.logger = logger
     51 
     52     def __call__(self, environ, start_response):
     53         start = time.localtime()
     54         req_uri = quote(environ.get('SCRIPT_NAME', '')
     55                                + environ.get('PATH_INFO', ''))
     56         if environ.get('QUERY_STRING'):
     57             req_uri += '?'+environ['QUERY_STRING']
     58         method = environ['REQUEST_METHOD']
     59         def replacement_start_response(status, headers, exc_info=None):
     60             # @@: Ideally we would count the bytes going by if no
     61             # content-length header was provided; but that does add
     62             # some overhead, so at least for now we'll be lazy.
     63             bytes = None
     64             for name, value in headers:
     65                 if name.lower() == 'content-length':
     66                     bytes = value
     67             self.write_log(environ, method, req_uri, start, status, bytes)
     68             return start_response(status, headers)
     69         return self.application(environ, replacement_start_response)
     70 
     71     def write_log(self, environ, method, req_uri, start, status, bytes):
     72         if bytes is None:
     73             bytes = '-'
     74         if time.daylight:
     75                 offset = time.altzone / 60 / 60 * -100
     76         else:
     77                 offset = time.timezone / 60 / 60 * -100
     78         if offset >= 0:
     79                 offset = "+%0.4d" % (offset)
     80         elif offset < 0:
     81                 offset = "%0.4d" % (offset)
     82         remote_addr = '-'
     83         if environ.get('HTTP_X_FORWARDED_FOR'):
     84             remote_addr = environ['HTTP_X_FORWARDED_FOR']
     85         elif environ.get('REMOTE_ADDR'):
     86             remote_addr = environ['REMOTE_ADDR']
     87         d = {
     88             'REMOTE_ADDR': remote_addr,
     89             'REMOTE_USER': environ.get('REMOTE_USER') or '-',
     90             'REQUEST_METHOD': method,
     91             'REQUEST_URI': req_uri,
     92             'HTTP_VERSION': environ.get('SERVER_PROTOCOL'),
     93             'time': time.strftime('%d/%b/%Y:%H:%M:%S ', start) + offset,
     94             'status': status.split(None, 1)[0],
     95             'bytes': bytes,
     96             'HTTP_REFERER': environ.get('HTTP_REFERER', '-'),
     97             'HTTP_USER_AGENT': environ.get('HTTP_USER_AGENT', '-'),
     98             }
     99         message = self.format % d
    100         self.logger.log(self.logging_level, message)
    101 
    102 def make_filter(
    103     app, global_conf,
    104     logger_name='wsgi',
    105     format=None,
    106     logging_level=logging.INFO,
    107     setup_console_handler=True,
    108     set_logger_level=logging.DEBUG):
    109     from paste.util.converters import asbool
    110     if isinstance(logging_level, (six.binary_type, six.text_type)):
    111         logging_level = logging._levelNames[logging_level]
    112     if isinstance(set_logger_level, (six.binary_type, six.text_type)):
    113         set_logger_level = logging._levelNames[set_logger_level]
    114     return TransLogger(
    115         app,
    116         format=format or None,
    117         logging_level=logging_level,
    118         logger_name=logger_name,
    119         setup_console_handler=asbool(setup_console_handler),
    120         set_logger_level=set_logger_level)
    121 
    122 make_filter.__doc__ = TransLogger.__doc__
    123