Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
log.py344 linesDownload Raw Back to tornado
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
17Tornado uses three logger streams:
18
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.
25
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
33
34from tornado.escape import _unicode
35from tornado.util import unicode_type, basestring_type
36
37try:
38    import colorama  # type: ignore
39except ImportError:
40    colorama = None
41
42try:
43    import curses
44except ImportError:
45    curses = None  # type: ignore
46
47from typing import Dict, Any, cast, Optional
48
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")
53
54
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
72
73
74def _safe_unicode(s: Any) -> str:
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
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    }
115
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``.
134
135        .. versionchanged:: 3.2
136
137           Added ``fmt`` and ``datefmt`` arguments.
138        """
139        logging.Formatter.__init__(self, datefmt=datefmt)
140        self._fmt = fmt
141
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""
146
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 = ""
167
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 = f"Bad message ({e!r}): {record.__dict__!r}"
191
192        record.asctime = self.formatTime(record, cast(str, self.datefmt))
193
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 = ""
199
200        formatted = self._fmt % record.__dict__
201
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    ")
213
214
215def enable_pretty_logging(
216    options: Any = None, logger: Optional[logging.Logger] = None
217) -> None:
218    """Turns on formatted logging output as configured.
219
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
225
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)
257
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)
263
264
265def define_logging_options(options: Any = None) -> None:
266    """Add logging-related flags to ``options``.
267
268    These options are present automatically on the default options instance;
269    this method is only necessary if you have created your own `.OptionParser`.
270
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
277
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    )
319
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    )
335
336    options.define(
337        "log_rotate_mode",
338        type=str,
339        default="size",
340        help="The mode of rotating files(time or size)",
341    )
342
343    options.add_parse_callback(lambda: enable_pretty_logging(options))
344 
codekingpro/portable-devtools · Team Ai