1# coding=utf-8 2# 3# Copyright (c) 2025 Huawei Device Co., Ltd. 4# Licensed under the Apache License, Version 2.0 (the "License"); 5# you may not use this file except in compliance with the License. 6# You may obtain 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, 12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13# See the License for the specific language governing permissions and 14# limitations under the License. 15 16import logging 17import os 18from sys import stderr 19from typing import ClassVar, TextIO 20 21from typing_extensions import override 22 23 24class AnsiStyle: 25 RED = "\033[31m" 26 GREEN = "\033[32m" 27 BLUE = "\033[34m" 28 YELLOW = "\033[33m" 29 MAGENTA = "\033[35m" 30 CYAN = "\033[36m" 31 GRAY = "\033[90m" 32 WHITE = "\033[97m" 33 34 # Background colors 35 BG_RED = "\033[41m" 36 BG_GREEN = "\033[42m" 37 BG_BLUE = "\033[44m" 38 BG_GRAY = "\033[100m" 39 40 # Styles 41 RESET = "\033[39m" 42 BRIGHT = "\033[1m" 43 REVERSE = "\033[7m" 44 RESET_ALL = "\033[0m" 45 46 47def should_use_color(f: TextIO) -> bool: 48 """Determine if we should use colored output.""" 49 # Check for explicit color disable 50 if os.getenv("TAIHE_NO_COLOR") or os.getenv("TERM") == "dumb": 51 return False 52 53 # Check for force color 54 if os.getenv("TAIHE_FORCE_COLOR"): 55 return True 56 57 # Check if we're writing to a terminal 58 return f.isatty() 59 60 61def setup_logger(verbosity: int = 0): 62 """Sets up a console-based log handler for the system logger. 63 64 Verbosity levels: 65 - 40: ERROR 66 - 30: WARNING 67 - 20: INFO 68 - 10: DEBUG 69 """ 70 handler = logging.StreamHandler() 71 handler.setFormatter(ColoredFormatter("%(message)s")) 72 73 logger = logging.getLogger() 74 logger.addHandler(handler) 75 76 logger.setLevel(verbosity) 77 78 79class ColoredFormatter(logging.Formatter): 80 """A pretty, colored, console-based log handler.""" 81 82 COLORS: ClassVar = { 83 logging.ERROR: f"{AnsiStyle.REVERSE}{AnsiStyle.RED}", 84 logging.WARNING: f"{AnsiStyle.REVERSE}{AnsiStyle.YELLOW}", 85 logging.INFO: f"{AnsiStyle.REVERSE}{AnsiStyle.GREEN}", 86 logging.DEBUG: f"{AnsiStyle.REVERSE}{AnsiStyle.BLUE}", 87 } 88 89 PREFIXES: ClassVar = { 90 logging.ERROR: "[!]", 91 logging.WARNING: "[*]", 92 logging.INFO: "[-]", 93 logging.DEBUG: "[.]", 94 } 95 96 def __init__(self, fmt: str | None = None, use_color: bool | None = None): 97 super().__init__(fmt) 98 if use_color is None: 99 use_color = should_use_color(stderr) 100 self.use_color = use_color 101 102 @override 103 def format(self, record: logging.LogRecord): 104 prefix = self.PREFIXES.get(record.levelno, "") 105 message = super().format(record) 106 107 if not self.use_color: 108 return f"{prefix} {message}" 109 110 color = self.COLORS.get(record.levelno, "") 111 return f"{color}{prefix}{AnsiStyle.RESET_ALL} {message}"