translogger.py 4.5 KB

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