|
1 | | -#!/usr/bin/env python |
2 | | -# Pieces of this code are from Tornado, they are being phased out. |
3 | | -# |
4 | | -# Copyright 2012 Facebook |
5 | | -# |
6 | | -# Licensed under the Apache License, Version 2.0 (the "License"); you may |
7 | | -# not use this file except in compliance with the License. You may obtain |
8 | | -# a copy of the License at |
9 | | -# |
10 | | -# http://www.apache.org/licenses/LICENSE-2.0 |
11 | | -# |
12 | | -# Unless required by applicable law or agreed to in writing, software |
13 | | -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
14 | | -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
15 | | -# License for the specific language governing permissions and limitations |
16 | | -# under the License. |
17 | | -from __future__ import absolute_import, division, print_function, with_statement |
18 | | - |
19 | | -import logging |
20 | | -import logging.handlers |
21 | | -import sys |
22 | | -import time |
23 | | -from ._vendor.colorama import init |
24 | | -from ._vendor.colorama import Fore, Back, Style |
25 | | -from .util import unicode, bytes, basestring |
26 | | - |
27 | | -try: |
28 | | - import curses |
29 | | -except ImportError: |
30 | | - curses = None |
31 | | - |
32 | | -# From tornado/util.py: |
33 | | -# Fake unicode literal support: Python 3.2 doesn't have the u'' marker for |
34 | | -# literal strings, and alternative solutions like "from __future__ import |
35 | | -# unicode_literals" have other problems (see PEP 414). u() can be applied |
36 | | -# to ascii strings that include \u escapes (but they must not contain |
37 | | -# literal non-ascii characters). |
38 | | -# todo _ can remove this, this next 10 lines is from |
39 | | -# http://www.rfk.id.au/blog/entry/preparing-pyenchant-for-python-3/ |
40 | | -_UTF8_TYPES = (bytes, type(None)) |
41 | | -_TO_UNICODE_TYPES = (unicode, type(None)) |
42 | | - |
43 | | - |
44 | | -def utf8(value): |
45 | | - """Convert a string argument to a byte string. |
46 | | -
|
47 | | - If the argument is already a byte string or None, it is returned unchanged. |
48 | | - Otherwise it must be a unicode string and is encoded as utf8. |
49 | | -
|
50 | | - """ |
51 | | - if isinstance(value, _UTF8_TYPES): |
52 | | - return value |
53 | | - assert isinstance(value, unicode), \ |
54 | | - "Expected bytes, unicode, or None; got %r" % type(value) |
55 | | - return value.encode("utf-8") |
56 | | - |
57 | | - |
58 | | -def to_unicode(value): |
59 | | - """Convert a string argument to a unicode string. |
60 | | -
|
61 | | - If the argument is already a unicode string or None, it is returned |
62 | | - unchanged. Otherwise it must be a byte string and is decoded as utf8. |
63 | | -
|
64 | | - """ |
65 | | - if isinstance(value, _TO_UNICODE_TYPES): |
66 | | - return value |
67 | | - assert isinstance(value, bytes), \ |
68 | | - "Expected bytes, unicode, or None; got %r" % type(value) |
69 | | - return value.decode("utf-8") |
70 | | - |
71 | | -# to_unicode was previously named _unicode not because it was private, |
72 | | -# but to avoid conflicts with the built-in unicode() function/type |
73 | | -_unicode = to_unicode |
74 | | - |
75 | | -# When dealing with the standard library across python 2 and 3 it is |
76 | | -# sometimes useful to have a direct conversion to the native string type |
77 | | -if str is unicode: |
78 | | - native_str = to_unicode |
79 | | -else: |
80 | | - native_str = utf8 |
81 | | - |
82 | | - |
83 | | -def _stderr_supports_color(): |
84 | | - color = False |
85 | | - if curses and sys.stderr.isatty(): |
86 | | - try: |
87 | | - curses.setupterm() |
88 | | - if curses.tigetnum("colors") > 0: |
89 | | - color = True |
90 | | - init() |
91 | | - except Exception: |
92 | | - pass |
93 | | - return color |
94 | | - |
95 | | -# Encoding notes: The logging module prefers to work with character |
96 | | -# strings, but only enforces that log messages are instances of |
97 | | -# basestring. In python 2, non-ascii bytestrings will make |
98 | | -# their way through the logging framework until they blow up with |
99 | | -# an unhelpful decoding error (with this formatter it happens |
100 | | -# when we attach the prefix, but there are other opportunities for |
101 | | -# exceptions further along in the framework). |
102 | | -# |
103 | | -# If a byte string makes it this far, convert it to unicode to |
104 | | -# ensure it will make it out to the logs. Use repr() as a fallback |
105 | | -# to ensure that all byte strings can be converted successfully, |
106 | | -# but don't do it by default so we don't add extra quotes to ascii |
107 | | -# bytestrings. This is a bit of a hacky place to do this, but |
108 | | -# it's worth it since the encoding errors that would otherwise |
109 | | -# result are so useless (and tornado is fond of using utf8-encoded |
110 | | -# byte strings whereever possible). |
111 | | - |
112 | | - |
113 | | -def safe_unicode(s): |
114 | | - try: |
115 | | - return _unicode(s) |
116 | | - except UnicodeDecodeError: |
117 | | - return repr(s) |
118 | | - |
119 | | -NORMAL = Fore.RESET + Style.RESET_ALL + Back.RESET |
120 | | - |
121 | | -LEVEL_COLORS = { |
122 | | - 'DEBUG': Fore.BLUE, # Blue |
123 | | - 'INFO': Fore.GREEN, # Green |
124 | | - 'WARNING': Fore.YELLOW, |
125 | | - 'ERROR': Fore.RED, |
126 | | - 'CRITICAL': Fore.RED |
127 | | -} |
128 | | - |
129 | | - |
130 | | -def default_log_template(self, record): |
131 | | - """ Return the prefix for the log message. Template for Formatter. |
132 | | -
|
133 | | - :param: record: :py:class:`logging.LogRecord` object. this is passed in |
134 | | - from inside the :py:meth:`logging.Formatter.format` record. |
135 | | -
|
136 | | - """ |
137 | | - |
138 | | - prefix_template = '' |
139 | | - prefix_template += NORMAL |
140 | | - prefix_template += LEVEL_COLORS.get(record.levelname) + Style.BRIGHT + '(%(levelname)s)' + NORMAL + ' ' |
141 | | - prefix_template += '[' + Fore.BLACK + Style.DIM + Style.BRIGHT + '%(asctime)s' + Fore.RESET + Style.RESET_ALL + ']' |
142 | | - prefix_template += ' ' + Fore.WHITE + Style.DIM + Style.BRIGHT + '%(name)s' + Fore.RESET + Style.RESET_ALL + ' ' |
143 | | - prefix_template += NORMAL |
144 | | - |
145 | | - return prefix_template |
146 | | - |
147 | | - |
148 | | -class LogFormatter(logging.Formatter): |
149 | | - |
150 | | - """Log formatter used in Tornado. |
151 | | -
|
152 | | - Key features of this formatter are: |
153 | | -
|
154 | | - * Color support when logging to a terminal that supports it. |
155 | | - * Timestamps on every log line. |
156 | | - * Robust against str/bytes encoding problems. |
157 | | -
|
158 | | - This formatter is enabled automatically by |
159 | | - `tornado.options.parse_command_line` (unless ``--logging=none`` is |
160 | | - used). |
161 | | -
|
162 | | - """ |
163 | | - |
164 | | - template = default_log_template |
165 | | - |
166 | | - def __init__(self, color=True, *args, **kwargs): |
167 | | - logging.Formatter.__init__(self, *args, **kwargs) |
168 | | - self._color = color and _stderr_supports_color() |
169 | | - |
170 | | - def format(self, record): |
171 | | - try: |
172 | | - record.message = record.getMessage() |
173 | | - except Exception as e: |
174 | | - record.message = "Bad message (%r): %r" % (e, record.__dict__) |
175 | | - assert isinstance( |
176 | | - record.message, basestring) # guaranteed by logging |
177 | | - |
178 | | - date_format = '%H:%m:%S' |
179 | | - record.asctime = time.strftime(date_format, self.converter(record.created)) |
180 | | - |
181 | | - prefix = self.template(record) % record.__dict__ |
182 | | - |
183 | | - formatted = prefix + " " + safe_unicode(record.message) |
184 | | - if record.exc_info: |
185 | | - if not record.exc_text: |
186 | | - record.exc_text = self.formatException(record.exc_info) |
187 | | - if record.exc_text: |
188 | | - # exc_text contains multiple lines. We need to safe_unicode |
189 | | - # each line separately so that non-utf8 bytes don't cause |
190 | | - # all the newlines to turn into '\n'. |
191 | | - lines = [formatted.rstrip()] |
192 | | - lines.extend(safe_unicode(ln) |
193 | | - for ln in record.exc_text.split('\n')) |
194 | | - formatted = '\n'.join(lines) |
195 | | - return formatted.replace("\n", "\n ") |
196 | | - |
197 | | - |
198 | | -def debug_log_template(self, record): |
199 | | - """ Return the prefix for the log message. Template for Formatter. |
200 | | -
|
201 | | - :param: record: :py:class:`logging.LogRecord` object. this is passed in |
202 | | - from inside the :py:meth:`logging.Formatter.format` record. |
203 | | -
|
204 | | - """ |
205 | | - |
206 | | - prefix_template = '' |
207 | | - prefix_template += NORMAL |
208 | | - prefix_template += LEVEL_COLORS.get(record.levelname) + Style.BRIGHT + '(%(levelname)1.1s)' + NORMAL + ' ' |
209 | | - prefix_template += '[' + Fore.BLACK + Style.DIM + Style.BRIGHT + '%(asctime)s' + Fore.RESET + Style.RESET_ALL + ']' |
210 | | - prefix_template += ' ' + Fore.WHITE + Style.DIM + Style.BRIGHT + '%(name)s' + Fore.RESET + Style.RESET_ALL + ' ' |
211 | | - prefix_template += Fore.GREEN + Style.BRIGHT + '%(module)s.%(funcName)s()' |
212 | | - prefix_template += Fore.BLACK + Style.DIM + Style.BRIGHT + ':' + NORMAL + Fore.CYAN + '%(lineno)d' |
213 | | - prefix_template += NORMAL |
214 | | - |
215 | | - return prefix_template |
216 | | - |
217 | | - |
218 | | -class DebugLogFormatter(LogFormatter): |
219 | | - |
220 | | - """Provides greater technical details than standard log Formatter.""" |
221 | | - |
222 | | - template = debug_log_template |
0 commit comments