将运行态数据(对话/用户工作区/日志等)默认迁出源码树到 ~/.agents/<mode>, 对齐 ~/.claude、~/.codex 惯例;同时为日志加上开关与轮转,并归档/清理仓库内 长期堆积的实验残留与误产生文件。 ## 路径变量重写(核心) - 重写 config/paths.py:运行态根按模式分流,host -> ~/.agents/host, 其它(默认 docker/web)-> ~/.agents/web。 - 三级解析优先级(高→低): 1) 具体目录变量 DATA_DIR / LOGS_DIR / USER_SPACE_DIR / API_USER_SPACE_DIR 2) 模式根变量 AGENTS_HOST_HOME / AGENTS_WEB_HOME 3) 兜底 ~/.agents/<mode> - 导出变量名保持不变,20+ 处消费方零改动;统一 config/sub_agent.py 重复的 路径解析逻辑,将 sub_agent 任务目录/状态文件收编进 DATA_DIR; SUB_AGENT_PROJECT_RESULTS_DIR 仍随工作区(有意保留)。 - config/*.json、prompts/、agentskills/ 属配置/资源,仍锚定源码树。 ## 日志止血 - 新增 utils/log_rotation.py:按大小轮转(默认 20MB×3 份)+ 按份清理 (dump 默认保留最近 30 个),阈值可由 AGENT_LOG_ROTATE_MAX_BYTES / AGENT_LOG_ROTATE_BACKUPS / AGENT_DUMP_KEEP 覆盖。 - utils/api_client.py:API 请求体 dump 默认关闭(AGENT_API_DUMP_ENABLED 开启), 收编两处写死的 logs/ 硬编码路径改用 LOGS_DIR;dump 关闭时 request_dump 安全置空。 - utils/host_workspace_debug.py、server/utils_common.py(chunk/conn_diag 等)、 utils/logger.py(TaskLogger/ErrorLogger)统一接入轮转。 ## 迁移与运维脚本 - scripts/migrate_runtime_data.py:源码树 -> 运行态根,复制+备份+校验+可回滚+幂等, logs 丢弃不迁;import config 复用程序同一套路径解析(模式由 .env 决定), 覆盖 data/users/api 及 sub_agent/tasks(-> data/sub_agent_tasks)。 - scripts/cleanup_misplaced_web.sh:清理误迁到 ~/.agents/web 的副本(带 chflags 兜底)。 ## 测试 - 重写 test/test_config_paths_resolution.py:覆盖默认分流、模式切换、模式根变量覆盖、 具体目录变量最高优先级、相对路径锚定 repo root、源码树配置项不随迁移等 6 个用例。 ## 仓库清理 - 删除旧版子智能体目录 sub_agent/(逻辑已统一在 easyagent/)。 - 删除误产生/垃圾文件:误敲命令生成的 "ystemctl status ..."、空文件 .zhouyanbo / testfile_from_ai、运行态日志/pid、test_playwright.png、test_system_message.py 等。 - 实验残留与历史文档(BUG_FIX/POLLING 变更日志、SUB_AGENT 文档、翻译资料、 model_tests、compact_result、奇奇怪怪的bug、截图、goal_research)归档到 本地 _experiments/,并从版本控制移除(已加入 .gitignore)。 - .gitignore:新增 api/、_experiments/,清理已失效的 sub_agent/* 条目与冗余项。 ## 文档 - CLAUDE.md / AGENTS.md:补充数据目录与路径变量、日志策略、迁移流程, 更新目录结构(移除 sub_agent、新增 _experiments/scripts),调试日志路径改为 ~/.agents/<mode>/logs/。
150 lines
4.7 KiB
Python
150 lines
4.7 KiB
Python
# utils/logger.py - 日志系统
|
||
|
||
import logging
|
||
import os
|
||
from datetime import datetime
|
||
from logging.handlers import RotatingFileHandler
|
||
from pathlib import Path
|
||
try:
|
||
from config import LOGS_DIR, LOG_LEVEL, LOG_FORMAT
|
||
except ImportError:
|
||
import sys
|
||
from pathlib import Path
|
||
project_root = Path(__file__).resolve().parents[1]
|
||
if str(project_root) not in sys.path:
|
||
sys.path.insert(0, str(project_root))
|
||
from config import LOGS_DIR, LOG_LEVEL, LOG_FORMAT
|
||
|
||
from utils.log_rotation import DEFAULT_MAX_BYTES, DEFAULT_BACKUPS
|
||
|
||
def _env_flag(name: str, default: bool = False) -> bool:
|
||
value = str(os.environ.get(name, "")).strip().lower()
|
||
if not value:
|
||
return default
|
||
return value in {"1", "true", "yes", "on"}
|
||
|
||
|
||
_FILE_LOG_ENABLED = _env_flag("AGENT_FILE_LOG_ENABLED", default=False)
|
||
|
||
def setup_logger(name: str, log_file: str = None) -> logging.Logger:
|
||
"""
|
||
设置日志记录器
|
||
|
||
Args:
|
||
name: 日志记录器名称
|
||
log_file: 日志文件路径(可选)
|
||
|
||
Returns:
|
||
配置好的日志记录器
|
||
"""
|
||
logger = logging.getLogger(name)
|
||
logger.setLevel(getattr(logging, LOG_LEVEL))
|
||
|
||
# 清除已有的处理器
|
||
logger.handlers.clear()
|
||
|
||
# 创建格式化器
|
||
formatter = logging.Formatter(LOG_FORMAT)
|
||
|
||
# 控制台处理器(与全局 LOG_LEVEL 保持一致,便于实时查看)
|
||
console_handler = logging.StreamHandler()
|
||
console_handler.setLevel(getattr(logging, LOG_LEVEL))
|
||
console_handler.setFormatter(formatter)
|
||
logger.addHandler(console_handler)
|
||
|
||
# 文件处理器(默认关闭,避免在工作区生成 agent_debug/agent_*.log)
|
||
if _FILE_LOG_ENABLED:
|
||
if log_file:
|
||
file_path = Path(LOGS_DIR) / log_file
|
||
else:
|
||
# 默认日志文件
|
||
today = datetime.now().strftime("%Y%m%d")
|
||
file_path = Path(LOGS_DIR) / f"agent_{today}.log"
|
||
|
||
# 确保日志目录存在
|
||
file_path.parent.mkdir(parents=True, exist_ok=True)
|
||
|
||
file_handler = RotatingFileHandler(
|
||
file_path,
|
||
maxBytes=DEFAULT_MAX_BYTES,
|
||
backupCount=DEFAULT_BACKUPS,
|
||
encoding='utf-8',
|
||
)
|
||
file_handler.setLevel(getattr(logging, LOG_LEVEL))
|
||
file_handler.setFormatter(formatter)
|
||
logger.addHandler(file_handler)
|
||
|
||
return logger
|
||
|
||
class TaskLogger:
|
||
"""任务专用日志记录器"""
|
||
|
||
def __init__(self, task_id: str):
|
||
self.task_id = task_id
|
||
self.log_file = Path(LOGS_DIR) / "tasks" / f"{task_id}.log"
|
||
self.log_file.parent.mkdir(parents=True, exist_ok=True)
|
||
|
||
self.logger = setup_logger(f"task_{task_id}", str(self.log_file))
|
||
|
||
def log_action(self, action: str, details: dict = None):
|
||
"""记录操作"""
|
||
log_entry = {
|
||
"timestamp": datetime.now().isoformat(),
|
||
"action": action,
|
||
"details": details or {}
|
||
}
|
||
self.logger.info(f"ACTION: {log_entry}")
|
||
|
||
def log_result(self, success: bool, message: str, data: dict = None):
|
||
"""记录结果"""
|
||
log_entry = {
|
||
"timestamp": datetime.now().isoformat(),
|
||
"success": success,
|
||
"message": message,
|
||
"data": data or {}
|
||
}
|
||
|
||
if success:
|
||
self.logger.info(f"RESULT: {log_entry}")
|
||
else:
|
||
self.logger.error(f"RESULT: {log_entry}")
|
||
|
||
def log_error(self, error: Exception, context: str = ""):
|
||
"""记录错误"""
|
||
log_entry = {
|
||
"timestamp": datetime.now().isoformat(),
|
||
"error": str(error),
|
||
"type": type(error).__name__,
|
||
"context": context
|
||
}
|
||
self.logger.error(f"ERROR: {log_entry}", exc_info=True)
|
||
|
||
def get_log_content(self) -> str:
|
||
"""获取日志内容"""
|
||
if self.log_file.exists():
|
||
with open(self.log_file, 'r', encoding='utf-8') as f:
|
||
return f.read()
|
||
return ""
|
||
|
||
class ErrorLogger:
|
||
"""错误专用日志记录器"""
|
||
|
||
@staticmethod
|
||
def log_error(module: str, error: Exception, context: dict = None):
|
||
"""记录错误到错误日志"""
|
||
today = datetime.now().strftime("%Y%m%d")
|
||
error_file = Path(LOGS_DIR) / "errors" / f"errors_{today}.log"
|
||
error_file.parent.mkdir(parents=True, exist_ok=True)
|
||
|
||
logger = setup_logger(f"error_{module}", str(error_file))
|
||
|
||
error_entry = {
|
||
"timestamp": datetime.now().isoformat(),
|
||
"module": module,
|
||
"error": str(error),
|
||
"type": type(error).__name__,
|
||
"context": context or {}
|
||
}
|
||
|
||
logger.error(f"ERROR: {error_entry}", exc_info=True)
|