From 9b4fa2155e5ac28d1e901f360949227415b72813 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 12 Jun 2025 12:30:00 +0000 Subject: [PATCH] =?UTF-8?q?=F0=9F=A4=96=20=E8=87=AA=E5=8A=A8=E6=A0=BC?= =?UTF-8?q?=E5=BC=8F=E5=8C=96=E4=BB=A3=E7=A0=81=20[skip=20ci]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bot.py | 2 +- reload_config_now.py | 2 +- scripts/log_viewer.py | 674 ++++++++++++++-------------- src/chat/focus_chat/heartFC_chat.py | 2 - src/common/logger.py | 222 ++++----- 5 files changed, 458 insertions(+), 444 deletions(-) diff --git a/bot.py b/bot.py index 979bd0d47..45acc3a03 100644 --- a/bot.py +++ b/bot.py @@ -10,6 +10,7 @@ from dotenv import load_dotenv # 最早期初始化日志系统,确保所有后续模块都使用正确的日志格式 from src.common.logger import initialize_logging + initialize_logging() from src.common.logger import get_logger @@ -46,7 +47,6 @@ app = None loop = None - async def request_shutdown() -> bool: """请求关闭程序""" try: diff --git a/reload_config_now.py b/reload_config_now.py index 3399ee4f4..2510bb7ff 100644 --- a/reload_config_now.py +++ b/reload_config_now.py @@ -14,4 +14,4 @@ from common.logger import reload_log_config print("🔄 重新加载日志配置...") reload_log_config() -print("✅ 配置已重新加载!faiss日志已被屏蔽。") \ No newline at end of file +print("✅ 配置已重新加载!faiss日志已被屏蔽。") diff --git a/scripts/log_viewer.py b/scripts/log_viewer.py index 52933cfd4..b37caafe5 100644 --- a/scripts/log_viewer.py +++ b/scripts/log_viewer.py @@ -8,48 +8,49 @@ import time import toml from datetime import datetime + class LogFormatter: """日志格式化器,同步logger.py的格式""" - + def __init__(self, config, custom_module_colors=None, custom_level_colors=None): self.config = config - + # 日志级别颜色 self.level_colors = { - "debug": "#FFA500", # 橙色 - "info": "#0000FF", # 蓝色 + "debug": "#FFA500", # 橙色 + "info": "#0000FF", # 蓝色 "success": "#008000", # 绿色 "warning": "#FFFF00", # 黄色 - "error": "#FF0000", # 红色 - "critical": "#800080", # 紫色 + "error": "#FF0000", # 红色 + "critical": "#800080", # 紫色 } - + # 模块颜色映射 - 同步logger.py中的MODULE_COLORS self.module_colors = { - "api": "#00FF00", # 亮绿色 - "emoji": "#00FF00", # 亮绿色 - "chat": "#0080FF", # 亮蓝色 - "config": "#FFFF00", # 亮黄色 - "common": "#FF00FF", # 亮紫色 - "tools": "#00FFFF", # 亮青色 - "lpmm": "#00FFFF", # 亮青色 - "plugin_system": "#FF0080", # 亮红色 - "experimental": "#FFFFFF", # 亮白色 + "api": "#00FF00", # 亮绿色 + "emoji": "#00FF00", # 亮绿色 + "chat": "#0080FF", # 亮蓝色 + "config": "#FFFF00", # 亮黄色 + "common": "#FF00FF", # 亮紫色 + "tools": "#00FFFF", # 亮青色 + "lpmm": "#00FFFF", # 亮青色 + "plugin_system": "#FF0080", # 亮红色 + "experimental": "#FFFFFF", # 亮白色 "person_info": "#008000", # 绿色 - "individuality": "#000080", # 蓝色 - "manager": "#800080", # 紫色 - "llm_models": "#008080", # 青色 - "plugins": "#800000", # 红色 - "plugin_api": "#808000", # 黄色 - "remote": "#8000FF", # 紫蓝色 + "individuality": "#000080", # 蓝色 + "manager": "#800080", # 紫色 + "llm_models": "#008080", # 青色 + "plugins": "#800000", # 红色 + "plugin_api": "#808000", # 黄色 + "remote": "#8000FF", # 紫蓝色 } - + # 应用自定义颜色 if custom_module_colors: self.module_colors.update(custom_module_colors) if custom_level_colors: self.level_colors.update(custom_level_colors) - + # 根据配置决定颜色启用状态 color_text = self.config.get("color_text", "full") if color_text == "none": @@ -76,17 +77,17 @@ class LogFormatter: level = log_entry.get("level", "info") logger_name = log_entry.get("logger_name", "") event = log_entry.get("event", "") - + # 格式化时间戳 formatted_timestamp = self.format_timestamp(timestamp) - + # 构建输出部分 parts = [] tags = [] - + # 日志级别样式配置 log_level_style = self.config.get("log_level_style", "lite") - + # 时间戳 if formatted_timestamp: if log_level_style == "lite" and self.enable_level_colors: @@ -96,7 +97,7 @@ class LogFormatter: else: parts.append(formatted_timestamp) tags.append("timestamp") - + # 日志级别显示 if log_level_style == "full": # 显示完整级别名 @@ -115,7 +116,7 @@ class LogFormatter: else: tags.append("level") # lite模式不显示级别 - + # 模块名称 if logger_name: module_text = f"[{logger_name}]" @@ -124,7 +125,7 @@ class LogFormatter: tags.append(f"module_{logger_name}") else: tags.append("module") - + # 消息内容 if isinstance(event, str): parts.append(event) @@ -136,7 +137,7 @@ class LogFormatter: else: parts.append(str(event)) tags.append("message") - + # 处理其他字段 extras = [] for key, value in log_entry.items(): @@ -149,97 +150,98 @@ class LogFormatter: else: value_str = str(value) extras.append(f"{key}={value_str}") - + if extras: parts.append(" ".join(extras)) tags.append("extras") - + return parts, tags def format_timestamp(self, timestamp): """格式化时间戳""" if not timestamp: return "" - + try: # 尝试解析ISO格式时间戳 - if 'T' in timestamp: - dt = datetime.fromisoformat(timestamp.replace('Z', '+00:00')) + if "T" in timestamp: + dt = datetime.fromisoformat(timestamp.replace("Z", "+00:00")) else: # 假设已经是格式化的字符串 return timestamp - + # 根据配置格式化 date_style = self.config.get("date_style", "m-d H:i:s") format_map = { - 'Y': '%Y', # 4位年份 - 'm': '%m', # 月份(01-12) - 'd': '%d', # 日期(01-31) - 'H': '%H', # 小时(00-23) - 'i': '%M', # 分钟(00-59) - 's': '%S', # 秒数(00-59) + "Y": "%Y", # 4位年份 + "m": "%m", # 月份(01-12) + "d": "%d", # 日期(01-31) + "H": "%H", # 小时(00-23) + "i": "%M", # 分钟(00-59) + "s": "%S", # 秒数(00-59) } - + python_format = date_style for php_char, python_char in format_map.items(): python_format = python_format.replace(php_char, python_char) - + return dt.strftime(python_format) except Exception: return timestamp + class LogViewer: def __init__(self, root): self.root = root self.root.title("MaiBot日志查看器") self.root.geometry("1200x800") - + # 加载配置 self.load_config() - + # 初始化日志格式化器 self.formatter = LogFormatter(self.log_config, self.custom_module_colors, self.custom_level_colors) - + # 创建主框架 self.main_frame = ttk.Frame(root) self.main_frame.pack(fill=tk.BOTH, expand=True, padx=5, pady=5) - + # 创建菜单栏 self.create_menu() - + # 创建控制面板 self.control_frame = ttk.Frame(self.main_frame) self.control_frame.pack(fill=tk.X, pady=(0, 5)) - + # 模块选择框架 self.module_frame = ttk.LabelFrame(self.control_frame, text="模块") self.module_frame.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=5) - + # 创建模块选择滚动区域 self.module_canvas = tk.Canvas(self.module_frame, height=80) self.module_canvas.pack(side=tk.LEFT, fill=tk.X, expand=True) - + # 创建模块选择内部框架 self.module_inner_frame = ttk.Frame(self.module_canvas) - self.module_canvas.create_window((0, 0), window=self.module_inner_frame, anchor='nw') - + self.module_canvas.create_window((0, 0), window=self.module_inner_frame, anchor="nw") + # 创建右侧控制区域(级别和搜索) self.right_control_frame = ttk.Frame(self.control_frame) self.right_control_frame.pack(side=tk.RIGHT, padx=5) - + # 映射编辑按钮 mapping_btn = ttk.Button(self.right_control_frame, text="模块映射", command=self.edit_module_mapping) mapping_btn.pack(side=tk.TOP, fill=tk.X, pady=1) - + # 日志级别选择 level_frame = ttk.Frame(self.right_control_frame) level_frame.pack(side=tk.TOP, fill=tk.X, pady=1) ttk.Label(level_frame, text="级别:").pack(side=tk.LEFT, padx=2) self.level_var = tk.StringVar(value="全部") self.level_combo = ttk.Combobox(level_frame, textvariable=self.level_var, width=8) - self.level_combo['values'] = ['全部', 'debug', 'info', 'warning', 'error', 'critical'] + self.level_combo["values"] = ["全部", "debug", "info", "warning", "error", "critical"] self.level_combo.pack(side=tk.LEFT, padx=2) - + # 搜索框 search_frame = ttk.Frame(self.right_control_frame) search_frame.pack(side=tk.TOP, fill=tk.X, pady=1) @@ -247,72 +249,78 @@ class LogViewer: self.search_var = tk.StringVar() self.search_entry = ttk.Entry(search_frame, textvariable=self.search_var, width=15) self.search_entry.pack(side=tk.LEFT, padx=2) - + # 创建日志显示区域 self.log_frame = ttk.Frame(self.main_frame) self.log_frame.pack(fill=tk.BOTH, expand=True) - + # 创建文本框和滚动条 self.scrollbar = ttk.Scrollbar(self.log_frame) self.scrollbar.pack(side=tk.RIGHT, fill=tk.Y) - - self.log_text = tk.Text(self.log_frame, wrap=tk.WORD, yscrollcommand=self.scrollbar.set, - background='#1e1e1e', foreground='#ffffff', - insertbackground='#ffffff', selectbackground='#404040') + + self.log_text = tk.Text( + self.log_frame, + wrap=tk.WORD, + yscrollcommand=self.scrollbar.set, + background="#1e1e1e", + foreground="#ffffff", + insertbackground="#ffffff", + selectbackground="#404040", + ) self.log_text.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) self.scrollbar.config(command=self.log_text.yview) - + # 配置文本标签样式 self.configure_text_tags() - + # 模块名映射 self.module_name_mapping = { - 'api': 'API接口', - 'async_task_manager': '异步任务管理器', - 'background_tasks': '后台任务', - 'base_tool': '基础工具', - 'chat_stream': '聊天流', - 'component_registry': '组件注册器', - 'config': '配置', - 'database_model': '数据库模型', - 'emoji': '表情', - 'heartflow': '心流', - 'local_storage': '本地存储', - 'lpmm': 'LPMM', - 'maibot_statistic': 'MaiBot统计', - 'main_message': '主消息', - 'main': '主程序', - 'memory': '内存', - 'mood': '情绪', - 'plugin_manager': '插件管理器', - 'remote': '远程', - 'willing': '意愿', + "api": "API接口", + "async_task_manager": "异步任务管理器", + "background_tasks": "后台任务", + "base_tool": "基础工具", + "chat_stream": "聊天流", + "component_registry": "组件注册器", + "config": "配置", + "database_model": "数据库模型", + "emoji": "表情", + "heartflow": "心流", + "local_storage": "本地存储", + "lpmm": "LPMM", + "maibot_statistic": "MaiBot统计", + "main_message": "主消息", + "main": "主程序", + "memory": "内存", + "mood": "情绪", + "plugin_manager": "插件管理器", + "remote": "远程", + "willing": "意愿", } - + # 加载自定义映射 self.load_module_mapping() - + # 创建日志队列和缓存 self.log_queue = queue.Queue() self.log_cache = [] - + # 选中的模块集合 self.selected_modules = set() - + # 初始化模块列表 self.modules = set() self.update_module_list() - + # 绑定事件 - self.level_combo.bind('<>', self.filter_logs) - self.search_var.trace('w', self.filter_logs) - + self.level_combo.bind("<>", self.filter_logs) + self.search_var.trace("w", self.filter_logs) + # 启动日志监控线程 self.running = True self.monitor_thread = threading.Thread(target=self.monitor_log_file) self.monitor_thread.daemon = True self.monitor_thread.start() - + # 启动日志更新线程 self.update_thread = threading.Thread(target=self.update_logs) self.update_thread.daemon = True @@ -322,92 +330,80 @@ class LogViewer: """加载配置文件""" # 默认配置 self.default_config = { - "log": { - "date_style": "m-d H:i:s", - "log_level_style": "lite", - "color_text": "full", - "log_level": "INFO" - }, + "log": {"date_style": "m-d H:i:s", "log_level_style": "lite", "color_text": "full", "log_level": "INFO"}, "viewer": { "theme": "dark", "font_size": 10, "max_lines": 1000, "auto_scroll": True, "show_milliseconds": False, - "window": { - "width": 1200, - "height": 800, - "remember_position": True - } - } + "window": {"width": 1200, "height": 800, "remember_position": True}, + }, } - + # 从bot_config.toml加载日志配置 config_path = Path("config/bot_config.toml") self.log_config = self.default_config["log"].copy() self.viewer_config = self.default_config["viewer"].copy() - + try: if config_path.exists(): - with open(config_path, 'r', encoding='utf-8') as f: + with open(config_path, "r", encoding="utf-8") as f: bot_config = toml.load(f) - if 'log' in bot_config: - self.log_config.update(bot_config['log']) + if "log" in bot_config: + self.log_config.update(bot_config["log"]) except Exception as e: print(f"加载bot配置失败: {e}") - + # 从viewer配置文件加载查看器配置 viewer_config_path = Path("config/log_viewer_config.toml") self.custom_module_colors = {} self.custom_level_colors = {} - + try: if viewer_config_path.exists(): - with open(viewer_config_path, 'r', encoding='utf-8') as f: + with open(viewer_config_path, "r", encoding="utf-8") as f: viewer_config = toml.load(f) - if 'viewer' in viewer_config: - self.viewer_config.update(viewer_config['viewer']) - + if "viewer" in viewer_config: + self.viewer_config.update(viewer_config["viewer"]) + # 加载自定义模块颜色 - if 'module_colors' in viewer_config['viewer']: - self.custom_module_colors = viewer_config['viewer']['module_colors'] - + if "module_colors" in viewer_config["viewer"]: + self.custom_module_colors = viewer_config["viewer"]["module_colors"] + # 加载自定义级别颜色 - if 'level_colors' in viewer_config['viewer']: - self.custom_level_colors = viewer_config['viewer']['level_colors'] - - if 'log' in viewer_config: - self.log_config.update(viewer_config['log']) + if "level_colors" in viewer_config["viewer"]: + self.custom_level_colors = viewer_config["viewer"]["level_colors"] + + if "log" in viewer_config: + self.log_config.update(viewer_config["log"]) except Exception as e: print(f"加载查看器配置失败: {e}") - + # 应用窗口配置 - window_config = self.viewer_config.get('window', {}) - window_width = window_config.get('width', 1200) - window_height = window_config.get('height', 800) + window_config = self.viewer_config.get("window", {}) + window_width = window_config.get("width", 1200) + window_height = window_config.get("height", 800) self.root.geometry(f"{window_width}x{window_height}") def save_viewer_config(self): """保存查看器配置""" # 准备完整的配置数据 viewer_config_copy = self.viewer_config.copy() - + # 保存自定义颜色(只保存与默认值不同的颜色) if self.custom_module_colors: viewer_config_copy["module_colors"] = self.custom_module_colors if self.custom_level_colors: viewer_config_copy["level_colors"] = self.custom_level_colors - - config_data = { - "log": self.log_config, - "viewer": viewer_config_copy - } - + + config_data = {"log": self.log_config, "viewer": viewer_config_copy} + config_path = Path("config/log_viewer_config.toml") config_path.parent.mkdir(exist_ok=True) - + try: - with open(config_path, 'w', encoding='utf-8') as f: + with open(config_path, "w", encoding="utf-8") as f: toml.dump(config_data, f) except Exception as e: print(f"保存查看器配置失败: {e}") @@ -416,7 +412,7 @@ class LogViewer: """创建菜单栏""" menubar = tk.Menu(self.root) self.root.config(menu=menubar) - + # 配置菜单 config_menu = tk.Menu(menubar, tearoff=0) menubar.add_cascade(label="配置", menu=config_menu) @@ -425,7 +421,7 @@ class LogViewer: config_menu.add_command(label="查看器设置", command=self.show_viewer_settings) config_menu.add_separator() config_menu.add_command(label="重新加载配置", command=self.reload_config) - + # 工具菜单 tools_menu = tk.Menu(menubar, tearoff=0) menubar.add_cascade(label="工具", menu=tools_menu) @@ -437,144 +433,147 @@ class LogViewer: format_window = tk.Toplevel(self.root) format_window.title("日志格式设置") format_window.geometry("400x300") - + frame = ttk.Frame(format_window) frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10) - + # 日期格式 - ttk.Label(frame, text="日期格式:").pack(anchor='w', pady=2) + ttk.Label(frame, text="日期格式:").pack(anchor="w", pady=2) date_style_var = tk.StringVar(value=self.log_config.get("date_style", "m-d H:i:s")) date_entry = ttk.Entry(frame, textvariable=date_style_var, width=30) - date_entry.pack(anchor='w', pady=2) - ttk.Label(frame, text="格式说明: Y=年份, m=月份, d=日期, H=小时, i=分钟, s=秒", - font=('', 8)).pack(anchor='w', pady=2) - + date_entry.pack(anchor="w", pady=2) + ttk.Label(frame, text="格式说明: Y=年份, m=月份, d=日期, H=小时, i=分钟, s=秒", font=("", 8)).pack( + anchor="w", pady=2 + ) + # 日志级别样式 - ttk.Label(frame, text="日志级别样式:").pack(anchor='w', pady=(10,2)) + ttk.Label(frame, text="日志级别样式:").pack(anchor="w", pady=(10, 2)) level_style_var = tk.StringVar(value=self.log_config.get("log_level_style", "lite")) level_frame = ttk.Frame(frame) - level_frame.pack(anchor='w', pady=2) - - ttk.Radiobutton(level_frame, text="简洁(lite)", variable=level_style_var, - value="lite").pack(side='left', padx=(0,10)) - ttk.Radiobutton(level_frame, text="紧凑(compact)", variable=level_style_var, - value="compact").pack(side='left', padx=(0,10)) - ttk.Radiobutton(level_frame, text="完整(full)", variable=level_style_var, - value="full").pack(side='left', padx=(0,10)) - + level_frame.pack(anchor="w", pady=2) + + ttk.Radiobutton(level_frame, text="简洁(lite)", variable=level_style_var, value="lite").pack( + side="left", padx=(0, 10) + ) + ttk.Radiobutton(level_frame, text="紧凑(compact)", variable=level_style_var, value="compact").pack( + side="left", padx=(0, 10) + ) + ttk.Radiobutton(level_frame, text="完整(full)", variable=level_style_var, value="full").pack( + side="left", padx=(0, 10) + ) + # 颜色文本设置 - ttk.Label(frame, text="文本颜色设置:").pack(anchor='w', pady=(10,2)) + ttk.Label(frame, text="文本颜色设置:").pack(anchor="w", pady=(10, 2)) color_text_var = tk.StringVar(value=self.log_config.get("color_text", "full")) color_frame = ttk.Frame(frame) - color_frame.pack(anchor='w', pady=2) - - ttk.Radiobutton(color_frame, text="无颜色(none)", variable=color_text_var, - value="none").pack(side='left', padx=(0,10)) - ttk.Radiobutton(color_frame, text="仅标题(title)", variable=color_text_var, - value="title").pack(side='left', padx=(0,10)) - ttk.Radiobutton(color_frame, text="全部(full)", variable=color_text_var, - value="full").pack(side='left', padx=(0,10)) - + color_frame.pack(anchor="w", pady=2) + + ttk.Radiobutton(color_frame, text="无颜色(none)", variable=color_text_var, value="none").pack( + side="left", padx=(0, 10) + ) + ttk.Radiobutton(color_frame, text="仅标题(title)", variable=color_text_var, value="title").pack( + side="left", padx=(0, 10) + ) + ttk.Radiobutton(color_frame, text="全部(full)", variable=color_text_var, value="full").pack( + side="left", padx=(0, 10) + ) + # 按钮 button_frame = ttk.Frame(frame) - button_frame.pack(fill='x', pady=(20,0)) - + button_frame.pack(fill="x", pady=(20, 0)) + def apply_format(): self.log_config["date_style"] = date_style_var.get() self.log_config["log_level_style"] = level_style_var.get() self.log_config["color_text"] = color_text_var.get() - + # 重新初始化格式化器 self.formatter = LogFormatter(self.log_config, self.custom_module_colors, self.custom_level_colors) self.configure_text_tags() - + # 保存配置 self.save_viewer_config() - + # 重新过滤日志以应用新格式 self.filter_logs() - + format_window.destroy() - - ttk.Button(button_frame, text="应用", command=apply_format).pack(side='right', padx=(5,0)) - ttk.Button(button_frame, text="取消", command=format_window.destroy).pack(side='right') + + ttk.Button(button_frame, text="应用", command=apply_format).pack(side="right", padx=(5, 0)) + ttk.Button(button_frame, text="取消", command=format_window.destroy).pack(side="right") def show_viewer_settings(self): """显示查看器设置窗口""" viewer_window = tk.Toplevel(self.root) viewer_window.title("查看器设置") viewer_window.geometry("350x250") - + frame = ttk.Frame(viewer_window) frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10) - + # 主题设置 - ttk.Label(frame, text="主题:").pack(anchor='w', pady=2) + ttk.Label(frame, text="主题:").pack(anchor="w", pady=2) theme_var = tk.StringVar(value=self.viewer_config.get("theme", "dark")) theme_frame = ttk.Frame(frame) - theme_frame.pack(anchor='w', pady=2) - ttk.Radiobutton(theme_frame, text="深色", variable=theme_var, value="dark").pack(side='left', padx=(0,10)) - ttk.Radiobutton(theme_frame, text="浅色", variable=theme_var, value="light").pack(side='left') - + theme_frame.pack(anchor="w", pady=2) + ttk.Radiobutton(theme_frame, text="深色", variable=theme_var, value="dark").pack(side="left", padx=(0, 10)) + ttk.Radiobutton(theme_frame, text="浅色", variable=theme_var, value="light").pack(side="left") + # 字体大小 - ttk.Label(frame, text="字体大小:").pack(anchor='w', pady=(10,2)) + ttk.Label(frame, text="字体大小:").pack(anchor="w", pady=(10, 2)) font_size_var = tk.IntVar(value=self.viewer_config.get("font_size", 10)) font_size_spin = ttk.Spinbox(frame, from_=8, to=20, textvariable=font_size_var, width=10) - font_size_spin.pack(anchor='w', pady=2) - + font_size_spin.pack(anchor="w", pady=2) + # 最大行数 - ttk.Label(frame, text="最大显示行数:").pack(anchor='w', pady=(10,2)) + ttk.Label(frame, text="最大显示行数:").pack(anchor="w", pady=(10, 2)) max_lines_var = tk.IntVar(value=self.viewer_config.get("max_lines", 1000)) - max_lines_spin = ttk.Spinbox(frame, from_=100, to=10000, increment=100, - textvariable=max_lines_var, width=10) - max_lines_spin.pack(anchor='w', pady=2) - + max_lines_spin = ttk.Spinbox(frame, from_=100, to=10000, increment=100, textvariable=max_lines_var, width=10) + max_lines_spin.pack(anchor="w", pady=2) + # 自动滚动 auto_scroll_var = tk.BooleanVar(value=self.viewer_config.get("auto_scroll", True)) - ttk.Checkbutton(frame, text="自动滚动到底部", variable=auto_scroll_var).pack(anchor='w', pady=(10,2)) - + ttk.Checkbutton(frame, text="自动滚动到底部", variable=auto_scroll_var).pack(anchor="w", pady=(10, 2)) + # 按钮 button_frame = ttk.Frame(frame) - button_frame.pack(fill='x', pady=(20,0)) - + button_frame.pack(fill="x", pady=(20, 0)) + def apply_viewer_settings(): self.viewer_config["theme"] = theme_var.get() self.viewer_config["font_size"] = font_size_var.get() self.viewer_config["max_lines"] = max_lines_var.get() self.viewer_config["auto_scroll"] = auto_scroll_var.get() - + # 应用主题 self.apply_theme() - + # 保存配置 self.save_viewer_config() - + viewer_window.destroy() - - ttk.Button(button_frame, text="应用", command=apply_viewer_settings).pack(side='right', padx=(5,0)) - ttk.Button(button_frame, text="取消", command=viewer_window.destroy).pack(side='right') + + ttk.Button(button_frame, text="应用", command=apply_viewer_settings).pack(side="right", padx=(5, 0)) + ttk.Button(button_frame, text="取消", command=viewer_window.destroy).pack(side="right") def apply_theme(self): """应用主题设置""" theme = self.viewer_config.get("theme", "dark") font_size = self.viewer_config.get("font_size", 10) - + if theme == "dark": - bg_color = '#1e1e1e' - fg_color = '#ffffff' - select_bg = '#404040' + bg_color = "#1e1e1e" + fg_color = "#ffffff" + select_bg = "#404040" else: - bg_color = '#ffffff' - fg_color = '#000000' - select_bg = '#c0c0c0' - + bg_color = "#ffffff" + fg_color = "#000000" + select_bg = "#c0c0c0" + self.log_text.config( - background=bg_color, - foreground=fg_color, - selectbackground=select_bg, - font=('Consolas', font_size) + background=bg_color, foreground=fg_color, selectbackground=select_bg, font=("Consolas", font_size) ) - + # 重新配置标签样式 self.configure_text_tags() @@ -582,20 +581,20 @@ class LogViewer: """配置文本标签样式""" # 清除现有标签 for tag in self.log_text.tag_names(): - if tag != 'sel': + if tag != "sel": self.log_text.tag_delete(tag) - + # 基础标签 self.log_text.tag_configure("timestamp", foreground="#808080") self.log_text.tag_configure("level", foreground="#808080") self.log_text.tag_configure("module", foreground="#808080") self.log_text.tag_configure("message", foreground=self.log_text.cget("foreground")) self.log_text.tag_configure("extras", foreground="#808080") - + # 日志级别颜色标签 for level, color in self.formatter.level_colors.items(): self.log_text.tag_configure(f"level_{level}", foreground=color) - + # 模块颜色标签 for module, color in self.formatter.module_colors.items(): self.log_text.tag_configure(f"module_{module}", foreground=color) @@ -615,12 +614,11 @@ class LogViewer: def export_logs(self): """导出当前显示的日志""" filename = filedialog.asksaveasfilename( - defaultextension=".txt", - filetypes=[("文本文件", "*.txt"), ("所有文件", "*.*")] + defaultextension=".txt", filetypes=[("文本文件", "*.txt"), ("所有文件", "*.*")] ) if filename: try: - with open(filename, 'w', encoding='utf-8') as f: + with open(filename, "w", encoding="utf-8") as f: f.write(self.log_text.get(1.0, tk.END)) messagebox.showinfo("导出成功", f"日志已导出到: {filename}") except Exception as e: @@ -631,7 +629,7 @@ class LogViewer: mapping_file = Path("config/module_mapping.json") if mapping_file.exists(): try: - with open(mapping_file, 'r', encoding='utf-8') as f: + with open(mapping_file, "r", encoding="utf-8") as f: custom_mapping = json.load(f) self.module_name_mapping.update(custom_mapping) except Exception as e: @@ -642,7 +640,7 @@ class LogViewer: mapping_file = Path("config/module_mapping.json") mapping_file.parent.mkdir(exist_ok=True) try: - with open(mapping_file, 'w', encoding='utf-8') as f: + with open(mapping_file, "w", encoding="utf-8") as f: json.dump(self.module_name_mapping, f, ensure_ascii=False, indent=2) except Exception as e: print(f"保存模块映射失败: {e}") @@ -652,55 +650,53 @@ class LogViewer: color_window = tk.Toplevel(self.root) color_window.title("颜色设置") color_window.geometry("300x400") - + # 创建滚动框架 frame = ttk.Frame(color_window) frame.pack(fill=tk.BOTH, expand=True, padx=5, pady=5) - + # 创建滚动条 scrollbar = ttk.Scrollbar(frame) scrollbar.pack(side=tk.RIGHT, fill=tk.Y) - + # 创建颜色设置列表 canvas = tk.Canvas(frame, yscrollcommand=scrollbar.set) canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) scrollbar.config(command=canvas.yview) - + # 创建内部框架 inner_frame = ttk.Frame(canvas) - canvas.create_window((0, 0), window=inner_frame, anchor='nw') - + canvas.create_window((0, 0), window=inner_frame, anchor="nw") + # 添加日志级别颜色设置 - ttk.Label(inner_frame, text="日志级别颜色", font=('', 10, 'bold')).pack(anchor='w', padx=5, pady=5) - for level in ['info', 'warning', 'error']: + ttk.Label(inner_frame, text="日志级别颜色", font=("", 10, "bold")).pack(anchor="w", padx=5, pady=5) + for level in ["info", "warning", "error"]: frame = ttk.Frame(inner_frame) frame.pack(fill=tk.X, padx=5, pady=2) ttk.Label(frame, text=level).pack(side=tk.LEFT) - color_btn = ttk.Button(frame, text="选择颜色", - command=lambda l=level: self.choose_color(l)) + color_btn = ttk.Button(frame, text="选择颜色", command=lambda l=level: self.choose_color(l)) color_btn.pack(side=tk.RIGHT) # 显示当前颜色 color_label = ttk.Label(frame, text="■", foreground=self.formatter.level_colors[level]) color_label.pack(side=tk.RIGHT, padx=5) - + # 添加模块颜色设置 - ttk.Label(inner_frame, text="\n模块颜色", font=('', 10, 'bold')).pack(anchor='w', padx=5, pady=5) + ttk.Label(inner_frame, text="\n模块颜色", font=("", 10, "bold")).pack(anchor="w", padx=5, pady=5) for module in sorted(self.modules): frame = ttk.Frame(inner_frame) frame.pack(fill=tk.X, padx=5, pady=2) ttk.Label(frame, text=module).pack(side=tk.LEFT) - color_btn = ttk.Button(frame, text="选择颜色", - command=lambda m=module: self.choose_module_color(m)) + color_btn = ttk.Button(frame, text="选择颜色", command=lambda m=module: self.choose_module_color(m)) color_btn.pack(side=tk.RIGHT) # 显示当前颜色 - color = self.formatter.module_colors.get(module, 'black') + color = self.formatter.module_colors.get(module, "black") color_label = ttk.Label(frame, text="■", foreground=color) color_label.pack(side=tk.RIGHT, padx=5) - + # 更新画布滚动区域 inner_frame.update_idletasks() canvas.config(scrollregion=canvas.bbox("all")) - + # 添加确定按钮 ttk.Button(color_window, text="确定", command=color_window.destroy).pack(pady=5) @@ -716,7 +712,7 @@ class LogViewer: def choose_module_color(self, module): """选择模块颜色""" - color = colorchooser.askcolor(color=self.formatter.module_colors.get(module, 'black'))[1] + color = colorchooser.askcolor(color=self.formatter.module_colors.get(module, "black"))[1] if color: self.formatter.module_colors[module] = color self.custom_module_colors[module] = color # 保存到自定义颜色 @@ -728,160 +724,164 @@ class LogViewer: """更新模块列表""" log_file = Path("logs/app.log.jsonl") if log_file.exists(): - with open(log_file, 'r', encoding='utf-8') as f: + with open(log_file, "r", encoding="utf-8") as f: for line in f: try: log_entry = json.loads(line) - if 'logger_name' in log_entry: - self.modules.add(log_entry['logger_name']) + if "logger_name" in log_entry: + self.modules.add(log_entry["logger_name"]) except json.JSONDecodeError: continue - + # 清空现有选项 for widget in self.module_inner_frame.winfo_children(): widget.destroy() - + # 计算总模块数(包括"全部") total_modules = len(self.modules) + 1 max_cols = min(4, max(2, total_modules)) # 减少最大列数,避免超出边界 - + # 配置网格列权重,让每列平均分配空间 for i in range(max_cols): self.module_inner_frame.grid_columnconfigure(i, weight=1, uniform="module_col") - + # 创建一个多行布局 current_row = 0 current_col = 0 - + # 添加"全部"选项 all_frame = ttk.Frame(self.module_inner_frame) - all_frame.grid(row=current_row, column=current_col, padx=3, pady=2, sticky='ew') - - all_var = tk.BooleanVar(value='全部' in self.selected_modules) - all_check = ttk.Checkbutton(all_frame, text="全部", variable=all_var, - command=lambda: self.toggle_module('全部', all_var)) + all_frame.grid(row=current_row, column=current_col, padx=3, pady=2, sticky="ew") + + all_var = tk.BooleanVar(value="全部" in self.selected_modules) + all_check = ttk.Checkbutton( + all_frame, text="全部", variable=all_var, command=lambda: self.toggle_module("全部", all_var) + ) all_check.pack(side=tk.LEFT) - + # 使用颜色标签替代按钮 - all_color = self.formatter.module_colors.get('全部', 'black') + all_color = self.formatter.module_colors.get("全部", "black") all_color_label = ttk.Label(all_frame, text="■", foreground=all_color, width=2, cursor="hand2") all_color_label.pack(side=tk.LEFT, padx=2) - all_color_label.bind('', lambda e: self.choose_module_color('全部')) - + all_color_label.bind("", lambda e: self.choose_module_color("全部")) + current_col += 1 - + # 添加其他模块选项 for module in sorted(self.modules): if current_col >= max_cols: current_row += 1 current_col = 0 - + frame = ttk.Frame(self.module_inner_frame) - frame.grid(row=current_row, column=current_col, padx=3, pady=2, sticky='ew') - + frame.grid(row=current_row, column=current_col, padx=3, pady=2, sticky="ew") + var = tk.BooleanVar(value=module in self.selected_modules) - + # 使用中文映射名称显示 display_name = self.get_display_name(module) if len(display_name) > 12: display_name = display_name[:10] + "..." - - check = ttk.Checkbutton(frame, text=display_name, variable=var, - command=lambda m=module, v=var: self.toggle_module(m, v)) + + check = ttk.Checkbutton( + frame, text=display_name, variable=var, command=lambda m=module, v=var: self.toggle_module(m, v) + ) check.pack(side=tk.LEFT) - + # 添加工具提示显示完整名称和英文名 full_tooltip = f"{self.get_display_name(module)}" if module != self.get_display_name(module): full_tooltip += f"\n({module})" self.create_tooltip(check, full_tooltip) - + # 使用颜色标签替代按钮 - color = self.formatter.module_colors.get(module, 'black') + color = self.formatter.module_colors.get(module, "black") color_label = ttk.Label(frame, text="■", foreground=color, width=2, cursor="hand2") color_label.pack(side=tk.LEFT, padx=2) - color_label.bind('', lambda e, m=module: self.choose_module_color(m)) - + color_label.bind("", lambda e, m=module: self.choose_module_color(m)) + current_col += 1 - + # 更新画布滚动区域 self.module_inner_frame.update_idletasks() self.module_canvas.config(scrollregion=self.module_canvas.bbox("all")) - + # 添加垂直滚动条 - if not hasattr(self, 'module_scrollbar'): - self.module_scrollbar = ttk.Scrollbar(self.module_frame, orient=tk.VERTICAL, - command=self.module_canvas.yview) + if not hasattr(self, "module_scrollbar"): + self.module_scrollbar = ttk.Scrollbar( + self.module_frame, orient=tk.VERTICAL, command=self.module_canvas.yview + ) self.module_scrollbar.pack(side=tk.RIGHT, fill=tk.Y) self.module_canvas.config(yscrollcommand=self.module_scrollbar.set) def create_tooltip(self, widget, text): """为控件创建工具提示""" + def on_enter(event): tooltip = tk.Toplevel() tooltip.wm_overrideredirect(True) - tooltip.wm_geometry(f"+{event.x_root+10}+{event.y_root+10}") + tooltip.wm_geometry(f"+{event.x_root + 10}+{event.y_root + 10}") label = ttk.Label(tooltip, text=text, background="lightyellow", relief="solid", borderwidth=1) label.pack() widget.tooltip = tooltip - + def on_leave(event): - if hasattr(widget, 'tooltip'): + if hasattr(widget, "tooltip"): widget.tooltip.destroy() del widget.tooltip - - widget.bind('', on_enter) - widget.bind('', on_leave) + + widget.bind("", on_enter) + widget.bind("", on_leave) def toggle_module(self, module, var): """切换模块选择状态""" - if module == '全部': + if module == "全部": if var.get(): - self.selected_modules = {'全部'} + self.selected_modules = {"全部"} else: self.selected_modules.clear() else: if var.get(): self.selected_modules.add(module) - if '全部' in self.selected_modules: - self.selected_modules.remove('全部') + if "全部" in self.selected_modules: + self.selected_modules.remove("全部") else: self.selected_modules.discard(module) - + self.filter_logs() def monitor_log_file(self): """监控日志文件变化""" log_file = Path("logs/app.log.jsonl") last_position = 0 - + while self.running: if log_file.exists(): try: - with open(log_file, 'r', encoding='utf-8') as f: + with open(log_file, "r", encoding="utf-8") as f: f.seek(last_position) new_lines = f.readlines() last_position = f.tell() - + for line in new_lines: try: log_entry = json.loads(line) self.log_queue.put(log_entry) self.log_cache.append(log_entry) - + # 检查是否有新模块 - if 'logger_name' in log_entry: - logger_name = log_entry['logger_name'] + if "logger_name" in log_entry: + logger_name = log_entry["logger_name"] if logger_name not in self.modules: self.modules.add(logger_name) # 在主线程中更新模块列表UI self.root.after(0, self.update_module_list) - + except json.JSONDecodeError: continue except Exception as e: print(f"Error reading log file: {e}") - + time.sleep(0.1) def update_logs(self): @@ -898,10 +898,10 @@ class LogViewer: # 检查过滤条件 if not self.should_show_log(log_entry): return - + # 使用格式化器格式化日志 parts, tags = self.formatter.format_log_entry(log_entry) - + # 在主线程中更新UI self.root.after(0, lambda: self.add_formatted_log_line(parts, tags, log_entry)) @@ -909,13 +909,13 @@ class LogViewer: """添加格式化的日志行到文本框""" # 控制最大行数 max_lines = self.viewer_config.get("max_lines", 1000) - current_lines = int(self.log_text.index('end-1c').split('.')[0]) - + current_lines = int(self.log_text.index("end-1c").split(".")[0]) + if current_lines > max_lines: # 删除前面的行 lines_to_delete = current_lines - max_lines + 100 # 一次删除多一些,减少频繁操作 self.log_text.delete(1.0, f"{lines_to_delete}.0") - + # 插入格式化的文本 for i, part in enumerate(parts): if i < len(tags): @@ -935,13 +935,13 @@ class LogViewer: self.log_text.insert(tk.END, part, tag) else: self.log_text.insert(tk.END, part) - + # 在部分之间添加空格(除了最后一个) if i < len(parts) - 1: self.log_text.insert(tk.END, " ") - + self.log_text.insert(tk.END, "\n") - + # 自动滚动 if self.viewer_config.get("auto_scroll", True): if self.log_text.yview()[1] >= 0.99: @@ -951,39 +951,39 @@ class LogViewer: """检查日志是否应该显示""" # 检查模块过滤 if self.selected_modules: - if '全部' not in self.selected_modules: - if log_entry.get('logger_name') not in self.selected_modules: + if "全部" not in self.selected_modules: + if log_entry.get("logger_name") not in self.selected_modules: return False - + # 检查级别过滤 - if self.level_var.get() != '全部': - if log_entry.get('level') != self.level_var.get(): + if self.level_var.get() != "全部": + if log_entry.get("level") != self.level_var.get(): return False - + # 检查搜索过滤 search_text = self.search_var.get().lower() if search_text: - event = str(log_entry.get('event', '')).lower() - logger_name = str(log_entry.get('logger_name', '')).lower() + event = str(log_entry.get("event", "")).lower() + logger_name = str(log_entry.get("logger_name", "")).lower() if search_text not in event and search_text not in logger_name: return False - + return True def filter_logs(self, *args): """过滤日志""" # 保存当前滚动位置 scroll_position = self.log_text.yview() - + # 清空显示 self.log_text.delete(1.0, tk.END) - + # 重新显示所有符合条件的日志 for log_entry in self.log_cache: if self.should_show_log(log_entry): parts, tags = self.formatter.format_log_entry(log_entry) self.add_formatted_log_line(parts, tags, log_entry) - + # 恢复滚动位置(如果不是自动滚动模式) if not self.viewer_config.get("auto_scroll", True): self.log_text.yview_moveto(scroll_position[0]) @@ -997,49 +997,49 @@ class LogViewer: mapping_window = tk.Toplevel(self.root) mapping_window.title("编辑模块映射") mapping_window.geometry("500x600") - + # 创建滚动框架 frame = ttk.Frame(mapping_window) frame.pack(fill=tk.BOTH, expand=True, padx=5, pady=5) - + # 创建滚动条 scrollbar = ttk.Scrollbar(frame) scrollbar.pack(side=tk.RIGHT, fill=tk.Y) - + # 创建映射编辑列表 canvas = tk.Canvas(frame, yscrollcommand=scrollbar.set) canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) scrollbar.config(command=canvas.yview) - + # 创建内部框架 inner_frame = ttk.Frame(canvas) - canvas.create_window((0, 0), window=inner_frame, anchor='nw') - + canvas.create_window((0, 0), window=inner_frame, anchor="nw") + # 添加标题 - ttk.Label(inner_frame, text="模块映射编辑", font=('', 12, 'bold')).pack(anchor='w', padx=5, pady=5) - ttk.Label(inner_frame, text="英文名 -> 中文名", font=('', 10)).pack(anchor='w', padx=5, pady=2) - + ttk.Label(inner_frame, text="模块映射编辑", font=("", 12, "bold")).pack(anchor="w", padx=5, pady=5) + ttk.Label(inner_frame, text="英文名 -> 中文名", font=("", 10)).pack(anchor="w", padx=5, pady=2) + # 映射编辑字典 mapping_vars = {} - + # 添加现有模块的映射编辑 all_modules = sorted(self.modules) for module in all_modules: frame_row = ttk.Frame(inner_frame) frame_row.pack(fill=tk.X, padx=5, pady=2) - + ttk.Label(frame_row, text=module, width=20).pack(side=tk.LEFT, padx=5) ttk.Label(frame_row, text="->").pack(side=tk.LEFT, padx=5) - + var = tk.StringVar(value=self.module_name_mapping.get(module, module)) mapping_vars[module] = var entry = ttk.Entry(frame_row, textvariable=var, width=25) entry.pack(side=tk.LEFT, padx=5) - + # 更新画布滚动区域 inner_frame.update_idletasks() canvas.config(scrollregion=canvas.bbox("all")) - + def save_mappings(): # 更新映射 for module, var in mapping_vars.items(): @@ -1048,23 +1048,25 @@ class LogViewer: self.module_name_mapping[module] = new_name elif module in self.module_name_mapping and not new_name: del self.module_name_mapping[module] - + # 保存到文件 self.save_module_mapping() # 更新模块列表显示 self.update_module_list() mapping_window.destroy() - + # 添加按钮 button_frame = ttk.Frame(mapping_window) button_frame.pack(fill=tk.X, padx=5, pady=5) ttk.Button(button_frame, text="保存", command=save_mappings).pack(side=tk.RIGHT, padx=5) ttk.Button(button_frame, text="取消", command=mapping_window.destroy).pack(side=tk.RIGHT, padx=5) + def main(): root = tk.Tk() - app = LogViewer(root) + LogViewer(root) root.mainloop() + if __name__ == "__main__": - main() \ No newline at end of file + main() diff --git a/src/chat/focus_chat/heartFC_chat.py b/src/chat/focus_chat/heartFC_chat.py index 251eea70f..98e43cf49 100644 --- a/src/chat/focus_chat/heartFC_chat.py +++ b/src/chat/focus_chat/heartFC_chat.py @@ -494,7 +494,6 @@ class HeartFChatting: "observed_messages": plan_result.get("observed_messages", ""), } - with Timer("执行动作", cycle_timers): action_type, action_data, reasoning = ( plan_result.get("action_result", {}).get("action_type", "error"), @@ -502,7 +501,6 @@ class HeartFChatting: plan_result.get("action_result", {}).get("reasoning", "未提供理由"), ) - if action_type == "reply": action_str = "回复" elif action_type == "no_reply": diff --git a/src/common/logger.py b/src/common/logger.py index a8d0417ca..4f9436145 100644 --- a/src/common/logger.py +++ b/src/common/logger.py @@ -5,71 +5,74 @@ from typing import Callable, Optional import json import structlog -from structlog.dev import ConsoleRenderer import toml # 创建logs目录 LOG_DIR = Path("logs") LOG_DIR.mkdir(exist_ok=True) + # 读取日志配置 def load_log_config(): """从配置文件加载日志设置""" config_path = Path("config/bot_config.toml") default_config = { "date_style": "Y-m-d H:i:s", - "log_level_style": "lite", + "log_level_style": "lite", "color_text": "title", "log_level": "INFO", "suppress_libraries": [], - "library_log_levels": {} + "library_log_levels": {}, } - + try: if config_path.exists(): - with open(config_path, 'r', encoding='utf-8') as f: + with open(config_path, "r", encoding="utf-8") as f: config = toml.load(f) - return config.get('log', default_config) + return config.get("log", default_config) except Exception: pass - + return default_config + LOG_CONFIG = load_log_config() + def get_timestamp_format(): """将配置中的日期格式转换为Python格式""" date_style = LOG_CONFIG.get("date_style", "Y-m-d H:i:s") # 转换PHP风格的日期格式到Python格式 format_map = { - 'Y': '%Y', # 4位年份 - 'm': '%m', # 月份(01-12) - 'd': '%d', # 日期(01-31) - 'H': '%H', # 小时(00-23) - 'i': '%M', # 分钟(00-59) - 's': '%S', # 秒数(00-59) + "Y": "%Y", # 4位年份 + "m": "%m", # 月份(01-12) + "d": "%d", # 日期(01-31) + "H": "%H", # 小时(00-23) + "i": "%M", # 分钟(00-59) + "s": "%S", # 秒数(00-59) } - + python_format = date_style for php_char, python_char in format_map.items(): python_format = python_format.replace(php_char, python_char) - + return python_format + def configure_third_party_loggers(): """配置第三方库的日志级别""" # 设置全局日志级别 global_log_level = LOG_CONFIG.get("log_level", "INFO") root_logger = logging.getLogger() root_logger.setLevel(getattr(logging, global_log_level.upper(), logging.INFO)) - + # 完全屏蔽的库 suppress_libraries = LOG_CONFIG.get("suppress_libraries", []) for lib_name in suppress_libraries: lib_logger = logging.getLogger(lib_name) lib_logger.setLevel(logging.CRITICAL + 1) # 设置为比CRITICAL更高的级别,基本屏蔽所有日志 lib_logger.propagate = False # 阻止向上传播 - + # 设置特定级别的库 library_log_levels = LOG_CONFIG.get("library_log_levels", {}) for lib_name, level_name in library_log_levels.items(): @@ -82,14 +85,14 @@ def reconfigure_existing_loggers(): """重新配置所有已存在的logger,解决加载顺序问题""" # 获取根logger root_logger = logging.getLogger() - + # 重新设置根logger的所有handler的格式化器 for handler in root_logger.handlers: if isinstance(handler, logging.handlers.RotatingFileHandler): handler.setFormatter(file_formatter) elif isinstance(handler, logging.StreamHandler): handler.setFormatter(console_formatter) - + # 遍历所有已存在的logger并重新配置 logger_dict = logging.getLogger().manager.loggerDict for name, logger_obj in logger_dict.items(): @@ -97,29 +100,29 @@ def reconfigure_existing_loggers(): # 检查是否是第三方库logger suppress_libraries = LOG_CONFIG.get("suppress_libraries", []) library_log_levels = LOG_CONFIG.get("library_log_levels", {}) - + # 如果在屏蔽列表中 if any(name.startswith(lib) for lib in suppress_libraries): logger_obj.setLevel(logging.CRITICAL + 1) logger_obj.propagate = False continue - + # 如果在特定级别设置中 for lib_name, level_name in library_log_levels.items(): if name.startswith(lib_name): level = getattr(logging, level_name.upper(), logging.WARNING) logger_obj.setLevel(level) break - + # 强制清除并重新设置所有handler original_handlers = logger_obj.handlers[:] for handler in original_handlers: logger_obj.removeHandler(handler) - + # 如果logger没有handler,让它使用根logger的handler(propagate=True) if not logger_obj.handlers: logger_obj.propagate = True - + # 如果logger有自己的handler,重新配置它们 for handler in original_handlers: if isinstance(handler, logging.handlers.RotatingFileHandler): @@ -128,24 +131,25 @@ def reconfigure_existing_loggers(): handler.setFormatter(console_formatter) logger_obj.addHandler(handler) + # 定义模块颜色映射 MODULE_COLORS = { - "api": "\033[92m", # 亮绿色 - "emoji": "\033[92m", # 亮绿色 - "chat": "\033[94m", # 亮蓝色 - "config": "\033[93m", # 亮黄色 - "common": "\033[95m", # 亮紫色 - "tools": "\033[96m", # 亮青色 + "api": "\033[92m", # 亮绿色 + "emoji": "\033[92m", # 亮绿色 + "chat": "\033[94m", # 亮蓝色 + "config": "\033[93m", # 亮黄色 + "common": "\033[95m", # 亮紫色 + "tools": "\033[96m", # 亮青色 "lpmm": "\033[96m", - "plugin_system": "\033[91m", # 亮红色 - "experimental": "\033[97m", # 亮白色 - "person_info": "\033[32m", # 绿色 - "individuality": "\033[34m", # 蓝色 - "manager": "\033[35m", # 紫色 + "plugin_system": "\033[91m", # 亮红色 + "experimental": "\033[97m", # 亮白色 + "person_info": "\033[32m", # 绿色 + "individuality": "\033[34m", # 蓝色 + "manager": "\033[35m", # 紫色 "llm_models": "\033[36m", # 青色 - "plugins": "\033[31m", # 红色 + "plugins": "\033[31m", # 红色 "plugin_api": "\033[33m", # 黄色 - "remote": "\033[38;5;93m", # 紫蓝色 + "remote": "\033[38;5;93m", # 紫蓝色 } RESET_COLOR = "\033[0m" @@ -153,21 +157,21 @@ RESET_COLOR = "\033[0m" class ModuleColoredConsoleRenderer: """自定义控制台渲染器,为不同模块提供不同颜色""" - + def __init__(self, colors=True): self._colors = colors self._config = LOG_CONFIG - + # 日志级别颜色 self._level_colors = { - "debug": "\033[38;5;208m", # 橙色 - "info": "\033[34m", # 蓝色 - "success": "\033[32m", # 绿色 - "warning": "\033[33m", # 黄色 - "error": "\033[31m", # 红色 - "critical": "\033[35m", # 紫色 + "debug": "\033[38;5;208m", # 橙色 + "info": "\033[34m", # 蓝色 + "success": "\033[32m", # 绿色 + "warning": "\033[33m", # 黄色 + "error": "\033[31m", # 红色 + "critical": "\033[35m", # 紫色 } - + # 根据配置决定是否启用颜色 color_text = self._config.get("color_text", "title") if color_text == "none": @@ -181,7 +185,7 @@ class ModuleColoredConsoleRenderer: else: self._enable_module_colors = True self._enable_level_colors = False - + def __call__(self, logger, method_name, event_dict): """渲染日志消息""" # 获取基本信息 @@ -189,14 +193,14 @@ class ModuleColoredConsoleRenderer: level = event_dict.get("level", "info") logger_name = event_dict.get("logger_name", "") event = event_dict.get("event", "") - + # 构建输出 parts = [] - + # 日志级别样式配置 log_level_style = self._config.get("log_level_style", "lite") level_color = self._level_colors.get(level.lower(), "") if self._colors else "" - + # 时间戳(lite模式下按级别着色) if timestamp: if log_level_style == "lite" and level_color: @@ -204,7 +208,7 @@ class ModuleColoredConsoleRenderer: else: timestamp_part = timestamp parts.append(timestamp_part) - + # 日志级别显示(根据配置样式) if log_level_style == "full": # 显示完整级别名并着色 @@ -214,7 +218,7 @@ class ModuleColoredConsoleRenderer: else: level_part = f"[{level_text:>8}]" parts.append(level_part) - + elif log_level_style == "compact": # 只显示首字母并着色 level_text = level.upper()[0] @@ -223,9 +227,9 @@ class ModuleColoredConsoleRenderer: else: level_part = f"[{level_text:>8}]" parts.append(level_part) - + # lite模式不显示级别,只给时间戳着色 - + # 模块名称(带颜色) if logger_name: if self._colors and self._enable_module_colors: @@ -237,7 +241,7 @@ class ModuleColoredConsoleRenderer: else: module_part = f"[{logger_name}]" parts.append(module_part) - + # 消息内容(确保转换为字符串) if isinstance(event, str): parts.append(event) @@ -250,7 +254,7 @@ class ModuleColoredConsoleRenderer: else: # 其他类型直接转换为字符串 parts.append(str(event)) - + # 处理其他字段 extras = [] for key, value in event_dict.items(): @@ -264,10 +268,10 @@ class ModuleColoredConsoleRenderer: else: value_str = str(value) extras.append(f"{key}={value_str}") - + if extras: parts.append(" ".join(extras)) - + return " ".join(parts) @@ -288,6 +292,7 @@ logging.basicConfig( ], ) + def configure_structlog(): """配置structlog""" structlog.configure( @@ -306,6 +311,7 @@ def configure_structlog(): cache_logger_on_first_use=True, ) + # 配置structlog configure_structlog() @@ -343,39 +349,43 @@ for handler in root_logger.handlers: else: handler.setFormatter(console_formatter) + # 立即配置日志系统,确保最早期的日志也使用正确格式 def _immediate_setup(): """立即设置日志系统,在模块导入时就生效""" # 重新配置structlog configure_structlog() - + # 清除所有已有的handler,重新配置 root_logger = logging.getLogger() for handler in root_logger.handlers[:]: root_logger.removeHandler(handler) - + # 重新添加配置好的handler - root_logger.addHandler(logging.handlers.RotatingFileHandler( - LOG_DIR / "app.log.jsonl", - maxBytes=10 * 1024 * 1024, - backupCount=5, - encoding="utf-8", - )) + root_logger.addHandler( + logging.handlers.RotatingFileHandler( + LOG_DIR / "app.log.jsonl", + maxBytes=10 * 1024 * 1024, + backupCount=5, + encoding="utf-8", + ) + ) root_logger.addHandler(logging.StreamHandler()) - + # 设置格式化器 for handler in root_logger.handlers: if isinstance(handler, logging.handlers.RotatingFileHandler): handler.setFormatter(file_formatter) else: handler.setFormatter(console_formatter) - + # 配置第三方库日志 configure_third_party_loggers() - + # 重新配置所有已存在的logger reconfigure_existing_loggers() + # 立即执行配置 _immediate_setup() @@ -418,7 +428,7 @@ def configure_logging( def set_module_color(module_name: str, color_code: str): """为指定模块设置颜色 - + Args: module_name: 模块名称 color_code: ANSI颜色代码,例如 '\033[92m' 表示亮绿色 @@ -435,27 +445,29 @@ def reload_log_config(): """重新加载日志配置""" global LOG_CONFIG LOG_CONFIG = load_log_config() - + # 重新配置console渲染器 root_logger = logging.getLogger() for handler in root_logger.handlers: if isinstance(handler, logging.StreamHandler) and not isinstance(handler, logging.handlers.RotatingFileHandler): # 这是控制台处理器,更新其格式化器 - handler.setFormatter(structlog.stdlib.ProcessorFormatter( - processor=ModuleColoredConsoleRenderer(colors=True), - foreign_pre_chain=[ - structlog.stdlib.add_logger_name, - structlog.stdlib.add_log_level, - structlog.stdlib.PositionalArgumentsFormatter(), - structlog.processors.TimeStamper(fmt=get_timestamp_format(), utc=False), - structlog.processors.StackInfoRenderer(), - structlog.processors.format_exc_info, - ], - )) - + handler.setFormatter( + structlog.stdlib.ProcessorFormatter( + processor=ModuleColoredConsoleRenderer(colors=True), + foreign_pre_chain=[ + structlog.stdlib.add_logger_name, + structlog.stdlib.add_log_level, + structlog.stdlib.PositionalArgumentsFormatter(), + structlog.processors.TimeStamper(fmt=get_timestamp_format(), utc=False), + structlog.processors.StackInfoRenderer(), + structlog.processors.format_exc_info, + ], + ) + ) + # 重新配置第三方库日志 configure_third_party_loggers() - + # 重新配置所有已存在的logger reconfigure_existing_loggers() @@ -469,27 +481,29 @@ def force_reset_all_loggers(): """强制重置所有logger,解决格式不一致问题""" # 清除所有现有的logger配置 logging.getLogger().manager.loggerDict.clear() - + # 重新配置根logger root_logger = logging.getLogger() root_logger.handlers.clear() - + # 重新添加我们的handler - root_logger.addHandler(logging.handlers.RotatingFileHandler( - LOG_DIR / "app.log.jsonl", - maxBytes=10 * 1024 * 1024, - backupCount=5, - encoding="utf-8", - )) + root_logger.addHandler( + logging.handlers.RotatingFileHandler( + LOG_DIR / "app.log.jsonl", + maxBytes=10 * 1024 * 1024, + backupCount=5, + encoding="utf-8", + ) + ) root_logger.addHandler(logging.StreamHandler()) - + # 设置格式化器 for handler in root_logger.handlers: if isinstance(handler, logging.handlers.RotatingFileHandler): handler.setFormatter(file_formatter) else: handler.setFormatter(console_formatter) - + # 设置级别 global_log_level = LOG_CONFIG.get("log_level", "INFO") root_logger.setLevel(getattr(logging, global_log_level.upper(), logging.INFO)) @@ -497,14 +511,14 @@ def force_reset_all_loggers(): def initialize_logging(): """手动初始化日志系统,确保所有logger都使用正确的配置 - + 在应用程序的早期调用此函数,确保所有模块都使用统一的日志配置 """ global LOG_CONFIG LOG_CONFIG = load_log_config() configure_third_party_loggers() reconfigure_existing_loggers() - + # 输出初始化信息 logger = get_logger("logger") log_level = LOG_CONFIG.get("log_level", "INFO") @@ -515,16 +529,16 @@ def force_initialize_logging(): """强制重新初始化整个日志系统,解决格式不一致问题""" global LOG_CONFIG LOG_CONFIG = load_log_config() - + # 强制重置所有logger force_reset_all_loggers() - + # 重新配置structlog configure_structlog() - + # 配置第三方库 configure_third_party_loggers() - + # 输出初始化信息 logger = get_logger("logger") log_level = LOG_CONFIG.get("log_level", "INFO") @@ -533,14 +547,14 @@ def force_initialize_logging(): def show_module_colors(): """显示所有模块的颜色效果""" - logger = get_logger("demo") + get_logger("demo") print("\n=== 模块颜色展示 ===") - - for module_name, color_code in MODULE_COLORS.items(): + + for module_name, _color_code in MODULE_COLORS.items(): # 临时创建一个该模块的logger来展示颜色 demo_logger = structlog.get_logger(module_name).bind(logger_name=module_name) demo_logger.info(f"这是 {module_name} 模块的颜色效果") - + print("=== 颜色展示结束 ===\n")