log.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290
  1. #
  2. # Copyright 2012 Facebook
  3. #
  4. # Licensed under the Apache License, Version 2.0 (the "License"); you may
  5. # not use this file except in compliance with the License. You may obtain
  6. # a copy of the License at
  7. #
  8. # http://www.apache.org/licenses/LICENSE-2.0
  9. #
  10. # Unless required by applicable law or agreed to in writing, software
  11. # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  12. # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  13. # License for the specific language governing permissions and limitations
  14. # under the License.
  15. """Logging support for Tornado.
  16. Tornado uses three logger streams:
  17. * ``tornado.access``: Per-request logging for Tornado's HTTP servers (and
  18. potentially other servers in the future)
  19. * ``tornado.application``: Logging of errors from application code (i.e.
  20. uncaught exceptions from callbacks)
  21. * ``tornado.general``: General-purpose logging, including any errors
  22. or warnings from Tornado itself.
  23. These streams may be configured independently using the standard library's
  24. `logging` module. For example, you may wish to send ``tornado.access`` logs
  25. to a separate file for analysis.
  26. """
  27. from __future__ import absolute_import, division, print_function
  28. import logging
  29. import logging.handlers
  30. import sys
  31. from tornado.escape import _unicode
  32. from tornado.util import unicode_type, basestring_type
  33. try:
  34. import colorama
  35. except ImportError:
  36. colorama = None
  37. try:
  38. import curses # type: ignore
  39. except ImportError:
  40. curses = None
  41. # Logger objects for internal tornado use
  42. access_log = logging.getLogger("tornado.access")
  43. app_log = logging.getLogger("tornado.application")
  44. gen_log = logging.getLogger("tornado.general")
  45. def _stderr_supports_color():
  46. try:
  47. if hasattr(sys.stderr, 'isatty') and sys.stderr.isatty():
  48. if curses:
  49. curses.setupterm()
  50. if curses.tigetnum("colors") > 0:
  51. return True
  52. elif colorama:
  53. if sys.stderr is getattr(colorama.initialise, 'wrapped_stderr',
  54. object()):
  55. return True
  56. except Exception:
  57. # Very broad exception handling because it's always better to
  58. # fall back to non-colored logs than to break at startup.
  59. pass
  60. return False
  61. def _safe_unicode(s):
  62. try:
  63. return _unicode(s)
  64. except UnicodeDecodeError:
  65. return repr(s)
  66. class LogFormatter(logging.Formatter):
  67. """Log formatter used in Tornado.
  68. Key features of this formatter are:
  69. * Color support when logging to a terminal that supports it.
  70. * Timestamps on every log line.
  71. * Robust against str/bytes encoding problems.
  72. This formatter is enabled automatically by
  73. `tornado.options.parse_command_line` or `tornado.options.parse_config_file`
  74. (unless ``--logging=none`` is used).
  75. Color support on Windows versions that do not support ANSI color codes is
  76. enabled by use of the colorama__ library. Applications that wish to use
  77. this must first initialize colorama with a call to ``colorama.init``.
  78. See the colorama documentation for details.
  79. __ https://pypi.python.org/pypi/colorama
  80. .. versionchanged:: 4.5
  81. Added support for ``colorama``. Changed the constructor
  82. signature to be compatible with `logging.config.dictConfig`.
  83. """
  84. DEFAULT_FORMAT = \
  85. '%(color)s[%(levelname)1.1s %(asctime)s %(module)s:%(lineno)d]%(end_color)s %(message)s'
  86. DEFAULT_DATE_FORMAT = '%y%m%d %H:%M:%S'
  87. DEFAULT_COLORS = {
  88. logging.DEBUG: 4, # Blue
  89. logging.INFO: 2, # Green
  90. logging.WARNING: 3, # Yellow
  91. logging.ERROR: 1, # Red
  92. }
  93. def __init__(self, fmt=DEFAULT_FORMAT, datefmt=DEFAULT_DATE_FORMAT,
  94. style='%', color=True, colors=DEFAULT_COLORS):
  95. r"""
  96. :arg bool color: Enables color support.
  97. :arg str fmt: Log message format.
  98. It will be applied to the attributes dict of log records. The
  99. text between ``%(color)s`` and ``%(end_color)s`` will be colored
  100. depending on the level if color support is on.
  101. :arg dict colors: color mappings from logging level to terminal color
  102. code
  103. :arg str datefmt: Datetime format.
  104. Used for formatting ``(asctime)`` placeholder in ``prefix_fmt``.
  105. .. versionchanged:: 3.2
  106. Added ``fmt`` and ``datefmt`` arguments.
  107. """
  108. logging.Formatter.__init__(self, datefmt=datefmt)
  109. self._fmt = fmt
  110. self._colors = {}
  111. if color and _stderr_supports_color():
  112. if curses is not None:
  113. # The curses module has some str/bytes confusion in
  114. # python3. Until version 3.2.3, most methods return
  115. # bytes, but only accept strings. In addition, we want to
  116. # output these strings with the logging module, which
  117. # works with unicode strings. The explicit calls to
  118. # unicode() below are harmless in python2 but will do the
  119. # right conversion in python 3.
  120. fg_color = (curses.tigetstr("setaf") or
  121. curses.tigetstr("setf") or "")
  122. if (3, 0) < sys.version_info < (3, 2, 3):
  123. fg_color = unicode_type(fg_color, "ascii")
  124. for levelno, code in colors.items():
  125. self._colors[levelno] = unicode_type(curses.tparm(fg_color, code), "ascii")
  126. self._normal = unicode_type(curses.tigetstr("sgr0"), "ascii")
  127. else:
  128. # If curses is not present (currently we'll only get here for
  129. # colorama on windows), assume hard-coded ANSI color codes.
  130. for levelno, code in colors.items():
  131. self._colors[levelno] = '\033[2;3%dm' % code
  132. self._normal = '\033[0m'
  133. else:
  134. self._normal = ''
  135. def format(self, record):
  136. try:
  137. message = record.getMessage()
  138. assert isinstance(message, basestring_type) # guaranteed by logging
  139. # Encoding notes: The logging module prefers to work with character
  140. # strings, but only enforces that log messages are instances of
  141. # basestring. In python 2, non-ascii bytestrings will make
  142. # their way through the logging framework until they blow up with
  143. # an unhelpful decoding error (with this formatter it happens
  144. # when we attach the prefix, but there are other opportunities for
  145. # exceptions further along in the framework).
  146. #
  147. # If a byte string makes it this far, convert it to unicode to
  148. # ensure it will make it out to the logs. Use repr() as a fallback
  149. # to ensure that all byte strings can be converted successfully,
  150. # but don't do it by default so we don't add extra quotes to ascii
  151. # bytestrings. This is a bit of a hacky place to do this, but
  152. # it's worth it since the encoding errors that would otherwise
  153. # result are so useless (and tornado is fond of using utf8-encoded
  154. # byte strings wherever possible).
  155. record.message = _safe_unicode(message)
  156. except Exception as e:
  157. record.message = "Bad message (%r): %r" % (e, record.__dict__)
  158. record.asctime = self.formatTime(record, self.datefmt)
  159. if record.levelno in self._colors:
  160. record.color = self._colors[record.levelno]
  161. record.end_color = self._normal
  162. else:
  163. record.color = record.end_color = ''
  164. formatted = self._fmt % record.__dict__
  165. if record.exc_info:
  166. if not record.exc_text:
  167. record.exc_text = self.formatException(record.exc_info)
  168. if record.exc_text:
  169. # exc_text contains multiple lines. We need to _safe_unicode
  170. # each line separately so that non-utf8 bytes don't cause
  171. # all the newlines to turn into '\n'.
  172. lines = [formatted.rstrip()]
  173. lines.extend(_safe_unicode(ln) for ln in record.exc_text.split('\n'))
  174. formatted = '\n'.join(lines)
  175. return formatted.replace("\n", "\n ")
  176. def enable_pretty_logging(options=None, logger=None):
  177. """Turns on formatted logging output as configured.
  178. This is called automatically by `tornado.options.parse_command_line`
  179. and `tornado.options.parse_config_file`.
  180. """
  181. if options is None:
  182. import tornado.options
  183. options = tornado.options.options
  184. if options.logging is None or options.logging.lower() == 'none':
  185. return
  186. if logger is None:
  187. logger = logging.getLogger()
  188. logger.setLevel(getattr(logging, options.logging.upper()))
  189. if options.log_file_prefix:
  190. rotate_mode = options.log_rotate_mode
  191. if rotate_mode == 'size':
  192. channel = logging.handlers.RotatingFileHandler(
  193. filename=options.log_file_prefix,
  194. maxBytes=options.log_file_max_size,
  195. backupCount=options.log_file_num_backups)
  196. elif rotate_mode == 'time':
  197. channel = logging.handlers.TimedRotatingFileHandler(
  198. filename=options.log_file_prefix,
  199. when=options.log_rotate_when,
  200. interval=options.log_rotate_interval,
  201. backupCount=options.log_file_num_backups)
  202. else:
  203. error_message = 'The value of log_rotate_mode option should be ' +\
  204. '"size" or "time", not "%s".' % rotate_mode
  205. raise ValueError(error_message)
  206. channel.setFormatter(LogFormatter(color=False))
  207. logger.addHandler(channel)
  208. if (options.log_to_stderr or
  209. (options.log_to_stderr is None and not logger.handlers)):
  210. # Set up color if we are in a tty and curses is installed
  211. channel = logging.StreamHandler()
  212. channel.setFormatter(LogFormatter())
  213. logger.addHandler(channel)
  214. def define_logging_options(options=None):
  215. """Add logging-related flags to ``options``.
  216. These options are present automatically on the default options instance;
  217. this method is only necessary if you have created your own `.OptionParser`.
  218. .. versionadded:: 4.2
  219. This function existed in prior versions but was broken and undocumented until 4.2.
  220. """
  221. if options is None:
  222. # late import to prevent cycle
  223. import tornado.options
  224. options = tornado.options.options
  225. options.define("logging", default="info",
  226. help=("Set the Python log level. If 'none', tornado won't touch the "
  227. "logging configuration."),
  228. metavar="debug|info|warning|error|none")
  229. options.define("log_to_stderr", type=bool, default=None,
  230. help=("Send log output to stderr (colorized if possible). "
  231. "By default use stderr if --log_file_prefix is not set and "
  232. "no other logging is configured."))
  233. options.define("log_file_prefix", type=str, default=None, metavar="PATH",
  234. help=("Path prefix for log files. "
  235. "Note that if you are running multiple tornado processes, "
  236. "log_file_prefix must be different for each of them (e.g. "
  237. "include the port number)"))
  238. options.define("log_file_max_size", type=int, default=100 * 1000 * 1000,
  239. help="max size of log files before rollover")
  240. options.define("log_file_num_backups", type=int, default=10,
  241. help="number of log files to keep")
  242. options.define("log_rotate_when", type=str, default='midnight',
  243. help=("specify the type of TimedRotatingFileHandler interval "
  244. "other options:('S', 'M', 'H', 'D', 'W0'-'W6')"))
  245. options.define("log_rotate_interval", type=int, default=1,
  246. help="The interval value of timed rotating")
  247. options.define("log_rotate_mode", type=str, default='size',
  248. help="The mode of rotating files(time or size)")
  249. options.add_parse_callback(lambda: enable_pretty_logging(options))