00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016 """Logging support for Tornado.
00017
00018 Tornado uses three logger streams:
00019
00020 * ``tornado.access``: Per-request logging for Tornado's HTTP servers (and
00021 potentially other servers in the future)
00022 * ``tornado.application``: Logging of errors from application code (i.e.
00023 uncaught exceptions from callbacks)
00024 * ``tornado.general``: General-purpose logging, including any errors
00025 or warnings from Tornado itself.
00026
00027 These streams may be configured independently using the standard library's
00028 `logging` module. For example, you may wish to send ``tornado.access`` logs
00029 to a separate file for analysis.
00030 """
00031 from __future__ import absolute_import, division, print_function, with_statement
00032
00033 import logging
00034 import logging.handlers
00035 import sys
00036
00037 from tornado.escape import _unicode
00038 from tornado.util import unicode_type, basestring_type
00039
00040 try:
00041 import curses
00042 except ImportError:
00043 curses = None
00044
00045
00046 access_log = logging.getLogger("tornado.access")
00047 app_log = logging.getLogger("tornado.application")
00048 gen_log = logging.getLogger("tornado.general")
00049
00050
00051 def _stderr_supports_color():
00052 color = False
00053 if curses and hasattr(sys.stderr, 'isatty') and sys.stderr.isatty():
00054 try:
00055 curses.setupterm()
00056 if curses.tigetnum("colors") > 0:
00057 color = True
00058 except Exception:
00059 pass
00060 return color
00061
00062
00063 def _safe_unicode(s):
00064 try:
00065 return _unicode(s)
00066 except UnicodeDecodeError:
00067 return repr(s)
00068
00069
00070 class LogFormatter(logging.Formatter):
00071 """Log formatter used in Tornado.
00072
00073 Key features of this formatter are:
00074
00075 * Color support when logging to a terminal that supports it.
00076 * Timestamps on every log line.
00077 * Robust against str/bytes encoding problems.
00078
00079 This formatter is enabled automatically by
00080 `tornado.options.parse_command_line` (unless ``--logging=none`` is
00081 used).
00082 """
00083 DEFAULT_FORMAT = '%(color)s[%(levelname)1.1s %(asctime)s %(module)s:%(lineno)d]%(end_color)s %(message)s'
00084 DEFAULT_DATE_FORMAT = '%y%m%d %H:%M:%S'
00085 DEFAULT_COLORS = {
00086 logging.DEBUG: 4,
00087 logging.INFO: 2,
00088 logging.WARNING: 3,
00089 logging.ERROR: 1,
00090 }
00091
00092 def __init__(self, color=True, fmt=DEFAULT_FORMAT,
00093 datefmt=DEFAULT_DATE_FORMAT, colors=DEFAULT_COLORS):
00094 r"""
00095 :arg bool color: Enables color support.
00096 :arg string fmt: Log message format.
00097 It will be applied to the attributes dict of log records. The
00098 text between ``%(color)s`` and ``%(end_color)s`` will be colored
00099 depending on the level if color support is on.
00100 :arg dict colors: color mappings from logging level to terminal color
00101 code
00102 :arg string datefmt: Datetime format.
00103 Used for formatting ``(asctime)`` placeholder in ``prefix_fmt``.
00104
00105 .. versionchanged:: 3.2
00106
00107 Added ``fmt`` and ``datefmt`` arguments.
00108 """
00109 logging.Formatter.__init__(self, datefmt=datefmt)
00110 self._fmt = fmt
00111
00112 self._colors = {}
00113 if color and _stderr_supports_color():
00114
00115
00116
00117
00118
00119
00120
00121 fg_color = (curses.tigetstr("setaf") or
00122 curses.tigetstr("setf") or "")
00123 if (3, 0) < sys.version_info < (3, 2, 3):
00124 fg_color = unicode_type(fg_color, "ascii")
00125
00126 for levelno, code in colors.items():
00127 self._colors[levelno] = unicode_type(curses.tparm(fg_color, code), "ascii")
00128 self._normal = unicode_type(curses.tigetstr("sgr0"), "ascii")
00129 else:
00130 self._normal = ''
00131
00132 def format(self, record):
00133 try:
00134 message = record.getMessage()
00135 assert isinstance(message, basestring_type)
00136
00137
00138
00139
00140
00141
00142
00143
00144
00145
00146
00147
00148
00149
00150
00151
00152 record.message = _safe_unicode(message)
00153 except Exception as e:
00154 record.message = "Bad message (%r): %r" % (e, record.__dict__)
00155
00156 record.asctime = self.formatTime(record, self.datefmt)
00157
00158 if record.levelno in self._colors:
00159 record.color = self._colors[record.levelno]
00160 record.end_color = self._normal
00161 else:
00162 record.color = record.end_color = ''
00163
00164 formatted = self._fmt % record.__dict__
00165
00166 if record.exc_info:
00167 if not record.exc_text:
00168 record.exc_text = self.formatException(record.exc_info)
00169 if record.exc_text:
00170
00171
00172
00173 lines = [formatted.rstrip()]
00174 lines.extend(_safe_unicode(ln) for ln in record.exc_text.split('\n'))
00175 formatted = '\n'.join(lines)
00176 return formatted.replace("\n", "\n ")
00177
00178
00179 def enable_pretty_logging(options=None, logger=None):
00180 """Turns on formatted logging output as configured.
00181
00182 This is called automatically by `tornado.options.parse_command_line`
00183 and `tornado.options.parse_config_file`.
00184 """
00185 if options is None:
00186 from tornado.options import options
00187 if options.logging is None or options.logging.lower() == 'none':
00188 return
00189 if logger is None:
00190 logger = logging.getLogger()
00191 logger.setLevel(getattr(logging, options.logging.upper()))
00192 if options.log_file_prefix:
00193 channel = logging.handlers.RotatingFileHandler(
00194 filename=options.log_file_prefix,
00195 maxBytes=options.log_file_max_size,
00196 backupCount=options.log_file_num_backups)
00197 channel.setFormatter(LogFormatter(color=False))
00198 logger.addHandler(channel)
00199
00200 if (options.log_to_stderr or
00201 (options.log_to_stderr is None and not logger.handlers)):
00202
00203 channel = logging.StreamHandler()
00204 channel.setFormatter(LogFormatter())
00205 logger.addHandler(channel)
00206
00207
00208 def define_logging_options(options=None):
00209 if options is None:
00210
00211 from tornado.options import options
00212 options.define("logging", default="info",
00213 help=("Set the Python log level. If 'none', tornado won't touch the "
00214 "logging configuration."),
00215 metavar="debug|info|warning|error|none")
00216 options.define("log_to_stderr", type=bool, default=None,
00217 help=("Send log output to stderr (colorized if possible). "
00218 "By default use stderr if --log_file_prefix is not set and "
00219 "no other logging is configured."))
00220 options.define("log_file_prefix", type=str, default=None, metavar="PATH",
00221 help=("Path prefix for log files. "
00222 "Note that if you are running multiple tornado processes, "
00223 "log_file_prefix must be different for each of them (e.g. "
00224 "include the port number)"))
00225 options.define("log_file_max_size", type=int, default=100 * 1000 * 1000,
00226 help="max size of log files before rollover")
00227 options.define("log_file_num_backups", type=int, default=10,
00228 help="number of log files to keep")
00229
00230 options.add_parse_callback(enable_pretty_logging)