Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/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"""
31import logging
32import logging.handlers
33import sys
35from tornado.escape import _unicode
36from tornado.util import basestring_type, unicode_type
38try:
39 import colorama # type: ignore
40except ImportError:
41 colorama = None
43try:
44 import curses
45except ImportError:
46 curses = None # type: ignore
48from typing import Any, cast
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")
56def _stderr_supports_color() -> bool:
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(
65 colorama.initialise, "wrapped_stderr", object()
66 ):
67 return True
68 except Exception:
69 # Very broad exception handling because it's always better to
70 # fall back to non-colored logs than to break at startup.
71 pass
72 return False
75def _safe_unicode(s: Any) -> str:
76 try:
77 return _unicode(s)
78 except UnicodeDecodeError:
79 return repr(s)
82class LogFormatter(logging.Formatter):
83 """Log formatter used in Tornado.
85 Key features of this formatter are:
87 * Color support when logging to a terminal that supports it.
88 * Timestamps on every log line.
89 * Robust against str/bytes encoding problems.
91 This formatter is enabled automatically by
92 `tornado.options.parse_command_line` or `tornado.options.parse_config_file`
93 (unless ``--logging=none`` is used).
95 Color support on Windows versions that do not support ANSI color codes is
96 enabled by use of the colorama__ library. Applications that wish to use
97 this must first initialize colorama with a call to ``colorama.init``.
98 See the colorama documentation for details.
100 __ https://pypi.python.org/pypi/colorama
102 .. versionchanged:: 4.5
103 Added support for ``colorama``. Changed the constructor
104 signature to be compatible with `logging.config.dictConfig`.
105 """
107 DEFAULT_FORMAT = "%(color)s[%(levelname)1.1s %(asctime)s %(module)s:%(lineno)d]%(end_color)s %(message)s" # noqa: E501
108 DEFAULT_DATE_FORMAT = "%y%m%d %H:%M:%S"
109 DEFAULT_COLORS = {
110 logging.DEBUG: 4, # Blue
111 logging.INFO: 2, # Green
112 logging.WARNING: 3, # Yellow
113 logging.ERROR: 1, # Red
114 logging.CRITICAL: 5, # Magenta
115 }
117 def __init__(
118 self,
119 fmt: str = DEFAULT_FORMAT,
120 datefmt: str = DEFAULT_DATE_FORMAT,
121 style: str = "%",
122 color: bool = True,
123 colors: dict[int, int] = DEFAULT_COLORS,
124 ) -> None:
125 r"""
126 :arg bool color: Enables color support.
127 :arg str fmt: Log message format.
128 It will be applied to the attributes dict of log records. The
129 text between ``%(color)s`` and ``%(end_color)s`` will be colored
130 depending on the level if color support is on.
131 :arg dict colors: color mappings from logging level to terminal color
132 code
133 :arg str datefmt: Datetime format.
134 Used for formatting ``(asctime)`` placeholder in ``prefix_fmt``.
136 .. versionchanged:: 3.2
138 Added ``fmt`` and ``datefmt`` arguments.
139 """
140 logging.Formatter.__init__(self, datefmt=datefmt)
141 self._fmt = fmt
143 self._colors: dict[int, str] = {}
144 if color and _stderr_supports_color():
145 if curses is not None:
146 fg_color = curses.tigetstr("setaf") or curses.tigetstr("setf") or b""
148 for levelno, code in colors.items():
149 # Convert the terminal control characters from
150 # bytes to unicode strings for easier use with the
151 # logging module.
152 self._colors[levelno] = unicode_type(
153 curses.tparm(fg_color, code), "ascii"
154 )
155 normal = curses.tigetstr("sgr0")
156 if normal is not None:
157 self._normal = unicode_type(normal, "ascii")
158 else:
159 self._normal = ""
160 else:
161 # If curses is not present (currently we'll only get here for
162 # colorama on windows), assume hard-coded ANSI color codes.
163 for levelno, code in colors.items():
164 self._colors[levelno] = "\033[2;3%dm" % code
165 self._normal = "\033[0m"
166 else:
167 self._normal = ""
169 def format(self, record: Any) -> str:
170 try:
171 message = record.getMessage()
172 assert isinstance(message, basestring_type) # guaranteed by logging
173 # Encoding notes: The logging module prefers to work with character
174 # strings, but only enforces that log messages are instances of
175 # basestring. In python 2, non-ascii bytestrings will make
176 # their way through the logging framework until they blow up with
177 # an unhelpful decoding error (with this formatter it happens
178 # when we attach the prefix, but there are other opportunities for
179 # exceptions further along in the framework).
180 #
181 # If a byte string makes it this far, convert it to unicode to
182 # ensure it will make it out to the logs. Use repr() as a fallback
183 # to ensure that all byte strings can be converted successfully,
184 # but don't do it by default so we don't add extra quotes to ascii
185 # bytestrings. This is a bit of a hacky place to do this, but
186 # it's worth it since the encoding errors that would otherwise
187 # result are so useless (and tornado is fond of using utf8-encoded
188 # byte strings wherever possible).
189 record.message = _safe_unicode(message)
190 except Exception as e:
191 record.message = f"Bad message ({e!r}): {record.__dict__!r}"
193 record.asctime = self.formatTime(record, cast(str, self.datefmt))
195 if record.levelno in self._colors:
196 record.color = self._colors[record.levelno]
197 record.end_color = self._normal
198 else:
199 record.color = record.end_color = ""
201 formatted = self._fmt % record.__dict__
203 if record.exc_info:
204 if not record.exc_text:
205 record.exc_text = self.formatException(record.exc_info)
206 if record.exc_text:
207 # exc_text contains multiple lines. We need to _safe_unicode
208 # each line separately so that non-utf8 bytes don't cause
209 # all the newlines to turn into '\n'.
210 lines = [formatted.rstrip()]
211 lines.extend(_safe_unicode(ln) for ln in record.exc_text.split("\n"))
212 formatted = "\n".join(lines)
213 return formatted.replace("\n", "\n ")
216def enable_pretty_logging(
217 options: Any = None, logger: logging.Logger | None = None
218) -> None:
219 """Turns on formatted logging output as configured.
221 This is called automatically by `tornado.options.parse_command_line`
222 and `tornado.options.parse_config_file`.
223 """
224 if options is None:
225 import tornado.options
227 options = tornado.options.options
228 if options.logging is None or options.logging.lower() == "none":
229 return
230 if logger is None:
231 logger = logging.getLogger()
232 logger.setLevel(getattr(logging, options.logging.upper()))
233 if options.log_file_prefix:
234 rotate_mode = options.log_rotate_mode
235 if rotate_mode == "size":
236 channel: logging.Handler = logging.handlers.RotatingFileHandler(
237 filename=options.log_file_prefix,
238 maxBytes=options.log_file_max_size,
239 backupCount=options.log_file_num_backups,
240 encoding="utf-8",
241 )
242 elif rotate_mode == "time":
243 channel = logging.handlers.TimedRotatingFileHandler(
244 filename=options.log_file_prefix,
245 when=options.log_rotate_when,
246 interval=options.log_rotate_interval,
247 backupCount=options.log_file_num_backups,
248 encoding="utf-8",
249 )
250 else:
251 error_message = (
252 "The value of log_rotate_mode option should be "
253 + '"size" or "time", not "%s".' % rotate_mode
254 )
255 raise ValueError(error_message)
256 channel.setFormatter(LogFormatter(color=False))
257 logger.addHandler(channel)
259 if options.log_to_stderr or (options.log_to_stderr is None and not logger.handlers):
260 # Set up color if we are in a tty and curses is installed
261 channel = logging.StreamHandler()
262 channel.setFormatter(LogFormatter())
263 logger.addHandler(channel)
266def define_logging_options(options: Any = None) -> None:
267 """Add logging-related flags to ``options``.
269 These options are present automatically on the default options instance;
270 this method is only necessary if you have created your own `.OptionParser`.
272 .. versionadded:: 4.2
273 This function existed in prior versions but was broken and undocumented until 4.2.
274 """
275 if options is None:
276 # late import to prevent cycle
277 import tornado.options
279 options = tornado.options.options
280 options.define(
281 "logging",
282 default="info",
283 help=(
284 "Set the Python log level. If 'none', tornado won't touch the "
285 "logging configuration."
286 ),
287 metavar="debug|info|warning|error|none",
288 )
289 options.define(
290 "log_to_stderr",
291 type=bool,
292 default=None,
293 help=(
294 "Send log output to stderr (colorized if possible). "
295 "By default use stderr if --log_file_prefix is not set and "
296 "no other logging is configured."
297 ),
298 )
299 options.define(
300 "log_file_prefix",
301 type=str,
302 default=None,
303 metavar="PATH",
304 help=(
305 "Path prefix for log files. "
306 "Note that if you are running multiple tornado processes, "
307 "log_file_prefix must be different for each of them (e.g. "
308 "include the port number)"
309 ),
310 )
311 options.define(
312 "log_file_max_size",
313 type=int,
314 default=100 * 1000 * 1000,
315 help="max size of log files before rollover",
316 )
317 options.define(
318 "log_file_num_backups", type=int, default=10, help="number of log files to keep"
319 )
321 options.define(
322 "log_rotate_when",
323 type=str,
324 default="midnight",
325 help=(
326 "specify the type of TimedRotatingFileHandler interval "
327 "other options:('S', 'M', 'H', 'D', 'W0'-'W6')"
328 ),
329 )
330 options.define(
331 "log_rotate_interval",
332 type=int,
333 default=1,
334 help="The interval value of timed rotating",
335 )
337 options.define(
338 "log_rotate_mode",
339 type=str,
340 default="size",
341 help="The mode of rotating files(time or size)",
342 )
344 options.add_parse_callback(lambda: enable_pretty_logging(options))