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