mi-task/utils/log_helper.py

405 lines
13 KiB
Python
Raw Normal View History

"""
日志辅助模块 - 使用loguru和rich提供美观的日志输出
此模块提供了基于loguru的彩色日志输出功能支持在关键日志处添加emoji表情
并增加了rich的一些高级功能
"""
import sys
import os
from typing import Optional, Dict, Any, Union
from datetime import datetime
import time
try:
from loguru import logger
from rich.console import Console
from rich.theme import Theme
from rich.table import Table
from rich.progress import Progress, TextColumn, BarColumn, TimeElapsedColumn, SpinnerColumn
from rich.panel import Panel
from rich.syntax import Syntax
from rich.json import JSON
from rich.traceback import install as install_rich_traceback
# 安装rich错误追踪
install_rich_traceback(show_locals=True)
# 创建rich控制台
console = Console(theme=Theme({
"info": "cyan",
"warning": "yellow",
"error": "bold red",
"success": "bold green",
"debug": "dim white"
}))
RICH_AVAILABLE = True
except ImportError:
RICH_AVAILABLE = False
try:
from loguru import logger
LOGURU_AVAILABLE = True
except ImportError:
LOGURU_AVAILABLE = False
import logging
logger = logging.getLogger("机器人")
# Emoji常量定义
EMOJI = {
"成功": "",
"失败": "",
"警告": "⚠️",
"错误": "🚫",
"信息": "",
"启动": "🚀",
"停止": "🛑",
"旋转": "🔄",
"扫描": "🔍",
"计算": "🧮",
"时间": "⏱️",
"距离": "📏",
"角度": "📐",
"调试": "🐞",
"关键": "🔑",
"位置": "📍",
"校准": "🎯",
"完成": "🏁",
"移动": "🚶",
"检测": "👁️"
}
class LogHelper:
"""日志辅助类,提供美观的日志输出"""
def __init__(self, name: str = "机器人控制系统"):
"""
初始化日志助手
参数:
name: 日志名称
"""
self.name = name
if LOGURU_AVAILABLE:
# 移除默认的处理器
logger.remove()
# 添加彩色控制台处理器
logger.add(
sys.stdout,
colorize=True,
format="<green>{time:HH:mm:ss}</green> | <level>{level: <8}</level> | <cyan>{name}</cyan> - <level>{message}</level>",
level="INFO"
)
# 添加文件处理器 - 按日期分割日志文件
log_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "logs")
os.makedirs(log_dir, exist_ok=True)
logger.add(
os.path.join(log_dir, "robot_{time:YYYY-MM-DD}.log"),
rotation="00:00", # 每天零点创建新文件
retention="30 days", # 保留30天的日志
compression="zip", # 压缩旧日志
format="{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {name} - {message}",
level="DEBUG"
)
self.logger = logger.bind(name=name)
else:
# 使用标准logging作为备选
self.logger = logging.getLogger(name)
# 配置基本的控制台处理器
if not self.logger.handlers:
handler = logging.StreamHandler(sys.stdout)
formatter = logging.Formatter(
'%(asctime)s | %(levelname)-8s | %(name)s - %(message)s',
'%H:%M:%S'
)
handler.setFormatter(formatter)
self.logger.addHandler(handler)
self.logger.setLevel(logging.INFO)
def debug(self, message: str, emoji: Optional[str] = None):
"""
输出调试级别日志
参数:
message: 日志消息
emoji: 可选的emoji名称
"""
msg = f"{EMOJI.get(emoji, EMOJI['调试']) if emoji else ''} {message}"
if LOGURU_AVAILABLE:
self.logger.debug(msg)
else:
self.logger.debug(msg)
def info(self, message: str, emoji: Optional[str] = None):
"""
输出信息级别日志
参数:
message: 日志消息
emoji: 可选的emoji名称
"""
msg = f"{EMOJI.get(emoji, EMOJI['信息']) if emoji else ''} {message}"
if LOGURU_AVAILABLE:
self.logger.info(msg)
else:
self.logger.info(msg)
def warning(self, message: str, emoji: Optional[str] = None):
"""
输出警告级别日志
参数:
message: 日志消息
emoji: 可选的emoji名称
"""
msg = f"{EMOJI.get(emoji, EMOJI['警告']) if emoji else ''} {message}"
if LOGURU_AVAILABLE:
self.logger.warning(msg)
else:
self.logger.warning(msg)
def error(self, message: str, emoji: Optional[str] = None):
"""
输出错误级别日志
参数:
message: 日志消息
emoji: 可选的emoji名称
"""
msg = f"{EMOJI.get(emoji, EMOJI['错误']) if emoji else ''} {message}"
if LOGURU_AVAILABLE:
self.logger.error(msg)
else:
self.logger.error(msg)
def success(self, message: str, emoji: Optional[str] = None):
"""
输出成功级别日志
参数:
message: 日志消息
emoji: 可选的emoji名称
"""
msg = f"{EMOJI.get(emoji, EMOJI['成功']) if emoji else ''} {message}"
if LOGURU_AVAILABLE:
self.logger.success(msg)
else:
# 标准logging没有success级别用info代替
self.logger.info(f"[成功] {msg}")
def critical(self, message: str, emoji: Optional[str] = None):
"""
输出严重错误级别日志
参数:
message: 日志消息
emoji: 可选的emoji名称
"""
msg = f"{EMOJI.get(emoji, EMOJI['错误']) if emoji else ''} {message}"
if LOGURU_AVAILABLE:
self.logger.critical(msg)
else:
self.logger.critical(msg)
def timing(self, operation: str, elapsed_time: float):
"""
记录操作耗时
参数:
operation: 操作名称
elapsed_time: 耗时()
"""
if elapsed_time < 0.1:
time_str = f"{elapsed_time*1000:.1f}毫秒"
else:
time_str = f"{elapsed_time:.3f}"
self.info(f"{operation}耗时: {time_str}", "时间")
def section(self, title: str, emoji: Optional[str] = None):
"""
输出一个分节标题
参数:
title: 标题内容
emoji: 可选的emoji名称
"""
emoji_char = EMOJI.get(emoji, "") if emoji else ""
separator = "=" * (30 - len(title) * 2)
message = f"{separator} {emoji_char} {title} {emoji_char} {separator}"
if LOGURU_AVAILABLE:
self.logger.opt(colors=True).info(f"<yellow>{message}</yellow>")
else:
self.logger.info(message)
def table(self, title: str, columns: list, rows: list, caption: Optional[str] = None):
"""
打印表格数据
参数:
title: 表格标题
columns: 列名列表
rows: 行数据列表每行是一个包含多个值的列表
caption: 可选的表格说明
"""
if RICH_AVAILABLE:
table = Table(title=title, caption=caption, show_header=True, header_style="bold magenta")
# 添加列
for col in columns:
table.add_column(col)
# 添加行
for row in rows:
table.add_row(*[str(item) for item in row])
# 打印表格
console.print(table)
# 同时记录日志
self.info(f"{title} - 表格数据,共{len(rows)}", "信息")
else:
# 不支持rich时使用简单格式输出
self.info(f"{title} - 表格数据:", "信息")
self.info(f"列: {', '.join(columns)}", "信息")
for i, row in enumerate(rows):
self.info(f"{i+1}: {' | '.join([str(item) for item in row])}", "信息")
def progress(self, items: list, desc: str = "处理中", *, unit: str = ""):
"""
显示进度条并处理列表中的每个项
参数:
items: 要处理的项列表
desc: 进度条描述
unit: 单位名称
用法:
for item in logger.progress(items, "处理文件"):
# 对每个item进行处理
process(item)
"""
if RICH_AVAILABLE:
with Progress(
SpinnerColumn(),
TextColumn("[bold blue]{task.description}"),
BarColumn(),
TextColumn("[progress.percentage]{task.percentage:>3.0f}%"),
TextColumn(f"[bold green]{{task.completed}}/{{task.total}} {unit}"),
TimeElapsedColumn(),
console=console
) as progress:
task = progress.add_task(desc, total=len(items))
for item in items:
yield item
progress.advance(task)
else:
# 不支持rich时直接迭代只打印开始和结束
self.info(f"{desc} - 开始处理 {len(items)} {unit}", "信息")
start_time = time.time()
for i, item in enumerate(items):
yield item
if (i+1) % max(1, len(items)//10) == 0: # 每处理10%打印一次
self.info(f"{desc} - 已处理 {i+1}/{len(items)} {unit}", "信息")
elapsed = time.time() - start_time
self.timing(f"{desc} - 完成处理 {len(items)} {unit}", elapsed)
def syntax(self, code: str, language: str = "python", title: Optional[str] = None):
"""
打印语法高亮的代码
参数:
code: 代码内容
language: 代码语言
title: 可选的标题
"""
if RICH_AVAILABLE:
syntax = Syntax(code, language, theme="monokai", line_numbers=True, word_wrap=True)
if title:
console.print(Panel(syntax, title=title))
else:
console.print(syntax)
else:
# 不支持rich时直接打印代码添加标记
if title:
self.info(f"代码块: {title}", "调试")
lines = code.split("\n")
for i, line in enumerate(lines):
self.debug(f"{i+1:4d} | {line}")
def json(self, data: Any, title: Optional[str] = None):
"""
美化打印JSON数据
参数:
data: 要打印的JSON数据对象
title: 可选的标题
"""
if RICH_AVAILABLE:
if title:
console.print(f"[bold]{title}[/bold]")
console.print(JSON.from_data(data))
else:
# 不支持rich时使用json模块直接打印
import json
if title:
self.info(f"JSON数据: {title}", "信息")
self.info(json.dumps(data, indent=2, ensure_ascii=False), "信息")
def panel(self, content: str, title: Optional[str] = None, style: str = "bold green"):
"""
打印带边框的面板
参数:
content: 面板内容
title: 可选的标题
style: 样式字符串
"""
if RICH_AVAILABLE:
console.print(Panel(content, title=title, style=style))
else:
# 不支持rich时直接输出
if title:
self.info(f"{title}", "信息")
self.info(content, "信息")
# 创建默认实例
log = LogHelper()
def get_logger(name: str = None) -> LogHelper:
"""
获取指定名称的日志器
参数:
name: 日志器名称
返回:
LogHelper实例
"""
if name:
return LogHelper(name)
return log
# 导出主要函数,方便直接使用
debug = log.debug
info = log.info
warning = log.warning
error = log.error
success = log.success
critical = log.critical
timing = log.timing
section = log.section
table = log.table
progress = log.progress
syntax = log.syntax
json_log = log.json
panel = log.panel