This commit is contained in:
SengokuCola
2025-06-12 20:30:46 +08:00
5 changed files with 458 additions and 444 deletions

2
bot.py
View File

@@ -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:

View File

@@ -8,6 +8,7 @@ import time
import toml
from datetime import datetime
class LogFormatter:
"""日志格式化器同步logger.py的格式"""
@@ -16,32 +17,32 @@ class LogFormatter:
# 日志级别颜色
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", # 紫蓝色
}
# 应用自定义颜色
@@ -163,8 +164,8 @@ class LogFormatter:
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
@@ -172,12 +173,12 @@ class LogFormatter:
# 根据配置格式化
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
@@ -188,6 +189,7 @@ class LogFormatter:
except Exception:
return timestamp
class LogViewer:
def __init__(self, root):
self.root = root
@@ -221,7 +223,7 @@ class LogViewer:
# 创建模块选择内部框架
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)
@@ -237,7 +239,7 @@ class LogViewer:
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)
# 搜索框
@@ -256,9 +258,15 @@ class LogViewer:
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)
@@ -267,26 +275,26 @@ class LogViewer:
# 模块名映射
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": "意愿",
}
# 加载自定义映射
@@ -304,8 +312,8 @@ class LogViewer:
self.update_module_list()
# 绑定事件
self.level_combo.bind('<<ComboboxSelected>>', self.filter_logs)
self.search_var.trace('w', self.filter_logs)
self.level_combo.bind("<<ComboboxSelected>>", self.filter_logs)
self.search_var.trace("w", self.filter_logs)
# 启动日志监控线程
self.running = True
@@ -322,24 +330,15 @@ 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加载日志配置
@@ -349,10 +348,10 @@ class LogViewer:
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}")
@@ -363,28 +362,28 @@ class LogViewer:
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 "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 "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):
@@ -398,16 +397,13 @@ class LogViewer:
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}")
@@ -442,42 +438,49 @@ class LogViewer:
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)
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.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)
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))
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()
@@ -496,8 +499,8 @@ class LogViewer:
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):
"""显示查看器设置窗口"""
@@ -509,33 +512,32 @@ class LogViewer:
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()
@@ -551,8 +553,8 @@ class LogViewer:
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):
"""应用主题设置"""
@@ -560,19 +562,16 @@ class LogViewer:
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)
)
# 重新配置标签样式
@@ -582,7 +581,7 @@ class LogViewer:
"""配置文本标签样式"""
# 清除现有标签
for tag in self.log_text.tag_names():
if tag != 'sel':
if tag != "sel":
self.log_text.tag_delete(tag)
# 基础标签
@@ -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}")
@@ -668,32 +666,30 @@ class LogViewer:
# 创建内部框架
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)
@@ -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,12 +724,12 @@ 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
@@ -755,18 +751,19 @@ class LogViewer:
# 添加"全部"选项
all_frame = ttk.Frame(self.module_inner_frame)
all_frame.grid(row=current_row, column=current_col, padx=3, pady=2, sticky='ew')
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_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('<Button-1>', lambda e: self.choose_module_color('全部'))
all_color_label.bind("<Button-1>", lambda e: self.choose_module_color("全部"))
current_col += 1
@@ -777,7 +774,7 @@ class LogViewer:
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)
@@ -786,8 +783,9 @@ class LogViewer:
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)
# 添加工具提示显示完整名称和英文名
@@ -797,10 +795,10 @@ class LogViewer:
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('<Button-1>', lambda e, m=module: self.choose_module_color(m))
color_label.bind("<Button-1>", lambda e, m=module: self.choose_module_color(m))
current_col += 1
@@ -809,42 +807,44 @@ class LogViewer:
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('<Enter>', on_enter)
widget.bind('<Leave>', on_leave)
widget.bind("<Enter>", on_enter)
widget.bind("<Leave>", 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)
@@ -858,7 +858,7 @@ class LogViewer:
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()
@@ -870,8 +870,8 @@ class LogViewer:
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
@@ -909,7 +909,7 @@ 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:
# 删除前面的行
@@ -951,20 +951,20 @@ 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
@@ -1013,11 +1013,11 @@ class LogViewer:
# 创建内部框架
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 = {}
@@ -1061,10 +1061,12 @@ class LogViewer:
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()

View File

@@ -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":

View File

@@ -5,13 +5,13 @@ 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():
"""从配置文件加载日志设置"""
@@ -22,32 +22,34 @@ def load_log_config():
"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
@@ -56,6 +58,7 @@ def get_timestamp_format():
return python_format
def configure_third_party_loggers():
"""配置第三方库的日志级别"""
# 设置全局日志级别
@@ -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"
@@ -160,12 +164,12 @@ class ModuleColoredConsoleRenderer:
# 日志级别颜色
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", # 紫色
}
# 根据配置决定是否启用颜色
@@ -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,6 +349,7 @@ for handler in root_logger.handlers:
else:
handler.setFormatter(console_formatter)
# 立即配置日志系统,确保最早期的日志也使用正确格式
def _immediate_setup():
"""立即设置日志系统,在模块导入时就生效"""
@@ -355,12 +362,14 @@ def _immediate_setup():
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())
# 设置格式化器
@@ -376,6 +385,7 @@ def _immediate_setup():
# 重新配置所有已存在的logger
reconfigure_existing_loggers()
# 立即执行配置
_immediate_setup()
@@ -441,17 +451,19 @@ def reload_log_config():
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()
@@ -475,12 +487,14 @@ def force_reset_all_loggers():
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())
# 设置格式化器
@@ -533,10 +547,10 @@ 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} 模块的颜色效果")