405 lines
13 KiB
Python
405 lines
13 KiB
Python
|
"""
|
|||
|
日志辅助模块 - 使用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
|