You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

logging.py 9.5KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318
  1. from __future__ import absolute_import
  2. import contextlib
  3. import errno
  4. import logging
  5. import logging.handlers
  6. import os
  7. import sys
  8. from pip._vendor.six import PY2
  9. from pip._internal.utils.compat import WINDOWS
  10. from pip._internal.utils.misc import ensure_dir
  11. try:
  12. import threading
  13. except ImportError:
  14. import dummy_threading as threading # type: ignore
  15. try:
  16. from pip._vendor import colorama
  17. # Lots of different errors can come from this, including SystemError and
  18. # ImportError.
  19. except Exception:
  20. colorama = None
  21. _log_state = threading.local()
  22. _log_state.indentation = 0
  23. class BrokenStdoutLoggingError(Exception):
  24. """
  25. Raised if BrokenPipeError occurs for the stdout stream while logging.
  26. """
  27. pass
  28. # BrokenPipeError does not exist in Python 2 and, in addition, manifests
  29. # differently in Windows and non-Windows.
  30. if WINDOWS:
  31. # In Windows, a broken pipe can show up as EINVAL rather than EPIPE:
  32. # https://bugs.python.org/issue19612
  33. # https://bugs.python.org/issue30418
  34. if PY2:
  35. def _is_broken_pipe_error(exc_class, exc):
  36. """See the docstring for non-Windows Python 3 below."""
  37. return (exc_class is IOError and
  38. exc.errno in (errno.EINVAL, errno.EPIPE))
  39. else:
  40. # In Windows, a broken pipe IOError became OSError in Python 3.
  41. def _is_broken_pipe_error(exc_class, exc):
  42. """See the docstring for non-Windows Python 3 below."""
  43. return ((exc_class is BrokenPipeError) or # noqa: F821
  44. (exc_class is OSError and
  45. exc.errno in (errno.EINVAL, errno.EPIPE)))
  46. elif PY2:
  47. def _is_broken_pipe_error(exc_class, exc):
  48. """See the docstring for non-Windows Python 3 below."""
  49. return (exc_class is IOError and exc.errno == errno.EPIPE)
  50. else:
  51. # Then we are in the non-Windows Python 3 case.
  52. def _is_broken_pipe_error(exc_class, exc):
  53. """
  54. Return whether an exception is a broken pipe error.
  55. Args:
  56. exc_class: an exception class.
  57. exc: an exception instance.
  58. """
  59. return (exc_class is BrokenPipeError) # noqa: F821
  60. @contextlib.contextmanager
  61. def indent_log(num=2):
  62. """
  63. A context manager which will cause the log output to be indented for any
  64. log messages emitted inside it.
  65. """
  66. _log_state.indentation += num
  67. try:
  68. yield
  69. finally:
  70. _log_state.indentation -= num
  71. def get_indentation():
  72. return getattr(_log_state, 'indentation', 0)
  73. class IndentingFormatter(logging.Formatter):
  74. def __init__(self, *args, **kwargs):
  75. """
  76. A logging.Formatter obeying containing indent_log contexts.
  77. :param add_timestamp: A bool indicating output lines should be prefixed
  78. with their record's timestamp.
  79. """
  80. self.add_timestamp = kwargs.pop("add_timestamp", False)
  81. super(IndentingFormatter, self).__init__(*args, **kwargs)
  82. def format(self, record):
  83. """
  84. Calls the standard formatter, but will indent all of the log messages
  85. by our current indentation level.
  86. """
  87. formatted = super(IndentingFormatter, self).format(record)
  88. prefix = ''
  89. if self.add_timestamp:
  90. prefix = self.formatTime(record, "%Y-%m-%dT%H:%M:%S ")
  91. prefix += " " * get_indentation()
  92. formatted = "".join([
  93. prefix + line
  94. for line in formatted.splitlines(True)
  95. ])
  96. return formatted
  97. def _color_wrap(*colors):
  98. def wrapped(inp):
  99. return "".join(list(colors) + [inp, colorama.Style.RESET_ALL])
  100. return wrapped
  101. class ColorizedStreamHandler(logging.StreamHandler):
  102. # Don't build up a list of colors if we don't have colorama
  103. if colorama:
  104. COLORS = [
  105. # This needs to be in order from highest logging level to lowest.
  106. (logging.ERROR, _color_wrap(colorama.Fore.RED)),
  107. (logging.WARNING, _color_wrap(colorama.Fore.YELLOW)),
  108. ]
  109. else:
  110. COLORS = []
  111. def __init__(self, stream=None, no_color=None):
  112. logging.StreamHandler.__init__(self, stream)
  113. self._no_color = no_color
  114. if WINDOWS and colorama:
  115. self.stream = colorama.AnsiToWin32(self.stream)
  116. def _using_stdout(self):
  117. """
  118. Return whether the handler is using sys.stdout.
  119. """
  120. if WINDOWS and colorama:
  121. # Then self.stream is an AnsiToWin32 object.
  122. return self.stream.wrapped is sys.stdout
  123. return self.stream is sys.stdout
  124. def should_color(self):
  125. # Don't colorize things if we do not have colorama or if told not to
  126. if not colorama or self._no_color:
  127. return False
  128. real_stream = (
  129. self.stream if not isinstance(self.stream, colorama.AnsiToWin32)
  130. else self.stream.wrapped
  131. )
  132. # If the stream is a tty we should color it
  133. if hasattr(real_stream, "isatty") and real_stream.isatty():
  134. return True
  135. # If we have an ANSI term we should color it
  136. if os.environ.get("TERM") == "ANSI":
  137. return True
  138. # If anything else we should not color it
  139. return False
  140. def format(self, record):
  141. msg = logging.StreamHandler.format(self, record)
  142. if self.should_color():
  143. for level, color in self.COLORS:
  144. if record.levelno >= level:
  145. msg = color(msg)
  146. break
  147. return msg
  148. # The logging module says handleError() can be customized.
  149. def handleError(self, record):
  150. exc_class, exc = sys.exc_info()[:2]
  151. # If a broken pipe occurred while calling write() or flush() on the
  152. # stdout stream in logging's Handler.emit(), then raise our special
  153. # exception so we can handle it in main() instead of logging the
  154. # broken pipe error and continuing.
  155. if (exc_class and self._using_stdout() and
  156. _is_broken_pipe_error(exc_class, exc)):
  157. raise BrokenStdoutLoggingError()
  158. return super(ColorizedStreamHandler, self).handleError(record)
  159. class BetterRotatingFileHandler(logging.handlers.RotatingFileHandler):
  160. def _open(self):
  161. ensure_dir(os.path.dirname(self.baseFilename))
  162. return logging.handlers.RotatingFileHandler._open(self)
  163. class MaxLevelFilter(logging.Filter):
  164. def __init__(self, level):
  165. self.level = level
  166. def filter(self, record):
  167. return record.levelno < self.level
  168. def setup_logging(verbosity, no_color, user_log_file):
  169. """Configures and sets up all of the logging
  170. Returns the requested logging level, as its integer value.
  171. """
  172. # Determine the level to be logging at.
  173. if verbosity >= 1:
  174. level = "DEBUG"
  175. elif verbosity == -1:
  176. level = "WARNING"
  177. elif verbosity == -2:
  178. level = "ERROR"
  179. elif verbosity <= -3:
  180. level = "CRITICAL"
  181. else:
  182. level = "INFO"
  183. level_number = getattr(logging, level)
  184. # The "root" logger should match the "console" level *unless* we also need
  185. # to log to a user log file.
  186. include_user_log = user_log_file is not None
  187. if include_user_log:
  188. additional_log_file = user_log_file
  189. root_level = "DEBUG"
  190. else:
  191. additional_log_file = "/dev/null"
  192. root_level = level
  193. # Disable any logging besides WARNING unless we have DEBUG level logging
  194. # enabled for vendored libraries.
  195. vendored_log_level = "WARNING" if level in ["INFO", "ERROR"] else "DEBUG"
  196. # Shorthands for clarity
  197. log_streams = {
  198. "stdout": "ext://sys.stdout",
  199. "stderr": "ext://sys.stderr",
  200. }
  201. handler_classes = {
  202. "stream": "pip._internal.utils.logging.ColorizedStreamHandler",
  203. "file": "pip._internal.utils.logging.BetterRotatingFileHandler",
  204. }
  205. logging.config.dictConfig({
  206. "version": 1,
  207. "disable_existing_loggers": False,
  208. "filters": {
  209. "exclude_warnings": {
  210. "()": "pip._internal.utils.logging.MaxLevelFilter",
  211. "level": logging.WARNING,
  212. },
  213. },
  214. "formatters": {
  215. "indent": {
  216. "()": IndentingFormatter,
  217. "format": "%(message)s",
  218. },
  219. "indent_with_timestamp": {
  220. "()": IndentingFormatter,
  221. "format": "%(message)s",
  222. "add_timestamp": True,
  223. },
  224. },
  225. "handlers": {
  226. "console": {
  227. "level": level,
  228. "class": handler_classes["stream"],
  229. "no_color": no_color,
  230. "stream": log_streams["stdout"],
  231. "filters": ["exclude_warnings"],
  232. "formatter": "indent",
  233. },
  234. "console_errors": {
  235. "level": "WARNING",
  236. "class": handler_classes["stream"],
  237. "no_color": no_color,
  238. "stream": log_streams["stderr"],
  239. "formatter": "indent",
  240. },
  241. "user_log": {
  242. "level": "DEBUG",
  243. "class": handler_classes["file"],
  244. "filename": additional_log_file,
  245. "delay": True,
  246. "formatter": "indent_with_timestamp",
  247. },
  248. },
  249. "root": {
  250. "level": root_level,
  251. "handlers": ["console", "console_errors"] + (
  252. ["user_log"] if include_user_log else []
  253. ),
  254. },
  255. "loggers": {
  256. "pip._vendor": {
  257. "level": vendored_log_level
  258. }
  259. },
  260. })
  261. return level_number