• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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"""
4Middleware for logging requests, using Apache combined log format
5"""
6
7import logging
8import six
9import time
10from six.moves.urllib.parse import quote
11
12class 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
102def 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
122make_filter.__doc__ = TransLogger.__doc__
123