# Copyright 2016 Alethea Katherine Flowers # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import logging from typing import Any, cast from colorlog import ColoredFormatter SUCCESS = 25 OUTPUT = logging.DEBUG - 1 def _get_format(colorlog: bool, add_timestamp: bool) -> str: if colorlog: if add_timestamp: return "%(cyan)s%(name)s > [%(asctime)s] %(log_color)s%(message)s" else: return "%(cyan)s%(name)s > %(log_color)s%(message)s" else: if add_timestamp: return "%(name)s > [%(asctime)s] %(message)s" else: return "%(name)s > %(message)s" class NoxFormatter(logging.Formatter): def __init__(self, add_timestamp: bool = False) -> None: super().__init__(fmt=_get_format(colorlog=False, add_timestamp=add_timestamp)) self._simple_fmt = logging.Formatter("%(message)s") def format(self, record: Any) -> str: if record.levelname == "OUTPUT": return self._simple_fmt.format(record) return super().format(record) class NoxColoredFormatter(ColoredFormatter): def __init__( self, datefmt: Any = None, style: Any = None, log_colors: Any = None, reset: bool = True, secondary_log_colors: Any = None, add_timestamp: bool = False, ) -> None: super().__init__( fmt=_get_format(colorlog=True, add_timestamp=add_timestamp), datefmt=datefmt, style=style, log_colors=log_colors, reset=reset, secondary_log_colors=secondary_log_colors, ) def format(self, record: Any) -> str: if record.levelname == "OUTPUT": return self._simple_fmt.format(record) return super().format(record) class LoggerWithSuccessAndOutput(logging.getLoggerClass()): # type: ignore def __init__(self, name: str, level: int = logging.NOTSET): super(LoggerWithSuccessAndOutput, self).__init__(name, level) logging.addLevelName(SUCCESS, "SUCCESS") logging.addLevelName(OUTPUT, "OUTPUT") def success(self, msg: str, *args: Any, **kwargs: Any) -> None: if self.isEnabledFor(SUCCESS): self._log(SUCCESS, msg, args, **kwargs) else: # pragma: no cover pass def output(self, msg: str, *args: Any, **kwargs: Any) -> None: if self.isEnabledFor(OUTPUT): self._log(OUTPUT, msg, args, **kwargs) else: # pragma: no cover pass logging.setLoggerClass(LoggerWithSuccessAndOutput) logger = cast(LoggerWithSuccessAndOutput, logging.getLogger("nox")) def _get_formatter(color: bool, add_timestamp: bool) -> logging.Formatter: if color is True: return NoxColoredFormatter( reset=True, log_colors={ "DEBUG": "cyan", "INFO": "blue", "WARNING": "yellow", "ERROR": "red", "CRITICAL": "red,bg_white", "SUCCESS": "green", }, style="%", secondary_log_colors=None, add_timestamp=add_timestamp, ) else: return NoxFormatter(add_timestamp=add_timestamp) def setup_logging( color: bool, verbose: bool = False, add_timestamp: bool = False ) -> None: # pragma: no cover """Setup logging. Args: color (bool): If true, the output will be colored using colorlog. Otherwise, it will be plaintext. """ root_logger = logging.getLogger() if verbose: root_logger.setLevel(OUTPUT) else: root_logger.setLevel(logging.DEBUG) handler = logging.StreamHandler() handler.setFormatter(_get_formatter(color, add_timestamp)) root_logger.addHandler(handler) # Silence noisy loggers logging.getLogger("sh").setLevel(logging.WARNING)