Path: blob/main/test/lib/python3.9/site-packages/pip/_internal/utils/logging.py
4804 views
import contextlib1import errno2import logging3import logging.handlers4import os5import sys6import threading7from dataclasses import dataclass8from io import TextIOWrapper9from logging import Filter10from typing import Any, ClassVar, Generator, List, Optional, TextIO, Type1112from pip._vendor.rich.console import (13Console,14ConsoleOptions,15ConsoleRenderable,16RenderResult,17)18from pip._vendor.rich.highlighter import NullHighlighter19from pip._vendor.rich.logging import RichHandler20from pip._vendor.rich.segment import Segment21from pip._vendor.rich.style import Style2223from pip._internal.utils._log import VERBOSE, getLogger24from pip._internal.utils.compat import WINDOWS25from pip._internal.utils.deprecation import DEPRECATION_MSG_PREFIX26from pip._internal.utils.misc import ensure_dir2728_log_state = threading.local()29subprocess_logger = getLogger("pip.subprocessor")303132class BrokenStdoutLoggingError(Exception):33"""34Raised if BrokenPipeError occurs for the stdout stream while logging.35"""363738def _is_broken_pipe_error(exc_class: Type[BaseException], exc: BaseException) -> bool:39if exc_class is BrokenPipeError:40return True4142# On Windows, a broken pipe can show up as EINVAL rather than EPIPE:43# https://bugs.python.org/issue1961244# https://bugs.python.org/issue3041845if not WINDOWS:46return False4748return isinstance(exc, OSError) and exc.errno in (errno.EINVAL, errno.EPIPE)495051@contextlib.contextmanager52def indent_log(num: int = 2) -> Generator[None, None, None]:53"""54A context manager which will cause the log output to be indented for any55log messages emitted inside it.56"""57# For thread-safety58_log_state.indentation = get_indentation()59_log_state.indentation += num60try:61yield62finally:63_log_state.indentation -= num646566def get_indentation() -> int:67return getattr(_log_state, "indentation", 0)686970class IndentingFormatter(logging.Formatter):71default_time_format = "%Y-%m-%dT%H:%M:%S"7273def __init__(74self,75*args: Any,76add_timestamp: bool = False,77**kwargs: Any,78) -> None:79"""80A logging.Formatter that obeys the indent_log() context manager.8182:param add_timestamp: A bool indicating output lines should be prefixed83with their record's timestamp.84"""85self.add_timestamp = add_timestamp86super().__init__(*args, **kwargs)8788def get_message_start(self, formatted: str, levelno: int) -> str:89"""90Return the start of the formatted log message (not counting the91prefix to add to each line).92"""93if levelno < logging.WARNING:94return ""95if formatted.startswith(DEPRECATION_MSG_PREFIX):96# Then the message already has a prefix. We don't want it to97# look like "WARNING: DEPRECATION: ...."98return ""99if levelno < logging.ERROR:100return "WARNING: "101102return "ERROR: "103104def format(self, record: logging.LogRecord) -> str:105"""106Calls the standard formatter, but will indent all of the log message107lines by our current indentation level.108"""109formatted = super().format(record)110message_start = self.get_message_start(formatted, record.levelno)111formatted = message_start + formatted112113prefix = ""114if self.add_timestamp:115prefix = f"{self.formatTime(record)} "116prefix += " " * get_indentation()117formatted = "".join([prefix + line for line in formatted.splitlines(True)])118return formatted119120121@dataclass122class IndentedRenderable:123renderable: ConsoleRenderable124indent: int125126def __rich_console__(127self, console: Console, options: ConsoleOptions128) -> RenderResult:129segments = console.render(self.renderable, options)130lines = Segment.split_lines(segments)131for line in lines:132yield Segment(" " * self.indent)133yield from line134yield Segment("\n")135136137class RichPipStreamHandler(RichHandler):138KEYWORDS: ClassVar[Optional[List[str]]] = []139140def __init__(self, stream: Optional[TextIO], no_color: bool) -> None:141super().__init__(142console=Console(file=stream, no_color=no_color, soft_wrap=True),143show_time=False,144show_level=False,145show_path=False,146highlighter=NullHighlighter(),147)148149# Our custom override on Rich's logger, to make things work as we need them to.150def emit(self, record: logging.LogRecord) -> None:151style: Optional[Style] = None152153# If we are given a diagnostic error to present, present it with indentation.154assert isinstance(record.args, tuple)155if record.msg == "[present-rich] %s" and len(record.args) == 1:156rich_renderable = record.args[0]157assert isinstance(158rich_renderable, ConsoleRenderable159), f"{rich_renderable} is not rich-console-renderable"160161renderable: ConsoleRenderable = IndentedRenderable(162rich_renderable, indent=get_indentation()163)164else:165message = self.format(record)166renderable = self.render_message(record, message)167if record.levelno is not None:168if record.levelno >= logging.ERROR:169style = Style(color="red")170elif record.levelno >= logging.WARNING:171style = Style(color="yellow")172173try:174self.console.print(renderable, overflow="ignore", crop=False, style=style)175except Exception:176self.handleError(record)177178def handleError(self, record: logging.LogRecord) -> None:179"""Called when logging is unable to log some output."""180181exc_class, exc = sys.exc_info()[:2]182# If a broken pipe occurred while calling write() or flush() on the183# stdout stream in logging's Handler.emit(), then raise our special184# exception so we can handle it in main() instead of logging the185# broken pipe error and continuing.186if (187exc_class188and exc189and self.console.file is sys.stdout190and _is_broken_pipe_error(exc_class, exc)191):192raise BrokenStdoutLoggingError()193194return super().handleError(record)195196197class BetterRotatingFileHandler(logging.handlers.RotatingFileHandler):198def _open(self) -> TextIOWrapper:199ensure_dir(os.path.dirname(self.baseFilename))200return super()._open()201202203class MaxLevelFilter(Filter):204def __init__(self, level: int) -> None:205self.level = level206207def filter(self, record: logging.LogRecord) -> bool:208return record.levelno < self.level209210211class ExcludeLoggerFilter(Filter):212213"""214A logging Filter that excludes records from a logger (or its children).215"""216217def filter(self, record: logging.LogRecord) -> bool:218# The base Filter class allows only records from a logger (or its219# children).220return not super().filter(record)221222223def setup_logging(verbosity: int, no_color: bool, user_log_file: Optional[str]) -> int:224"""Configures and sets up all of the logging225226Returns the requested logging level, as its integer value.227"""228229# Determine the level to be logging at.230if verbosity >= 2:231level_number = logging.DEBUG232elif verbosity == 1:233level_number = VERBOSE234elif verbosity == -1:235level_number = logging.WARNING236elif verbosity == -2:237level_number = logging.ERROR238elif verbosity <= -3:239level_number = logging.CRITICAL240else:241level_number = logging.INFO242243level = logging.getLevelName(level_number)244245# The "root" logger should match the "console" level *unless* we also need246# to log to a user log file.247include_user_log = user_log_file is not None248if include_user_log:249additional_log_file = user_log_file250root_level = "DEBUG"251else:252additional_log_file = "/dev/null"253root_level = level254255# Disable any logging besides WARNING unless we have DEBUG level logging256# enabled for vendored libraries.257vendored_log_level = "WARNING" if level in ["INFO", "ERROR"] else "DEBUG"258259# Shorthands for clarity260log_streams = {261"stdout": "ext://sys.stdout",262"stderr": "ext://sys.stderr",263}264handler_classes = {265"stream": "pip._internal.utils.logging.RichPipStreamHandler",266"file": "pip._internal.utils.logging.BetterRotatingFileHandler",267}268handlers = ["console", "console_errors", "console_subprocess"] + (269["user_log"] if include_user_log else []270)271272logging.config.dictConfig(273{274"version": 1,275"disable_existing_loggers": False,276"filters": {277"exclude_warnings": {278"()": "pip._internal.utils.logging.MaxLevelFilter",279"level": logging.WARNING,280},281"restrict_to_subprocess": {282"()": "logging.Filter",283"name": subprocess_logger.name,284},285"exclude_subprocess": {286"()": "pip._internal.utils.logging.ExcludeLoggerFilter",287"name": subprocess_logger.name,288},289},290"formatters": {291"indent": {292"()": IndentingFormatter,293"format": "%(message)s",294},295"indent_with_timestamp": {296"()": IndentingFormatter,297"format": "%(message)s",298"add_timestamp": True,299},300},301"handlers": {302"console": {303"level": level,304"class": handler_classes["stream"],305"no_color": no_color,306"stream": log_streams["stdout"],307"filters": ["exclude_subprocess", "exclude_warnings"],308"formatter": "indent",309},310"console_errors": {311"level": "WARNING",312"class": handler_classes["stream"],313"no_color": no_color,314"stream": log_streams["stderr"],315"filters": ["exclude_subprocess"],316"formatter": "indent",317},318# A handler responsible for logging to the console messages319# from the "subprocessor" logger.320"console_subprocess": {321"level": level,322"class": handler_classes["stream"],323"stream": log_streams["stderr"],324"no_color": no_color,325"filters": ["restrict_to_subprocess"],326"formatter": "indent",327},328"user_log": {329"level": "DEBUG",330"class": handler_classes["file"],331"filename": additional_log_file,332"encoding": "utf-8",333"delay": True,334"formatter": "indent_with_timestamp",335},336},337"root": {338"level": root_level,339"handlers": handlers,340},341"loggers": {"pip._vendor": {"level": vendored_log_level}},342}343)344345return level_number346347348