translogger.py 4.8 KB

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