Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/tornado/log.py: 23%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
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.
17Tornado uses three logger streams:
19* ``tornado.access``: Per-request logging for Tornado's HTTP servers (and
20 potentially other servers in the future)
21* ``tornado.application``: Logging of errors from application code (i.e.
22 uncaught exceptions from callbacks)
23* ``tornado.general``: General-purpose logging, including any errors
24 or warnings from Tornado itself.
26These streams may be configured independently using the standard library's
27`logging` module. For example, you may wish to send ``tornado.access`` logs
28to a separate file for analysis.
29"""
30import logging
31import logging.handlers
32import sys
34from tornado.escape import _unicode
35from tornado.util import unicode_type, basestring_type
37try:
38 import colorama # type: ignore
39except ImportError:
40 colorama = None
42try:
43 import curses
44except ImportError:
45 curses = None # type: ignore
47from typing import Dict, Any, cast, Optional
49# Logger objects for internal tornado use
50access_log = logging.getLogger("tornado.access")
51app_log = logging.getLogger("tornado.application")
52gen_log = logging.getLogger("tornado.general")
55def _stderr_supports_color() -> bool:
56 try:
57 if hasattr(sys.stderr, "isatty") and sys.stderr.isatty():
58 if curses:
59 curses.setupterm()
60 if curses.tigetnum("colors") > 0:
61 return True
62 elif colorama:
63 if sys.stderr is getattr(
64 colorama.initialise, "wrapped_stderr", object()
65 ):
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
74def _safe_unicode(s: Any) -> str:
75 try:
76 return _unicode(s)
77 except UnicodeDecodeError:
78 return repr(s)
81class LogFormatter(logging.Formatter):
82 """Log formatter used in Tornado.
84 Key features of this formatter are:
86 * Color support when logging to a terminal that supports it.
87 * Timestamps on every log line.
88 * Robust against str/bytes encoding problems.
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).
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.
99 __ https://pypi.python.org/pypi/colorama
101 .. versionchanged:: 4.5
102 Added support for ``colorama``. Changed the constructor
103 signature to be compatible with `logging.config.dictConfig`.
104 """
106 DEFAULT_FORMAT = "%(color)s[%(levelname)1.1s %(asctime)s %(module)s:%(lineno)d]%(end_color)s %(message)s" # noqa: E501
107 DEFAULT_DATE_FORMAT = "%y%m%d %H:%M:%S"
108 DEFAULT_COLORS = {
109 logging.DEBUG: 4, # Blue
110 logging.INFO: 2, # Green
111 logging.WARNING: 3, # Yellow
112 logging.ERROR: 1, # Red
113 logging.CRITICAL: 5, # Magenta
114 }
116 def __init__(
117 self,
118 fmt: str = DEFAULT_FORMAT,
119 datefmt: str = DEFAULT_DATE_FORMAT,
120 style: str = "%",
121 color: bool = True,
122 colors: Dict[int, int] = DEFAULT_COLORS,
123 ) -> None:
124 r"""
125 :arg bool color: Enables color support.
126 :arg str fmt: Log message format.
127 It will be applied to the attributes dict of log records. The
128 text between ``%(color)s`` and ``%(end_color)s`` will be colored
129 depending on the level if color support is on.
130 :arg dict colors: color mappings from logging level to terminal color
131 code
132 :arg str datefmt: Datetime format.
133 Used for formatting ``(asctime)`` placeholder in ``prefix_fmt``.
135 .. versionchanged:: 3.2
137 Added ``fmt`` and ``datefmt`` arguments.
138 """
139 logging.Formatter.__init__(self, datefmt=datefmt)
140 self._fmt = fmt
142 self._colors = {} # type: Dict[int, str]
143 if color and _stderr_supports_color():
144 if curses is not None:
145 fg_color = curses.tigetstr("setaf") or curses.tigetstr("setf") or b""
147 for levelno, code in colors.items():
148 # Convert the terminal control characters from
149 # bytes to unicode strings for easier use with the
150 # logging module.
151 self._colors[levelno] = unicode_type(
152 curses.tparm(fg_color, code), "ascii"
153 )
154 normal = curses.tigetstr("sgr0")
155 if normal is not None:
156 self._normal = unicode_type(normal, "ascii")
157 else:
158 self._normal = ""
159 else:
160 # If curses is not present (currently we'll only get here for
161 # colorama on windows), assume hard-coded ANSI color codes.
162 for levelno, code in colors.items():
163 self._colors[levelno] = "\033[2;3%dm" % code
164 self._normal = "\033[0m"
165 else:
166 self._normal = ""
168 def format(self, record: Any) -> str:
169 try:
170 message = record.getMessage()
171 assert isinstance(message, basestring_type) # guaranteed by logging
172 # Encoding notes: The logging module prefers to work with character
173 # strings, but only enforces that log messages are instances of
174 # basestring. In python 2, non-ascii bytestrings will make
175 # their way through the logging framework until they blow up with
176 # an unhelpful decoding error (with this formatter it happens
177 # when we attach the prefix, but there are other opportunities for
178 # exceptions further along in the framework).
179 #
180 # If a byte string makes it this far, convert it to unicode to
181 # ensure it will make it out to the logs. Use repr() as a fallback
182 # to ensure that all byte strings can be converted successfully,
183 # but don't do it by default so we don't add extra quotes to ascii
184 # bytestrings. This is a bit of a hacky place to do this, but
185 # it's worth it since the encoding errors that would otherwise
186 # result are so useless (and tornado is fond of using utf8-encoded
187 # byte strings wherever possible).
188 record.message = _safe_unicode(message)
189 except Exception as e:
190 record.message = "Bad message (%r): %r" % (e, record.__dict__)
192 record.asctime = self.formatTime(record, cast(str, self.datefmt))
194 if record.levelno in self._colors:
195 record.color = self._colors[record.levelno]
196 record.end_color = self._normal
197 else:
198 record.color = record.end_color = ""
200 formatted = self._fmt % record.__dict__
202 if record.exc_info:
203 if not record.exc_text:
204 record.exc_text = self.formatException(record.exc_info)
205 if record.exc_text:
206 # exc_text contains multiple lines. We need to _safe_unicode
207 # each line separately so that non-utf8 bytes don't cause
208 # all the newlines to turn into '\n'.
209 lines = [formatted.rstrip()]
210 lines.extend(_safe_unicode(ln) for ln in record.exc_text.split("\n"))
211 formatted = "\n".join(lines)
212 return formatted.replace("\n", "\n ")
215def enable_pretty_logging(
216 options: Any = None, logger: Optional[logging.Logger] = None
217) -> None:
218 """Turns on formatted logging output as configured.
220 This is called automatically by `tornado.options.parse_command_line`
221 and `tornado.options.parse_config_file`.
222 """
223 if options is None:
224 import tornado.options
226 options = tornado.options.options
227 if options.logging is None or options.logging.lower() == "none":
228 return
229 if logger is None:
230 logger = logging.getLogger()
231 logger.setLevel(getattr(logging, options.logging.upper()))
232 if options.log_file_prefix:
233 rotate_mode = options.log_rotate_mode
234 if rotate_mode == "size":
235 channel = logging.handlers.RotatingFileHandler(
236 filename=options.log_file_prefix,
237 maxBytes=options.log_file_max_size,
238 backupCount=options.log_file_num_backups,
239 encoding="utf-8",
240 ) # type: logging.Handler
241 elif rotate_mode == "time":
242 channel = logging.handlers.TimedRotatingFileHandler(
243 filename=options.log_file_prefix,
244 when=options.log_rotate_when,
245 interval=options.log_rotate_interval,
246 backupCount=options.log_file_num_backups,
247 encoding="utf-8",
248 )
249 else:
250 error_message = (
251 "The value of log_rotate_mode option should be "
252 + '"size" or "time", not "%s".' % rotate_mode
253 )
254 raise ValueError(error_message)
255 channel.setFormatter(LogFormatter(color=False))
256 logger.addHandler(channel)
258 if options.log_to_stderr or (options.log_to_stderr is None and not logger.handlers):
259 # Set up color if we are in a tty and curses is installed
260 channel = logging.StreamHandler()
261 channel.setFormatter(LogFormatter())
262 logger.addHandler(channel)
265def define_logging_options(options: Any = None) -> None:
266 """Add logging-related flags to ``options``.
268 These options are present automatically on the default options instance;
269 this method is only necessary if you have created your own `.OptionParser`.
271 .. versionadded:: 4.2
272 This function existed in prior versions but was broken and undocumented until 4.2.
273 """
274 if options is None:
275 # late import to prevent cycle
276 import tornado.options
278 options = tornado.options.options
279 options.define(
280 "logging",
281 default="info",
282 help=(
283 "Set the Python log level. If 'none', tornado won't touch the "
284 "logging configuration."
285 ),
286 metavar="debug|info|warning|error|none",
287 )
288 options.define(
289 "log_to_stderr",
290 type=bool,
291 default=None,
292 help=(
293 "Send log output to stderr (colorized if possible). "
294 "By default use stderr if --log_file_prefix is not set and "
295 "no other logging is configured."
296 ),
297 )
298 options.define(
299 "log_file_prefix",
300 type=str,
301 default=None,
302 metavar="PATH",
303 help=(
304 "Path prefix for log files. "
305 "Note that if you are running multiple tornado processes, "
306 "log_file_prefix must be different for each of them (e.g. "
307 "include the port number)"
308 ),
309 )
310 options.define(
311 "log_file_max_size",
312 type=int,
313 default=100 * 1000 * 1000,
314 help="max size of log files before rollover",
315 )
316 options.define(
317 "log_file_num_backups", type=int, default=10, help="number of log files to keep"
318 )
320 options.define(
321 "log_rotate_when",
322 type=str,
323 default="midnight",
324 help=(
325 "specify the type of TimedRotatingFileHandler interval "
326 "other options:('S', 'M', 'H', 'D', 'W0'-'W6')"
327 ),
328 )
329 options.define(
330 "log_rotate_interval",
331 type=int,
332 default=1,
333 help="The interval value of timed rotating",
334 )
336 options.define(
337 "log_rotate_mode",
338 type=str,
339 default="size",
340 help="The mode of rotating files(time or size)",
341 )
343 options.add_parse_callback(lambda: enable_pretty_logging(options))