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