| import logging |
| import os |
| from pathlib import Path |
| from datetime import datetime |
| from typing import Dict, Optional |
| from termcolor import colored |
|
|
| class ColoredFormatter(logging.Formatter): |
| """自定义彩色日志格式化器""" |
| |
| |
| DEFAULT_STYLE_CONFIG = { |
| 'env': { |
| 'tag': '🤖 ENV', |
| 'DEBUG': {'color': 'grey', 'attrs': ['dark']}, |
| 'INFO': {'color': 'green', 'attrs': []}, |
| 'WARNING': {'color': 'yellow', 'attrs': ['bold']}, |
| 'ERROR': {'color': 'red', 'attrs': ['bold']}, |
| 'CRITICAL': {'color': 'white', 'attrs': ['bold'], 'on_color': 'on_red'} |
| }, |
| 'model': { |
| 'tag': '🧠 MODEL', |
| 'DEBUG': {'color': 'grey', 'attrs': ['dark']}, |
| 'INFO': {'color': 'blue', 'attrs': ['bold']}, |
| 'WARNING': {'color': 'magenta', 'attrs': ['bold']}, |
| 'ERROR': {'color': 'red', 'attrs': ['bold']}, |
| 'CRITICAL': {'color': 'white', 'attrs': ['bold'], 'on_color': 'on_red'} |
| }, |
| 'robot': { |
| 'tag': '🦾 ROBOT', |
| 'DEBUG': {'color': 'cyan', 'attrs': ['dark']}, |
| 'INFO': {'color': 'green', 'attrs': []}, |
| 'WARNING': {'color': 'yellow', 'attrs': ['bold']}, |
| 'ERROR': {'color': 'red', 'attrs': ['bold']}, |
| 'CRITICAL': {'color': 'red', 'attrs': ['bold', 'underline', 'blink']} |
| } |
| } |
| |
| def __init__(self, fmt: str, style_config: Dict = None): |
| super().__init__(fmt) |
| self.style_config = style_config or self.DEFAULT_STYLE_CONFIG |
| self.is_console = False |
|
|
| def format(self, record): |
| |
| original_msg = record.msg |
| |
| |
| source_config = self.style_config.get(record.name, {}) |
| source_tag = source_config.get('tag', f'📝 {record.name.upper()}') |
| style = source_config.get(record.levelname, {'color': 'white', 'attrs': []}) |
| |
| |
| location_info = "" |
| if hasattr(record, 'pathname') and hasattr(record, 'lineno'): |
| fnameline = f"{record.pathname}:{record.lineno}" |
| |
| |
| location_info = f" {fnameline}" |
| |
| |
| if hasattr(self, 'is_console') and self.is_console: |
| |
| colored_message = colored( |
| f"{record.levelname}: {original_msg}", |
| color=style['color'], |
| on_color=style.get('on_color'), |
| attrs=style['attrs'] |
| ) |
| record.msg = f"{source_tag} | {colored_message} |{location_info} " |
| else: |
| |
| record.msg = f"{source_tag} | {record.levelname}: {original_msg} | {location_info} " |
| |
| formatted_message = super().format(record) |
| |
| |
| record.msg = original_msg |
| |
| return formatted_message |
|
|
| class LoggerManager: |
| def __init__(self, |
| log_dir: Optional[str] = None, |
| log_level: str = "INFO", |
| custom_loggers: Optional[Dict] = None, |
| save_to_file: bool = False): |
| """ |
| 初始化日志管理器 |
| |
| Args: |
| log_dir: 日志存储目录 |
| log_level: 日志级别 |
| custom_loggers: 自定义logger配置 |
| 例如: { |
| 'other': { |
| 'tag': '👁️ OHTER', |
| 'DEBUG': {'color': 'grey'}, |
| 'INFO': {'color': 'blue'}, |
| ... |
| } |
| } |
| save_to_file: 是否将日志保存到文件,默认为False |
| """ |
| self.log_level = getattr(logging, log_level.upper()) |
| self.log_dir = self._setup_log_dir(log_dir) if save_to_file else None |
| self.loggers = {} |
| |
| |
| self.style_config = ColoredFormatter.DEFAULT_STYLE_CONFIG.copy() |
| if custom_loggers: |
| self.style_config.update(custom_loggers) |
|
|
| |
| self.file_handler = None |
| if save_to_file: |
| timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") |
| self.file_handler = logging.FileHandler( |
| self.log_dir / f"kuavomimic_{timestamp}.log", |
| encoding='utf-8' |
| ) |
| |
| file_formatter = ColoredFormatter( |
| '%(asctime)s - %(message)s', |
| style_config=self.style_config |
| ) |
| file_formatter.is_console = False |
| self.file_handler.setFormatter(file_formatter) |
|
|
| def _setup_log_dir(self, log_dir: Optional[str]) -> Path: |
| if log_dir is None: |
| log_dir = Path.cwd() / 'logs' |
| else: |
| log_dir = Path(log_dir) |
| log_dir.mkdir(parents=True, exist_ok=True) |
| return log_dir |
|
|
| def get_logger(self, name: str) -> logging.Logger: |
| """获取或创建logger""" |
| if name not in self.loggers: |
| logger = logging.getLogger(name) |
| logger.setLevel(self.log_level) |
| logger.handlers.clear() |
| |
| |
| console_handler = logging.StreamHandler() |
| console_formatter = ColoredFormatter( |
| '%(asctime)s - %(message)s', |
| style_config=self.style_config |
| ) |
| console_formatter.is_console = True |
| console_handler.setFormatter(console_formatter) |
| |
| logger.addHandler(console_handler) |
| if self.file_handler: |
| logger.addHandler(self.file_handler) |
| |
| self.loggers[name] = logger |
| |
| return self.loggers[name] |
|
|
| |
| _log_manager = None |
|
|
| def get_log_manager(log_dir: Optional[str] = None, |
| log_level: str = "INFO", |
| custom_loggers: Optional[Dict] = None, |
| save_to_file: bool = False) -> LoggerManager: |
| """获取全局日志管理器实例""" |
| global _log_manager |
| if _log_manager is None: |
| _log_manager = LoggerManager(log_dir, log_level, custom_loggers, save_to_file) |
| return _log_manager |
|
|
| def setup_logger(name: str, level: int = logging.INFO, log_file: Optional[str] = None, save_to_file: bool = False) -> logging.Logger: |
| """ |
| 设置并返回一个命名的日志记录器 |
| |
| Args: |
| name: 日志记录器名称 |
| level: 日志级别 |
| log_file: 可选的日志文件路径 |
| save_to_file: 是否将日志保存到文件,默认为False |
| |
| Returns: |
| 配置好的日志记录器 |
| """ |
| |
| log_manager = get_log_manager(log_dir=None, log_level="INFO", custom_loggers=None, save_to_file=save_to_file) |
| |
| |
| logger = log_manager.get_logger(name) |
| logger.setLevel(level) |
| |
| |
| if log_file: |
| file_handler = logging.FileHandler(log_file, encoding='utf-8') |
| file_formatter = logging.Formatter( |
| "%(asctime)s - [%(name)s] - %(levelname)s - %(message)s", |
| datefmt="%Y-%m-%d %H:%M:%S" |
| ) |
| file_handler.setFormatter(file_formatter) |
| logger.addHandler(file_handler) |
| |
| return logger |
|
|
| def highlight_message(logger, message, color="magenta", attrs=None): |
| """使用自定义颜色和属性高亮显示消息""" |
| if attrs is None: |
| attrs = ["bold"] |
| print(colored(f">>> {message} <<<", color=color, attrs=attrs)) |
| return logger.info(message) |
|
|
| def test_logging(): |
| """测试日志功能""" |
| |
| log_dir = Path.cwd() / 'logs' |
| log_dir.mkdir(parents=True, exist_ok=True) |
| print(f"日志文件将保存在: {log_dir}") |
| |
| |
| custom_loggers = { |
| 'other': { |
| 'tag': '👋 OTHER', |
| 'DEBUG': {'color': 'grey', 'attrs': ['dark']}, |
| 'INFO': {'color': 'cyan', 'attrs': ['bold']}, |
| 'WARNING': {'color': 'yellow', 'attrs': ['bold']}, |
| 'ERROR': {'color': 'red', 'attrs': ['bold']}, |
| 'CRITICAL': {'color': 'white', 'attrs': ['bold'], 'on_color': 'on_red'} |
| } |
| } |
| |
| |
| log_manager = LoggerManager(log_dir=str(log_dir), log_level="DEBUG", custom_loggers=custom_loggers) |
| |
| |
| env_logger = log_manager.get_logger("env") |
| model_logger = log_manager.get_logger("model") |
| robot_logger = log_manager.get_logger("robot") |
| other_logger = log_manager.get_logger("other") |
| |
| |
| env_logger.info("环境初始化完成") |
| model_logger.warning("模型性能下降") |
| robot_logger.info("机器人状态正常") |
| other_logger.info("处理相机数据") |
| env_logger.error("检测到碰撞风险") |
| |
| |
| test_logger = setup_logger("test", logging.DEBUG, save_to_file=False) |
| test_logger.debug("这是一条测试日志(仅控制台输出)") |
| test_logger.info("测试信息(仅控制台输出)") |
| |
| |
| test_logger_with_file = setup_logger("test_file", logging.DEBUG, save_to_file=True) |
| test_logger_with_file.debug("这是一条测试日志(同时输出到文件)") |
| test_logger_with_file.info("测试信息(同时输出到文件)") |
| |
| |
| highlight_message(test_logger, "这是一条高亮消息") |
| |
| |
| |
| |
| |
| |
| |
|
|
| if __name__ == "__main__": |
| test_logging() |
|
|