comparison planemo/lib/python3.7/site-packages/pip/_internal/utils/logging.py @ 1:56ad4e20f292 draft

"planemo upload commit 6eee67778febed82ddd413c3ca40b3183a3898f1"
author guerler
date Fri, 31 Jul 2020 00:32:28 -0400
parents
children
comparison
equal deleted inserted replaced
0:d30785e31577 1:56ad4e20f292
1 from __future__ import absolute_import
2
3 import contextlib
4 import errno
5 import logging
6 import logging.handlers
7 import os
8 import sys
9 from logging import Filter
10
11 from pip._vendor.six import PY2
12
13 from pip._internal.utils.compat import WINDOWS
14 from pip._internal.utils.deprecation import DEPRECATION_MSG_PREFIX
15 from pip._internal.utils.misc import ensure_dir, subprocess_logger
16
17 try:
18 import threading
19 except ImportError:
20 import dummy_threading as threading # type: ignore
21
22
23 try:
24 # Use "import as" and set colorama in the else clause to avoid mypy
25 # errors and get the following correct revealed type for colorama:
26 # `Union[_importlib_modulespec.ModuleType, None]`
27 # Otherwise, we get an error like the following in the except block:
28 # > Incompatible types in assignment (expression has type "None",
29 # variable has type Module)
30 # TODO: eliminate the need to use "import as" once mypy addresses some
31 # of its issues with conditional imports. Here is an umbrella issue:
32 # https://github.com/python/mypy/issues/1297
33 from pip._vendor import colorama as _colorama
34 # Lots of different errors can come from this, including SystemError and
35 # ImportError.
36 except Exception:
37 colorama = None
38 else:
39 # Import Fore explicitly rather than accessing below as colorama.Fore
40 # to avoid the following error running mypy:
41 # > Module has no attribute "Fore"
42 # TODO: eliminate the need to import Fore once mypy addresses some of its
43 # issues with conditional imports. This particular case could be an
44 # instance of the following issue (but also see the umbrella issue above):
45 # https://github.com/python/mypy/issues/3500
46 from pip._vendor.colorama import Fore
47
48 colorama = _colorama
49
50
51 _log_state = threading.local()
52 _log_state.indentation = 0
53
54
55 class BrokenStdoutLoggingError(Exception):
56 """
57 Raised if BrokenPipeError occurs for the stdout stream while logging.
58 """
59 pass
60
61
62 # BrokenPipeError does not exist in Python 2 and, in addition, manifests
63 # differently in Windows and non-Windows.
64 if WINDOWS:
65 # In Windows, a broken pipe can show up as EINVAL rather than EPIPE:
66 # https://bugs.python.org/issue19612
67 # https://bugs.python.org/issue30418
68 if PY2:
69 def _is_broken_pipe_error(exc_class, exc):
70 """See the docstring for non-Windows Python 3 below."""
71 return (exc_class is IOError and
72 exc.errno in (errno.EINVAL, errno.EPIPE))
73 else:
74 # In Windows, a broken pipe IOError became OSError in Python 3.
75 def _is_broken_pipe_error(exc_class, exc):
76 """See the docstring for non-Windows Python 3 below."""
77 return ((exc_class is BrokenPipeError) or # noqa: F821
78 (exc_class is OSError and
79 exc.errno in (errno.EINVAL, errno.EPIPE)))
80 elif PY2:
81 def _is_broken_pipe_error(exc_class, exc):
82 """See the docstring for non-Windows Python 3 below."""
83 return (exc_class is IOError and exc.errno == errno.EPIPE)
84 else:
85 # Then we are in the non-Windows Python 3 case.
86 def _is_broken_pipe_error(exc_class, exc):
87 """
88 Return whether an exception is a broken pipe error.
89
90 Args:
91 exc_class: an exception class.
92 exc: an exception instance.
93 """
94 return (exc_class is BrokenPipeError) # noqa: F821
95
96
97 @contextlib.contextmanager
98 def indent_log(num=2):
99 """
100 A context manager which will cause the log output to be indented for any
101 log messages emitted inside it.
102 """
103 _log_state.indentation += num
104 try:
105 yield
106 finally:
107 _log_state.indentation -= num
108
109
110 def get_indentation():
111 return getattr(_log_state, 'indentation', 0)
112
113
114 class IndentingFormatter(logging.Formatter):
115
116 def __init__(self, *args, **kwargs):
117 """
118 A logging.Formatter that obeys the indent_log() context manager.
119
120 :param add_timestamp: A bool indicating output lines should be prefixed
121 with their record's timestamp.
122 """
123 self.add_timestamp = kwargs.pop("add_timestamp", False)
124 super(IndentingFormatter, self).__init__(*args, **kwargs)
125
126 def get_message_start(self, formatted, levelno):
127 """
128 Return the start of the formatted log message (not counting the
129 prefix to add to each line).
130 """
131 if levelno < logging.WARNING:
132 return ''
133 if formatted.startswith(DEPRECATION_MSG_PREFIX):
134 # Then the message already has a prefix. We don't want it to
135 # look like "WARNING: DEPRECATION: ...."
136 return ''
137 if levelno < logging.ERROR:
138 return 'WARNING: '
139
140 return 'ERROR: '
141
142 def format(self, record):
143 """
144 Calls the standard formatter, but will indent all of the log message
145 lines by our current indentation level.
146 """
147 formatted = super(IndentingFormatter, self).format(record)
148 message_start = self.get_message_start(formatted, record.levelno)
149 formatted = message_start + formatted
150
151 prefix = ''
152 if self.add_timestamp:
153 # TODO: Use Formatter.default_time_format after dropping PY2.
154 t = self.formatTime(record, "%Y-%m-%dT%H:%M:%S")
155 prefix = '%s,%03d ' % (t, record.msecs)
156 prefix += " " * get_indentation()
157 formatted = "".join([
158 prefix + line
159 for line in formatted.splitlines(True)
160 ])
161 return formatted
162
163
164 def _color_wrap(*colors):
165 def wrapped(inp):
166 return "".join(list(colors) + [inp, colorama.Style.RESET_ALL])
167 return wrapped
168
169
170 class ColorizedStreamHandler(logging.StreamHandler):
171
172 # Don't build up a list of colors if we don't have colorama
173 if colorama:
174 COLORS = [
175 # This needs to be in order from highest logging level to lowest.
176 (logging.ERROR, _color_wrap(Fore.RED)),
177 (logging.WARNING, _color_wrap(Fore.YELLOW)),
178 ]
179 else:
180 COLORS = []
181
182 def __init__(self, stream=None, no_color=None):
183 logging.StreamHandler.__init__(self, stream)
184 self._no_color = no_color
185
186 if WINDOWS and colorama:
187 self.stream = colorama.AnsiToWin32(self.stream)
188
189 def _using_stdout(self):
190 """
191 Return whether the handler is using sys.stdout.
192 """
193 if WINDOWS and colorama:
194 # Then self.stream is an AnsiToWin32 object.
195 return self.stream.wrapped is sys.stdout
196
197 return self.stream is sys.stdout
198
199 def should_color(self):
200 # Don't colorize things if we do not have colorama or if told not to
201 if not colorama or self._no_color:
202 return False
203
204 real_stream = (
205 self.stream if not isinstance(self.stream, colorama.AnsiToWin32)
206 else self.stream.wrapped
207 )
208
209 # If the stream is a tty we should color it
210 if hasattr(real_stream, "isatty") and real_stream.isatty():
211 return True
212
213 # If we have an ANSI term we should color it
214 if os.environ.get("TERM") == "ANSI":
215 return True
216
217 # If anything else we should not color it
218 return False
219
220 def format(self, record):
221 msg = logging.StreamHandler.format(self, record)
222
223 if self.should_color():
224 for level, color in self.COLORS:
225 if record.levelno >= level:
226 msg = color(msg)
227 break
228
229 return msg
230
231 # The logging module says handleError() can be customized.
232 def handleError(self, record):
233 exc_class, exc = sys.exc_info()[:2]
234 # If a broken pipe occurred while calling write() or flush() on the
235 # stdout stream in logging's Handler.emit(), then raise our special
236 # exception so we can handle it in main() instead of logging the
237 # broken pipe error and continuing.
238 if (exc_class and self._using_stdout() and
239 _is_broken_pipe_error(exc_class, exc)):
240 raise BrokenStdoutLoggingError()
241
242 return super(ColorizedStreamHandler, self).handleError(record)
243
244
245 class BetterRotatingFileHandler(logging.handlers.RotatingFileHandler):
246
247 def _open(self):
248 ensure_dir(os.path.dirname(self.baseFilename))
249 return logging.handlers.RotatingFileHandler._open(self)
250
251
252 class MaxLevelFilter(Filter):
253
254 def __init__(self, level):
255 self.level = level
256
257 def filter(self, record):
258 return record.levelno < self.level
259
260
261 class ExcludeLoggerFilter(Filter):
262
263 """
264 A logging Filter that excludes records from a logger (or its children).
265 """
266
267 def filter(self, record):
268 # The base Filter class allows only records from a logger (or its
269 # children).
270 return not super(ExcludeLoggerFilter, self).filter(record)
271
272
273 def setup_logging(verbosity, no_color, user_log_file):
274 """Configures and sets up all of the logging
275
276 Returns the requested logging level, as its integer value.
277 """
278
279 # Determine the level to be logging at.
280 if verbosity >= 1:
281 level = "DEBUG"
282 elif verbosity == -1:
283 level = "WARNING"
284 elif verbosity == -2:
285 level = "ERROR"
286 elif verbosity <= -3:
287 level = "CRITICAL"
288 else:
289 level = "INFO"
290
291 level_number = getattr(logging, level)
292
293 # The "root" logger should match the "console" level *unless* we also need
294 # to log to a user log file.
295 include_user_log = user_log_file is not None
296 if include_user_log:
297 additional_log_file = user_log_file
298 root_level = "DEBUG"
299 else:
300 additional_log_file = "/dev/null"
301 root_level = level
302
303 # Disable any logging besides WARNING unless we have DEBUG level logging
304 # enabled for vendored libraries.
305 vendored_log_level = "WARNING" if level in ["INFO", "ERROR"] else "DEBUG"
306
307 # Shorthands for clarity
308 log_streams = {
309 "stdout": "ext://sys.stdout",
310 "stderr": "ext://sys.stderr",
311 }
312 handler_classes = {
313 "stream": "pip._internal.utils.logging.ColorizedStreamHandler",
314 "file": "pip._internal.utils.logging.BetterRotatingFileHandler",
315 }
316 handlers = ["console", "console_errors", "console_subprocess"] + (
317 ["user_log"] if include_user_log else []
318 )
319
320 logging.config.dictConfig({
321 "version": 1,
322 "disable_existing_loggers": False,
323 "filters": {
324 "exclude_warnings": {
325 "()": "pip._internal.utils.logging.MaxLevelFilter",
326 "level": logging.WARNING,
327 },
328 "restrict_to_subprocess": {
329 "()": "logging.Filter",
330 "name": subprocess_logger.name,
331 },
332 "exclude_subprocess": {
333 "()": "pip._internal.utils.logging.ExcludeLoggerFilter",
334 "name": subprocess_logger.name,
335 },
336 },
337 "formatters": {
338 "indent": {
339 "()": IndentingFormatter,
340 "format": "%(message)s",
341 },
342 "indent_with_timestamp": {
343 "()": IndentingFormatter,
344 "format": "%(message)s",
345 "add_timestamp": True,
346 },
347 },
348 "handlers": {
349 "console": {
350 "level": level,
351 "class": handler_classes["stream"],
352 "no_color": no_color,
353 "stream": log_streams["stdout"],
354 "filters": ["exclude_subprocess", "exclude_warnings"],
355 "formatter": "indent",
356 },
357 "console_errors": {
358 "level": "WARNING",
359 "class": handler_classes["stream"],
360 "no_color": no_color,
361 "stream": log_streams["stderr"],
362 "filters": ["exclude_subprocess"],
363 "formatter": "indent",
364 },
365 # A handler responsible for logging to the console messages
366 # from the "subprocessor" logger.
367 "console_subprocess": {
368 "level": level,
369 "class": handler_classes["stream"],
370 "no_color": no_color,
371 "stream": log_streams["stderr"],
372 "filters": ["restrict_to_subprocess"],
373 "formatter": "indent",
374 },
375 "user_log": {
376 "level": "DEBUG",
377 "class": handler_classes["file"],
378 "filename": additional_log_file,
379 "delay": True,
380 "formatter": "indent_with_timestamp",
381 },
382 },
383 "root": {
384 "level": root_level,
385 "handlers": handlers,
386 },
387 "loggers": {
388 "pip._vendor": {
389 "level": vendored_log_level
390 }
391 },
392 })
393
394 return level_number