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

114 statements  

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 = "Bad message (%r): %r" % (e, record.__dict__) 

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))