2026-01-19 00:09:36 +08:00
|
|
|
|
"""
|
|
|
|
|
|
执行日志服务
|
2026-05-10 19:50:20 +08:00
|
|
|
|
- ExecutionLogger: 工作流执行的日志记录器(per-execution)
|
|
|
|
|
|
- ExecutionLoggerService: Agent 执行的日志服务(全局单例)
|
2026-01-19 00:09:36 +08:00
|
|
|
|
"""
|
2026-05-10 19:50:20 +08:00
|
|
|
|
from typing import Dict, Any, Optional, List
|
2026-01-19 00:09:36 +08:00
|
|
|
|
from datetime import datetime
|
|
|
|
|
|
from sqlalchemy.orm import Session
|
|
|
|
|
|
from app.models.execution_log import ExecutionLog
|
2026-05-10 19:50:20 +08:00
|
|
|
|
import asyncio
|
2026-01-19 00:09:36 +08:00
|
|
|
|
import logging
|
|
|
|
|
|
|
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ExecutionLogger:
|
2026-05-10 19:50:20 +08:00
|
|
|
|
"""执行日志记录器(工作流/编排执行上下文)"""
|
|
|
|
|
|
|
2026-01-19 00:09:36 +08:00
|
|
|
|
def __init__(self, execution_id: str, db: Session):
|
|
|
|
|
|
self.execution_id = execution_id
|
|
|
|
|
|
self.db = db
|
2026-05-10 19:50:20 +08:00
|
|
|
|
|
2026-01-19 00:09:36 +08:00
|
|
|
|
def log(
|
|
|
|
|
|
self,
|
|
|
|
|
|
level: str,
|
|
|
|
|
|
message: str,
|
|
|
|
|
|
node_id: Optional[str] = None,
|
|
|
|
|
|
node_type: Optional[str] = None,
|
|
|
|
|
|
data: Optional[Dict[str, Any]] = None,
|
|
|
|
|
|
duration: Optional[int] = None
|
|
|
|
|
|
):
|
|
|
|
|
|
try:
|
|
|
|
|
|
log_entry = ExecutionLog(
|
|
|
|
|
|
execution_id=self.execution_id,
|
|
|
|
|
|
node_id=node_id,
|
|
|
|
|
|
node_type=node_type,
|
|
|
|
|
|
level=level.upper(),
|
|
|
|
|
|
message=message,
|
|
|
|
|
|
data=data,
|
|
|
|
|
|
duration=duration,
|
|
|
|
|
|
timestamp=datetime.utcnow()
|
|
|
|
|
|
)
|
|
|
|
|
|
self.db.add(log_entry)
|
|
|
|
|
|
self.db.commit()
|
|
|
|
|
|
log_method = getattr(logger, level.lower(), logger.info)
|
|
|
|
|
|
log_msg = f"[执行 {self.execution_id}]"
|
|
|
|
|
|
if node_id:
|
|
|
|
|
|
log_msg += f" [节点 {node_id}]"
|
|
|
|
|
|
log_msg += f" {message}"
|
|
|
|
|
|
log_method(log_msg)
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
logger.error(f"记录执行日志失败: {str(e)}")
|
2026-05-10 19:50:20 +08:00
|
|
|
|
|
2026-01-19 00:09:36 +08:00
|
|
|
|
def info(self, message: str, **kwargs):
|
|
|
|
|
|
self.log("INFO", message, **kwargs)
|
2026-05-10 19:50:20 +08:00
|
|
|
|
|
2026-01-19 00:09:36 +08:00
|
|
|
|
def warn(self, message: str, **kwargs):
|
|
|
|
|
|
self.log("WARN", message, **kwargs)
|
2026-05-10 19:50:20 +08:00
|
|
|
|
|
2026-01-19 00:09:36 +08:00
|
|
|
|
def error(self, message: str, **kwargs):
|
|
|
|
|
|
self.log("ERROR", message, **kwargs)
|
2026-05-10 19:50:20 +08:00
|
|
|
|
|
2026-01-19 00:09:36 +08:00
|
|
|
|
def debug(self, message: str, **kwargs):
|
|
|
|
|
|
self.log("DEBUG", message, **kwargs)
|
2026-05-10 19:50:20 +08:00
|
|
|
|
|
2026-01-19 00:09:36 +08:00
|
|
|
|
def log_node_start(self, node_id: str, node_type: str, input_data: Optional[Dict[str, Any]] = None):
|
|
|
|
|
|
self.info(
|
|
|
|
|
|
f"节点 {node_id} ({node_type}) 开始执行",
|
2026-05-10 19:50:20 +08:00
|
|
|
|
node_id=node_id, node_type=node_type,
|
2026-01-19 00:09:36 +08:00
|
|
|
|
data={"input": input_data} if input_data else None
|
|
|
|
|
|
)
|
2026-05-10 19:50:20 +08:00
|
|
|
|
|
2026-01-19 00:09:36 +08:00
|
|
|
|
def log_node_complete(self, node_id: str, node_type: str, output_data: Optional[Dict[str, Any]] = None, duration: Optional[int] = None):
|
|
|
|
|
|
self.info(
|
|
|
|
|
|
f"节点 {node_id} ({node_type}) 执行完成",
|
2026-05-10 19:50:20 +08:00
|
|
|
|
node_id=node_id, node_type=node_type,
|
2026-01-19 00:09:36 +08:00
|
|
|
|
data={"output": output_data} if output_data else None,
|
|
|
|
|
|
duration=duration
|
|
|
|
|
|
)
|
2026-05-10 19:50:20 +08:00
|
|
|
|
|
2026-01-19 00:09:36 +08:00
|
|
|
|
def log_node_error(self, node_id: str, node_type: str, error: Exception, duration: Optional[int] = None):
|
|
|
|
|
|
self.error(
|
|
|
|
|
|
f"节点 {node_id} ({node_type}) 执行失败: {str(error)}",
|
2026-05-10 19:50:20 +08:00
|
|
|
|
node_id=node_id, node_type=node_type,
|
2026-01-19 00:09:36 +08:00
|
|
|
|
data={"error": str(error), "error_type": type(error).__name__},
|
|
|
|
|
|
duration=duration
|
|
|
|
|
|
)
|
2026-05-10 19:50:20 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ExecutionLoggerService:
|
|
|
|
|
|
"""Agent 执行日志服务(全局单例,记录 Agent 每次执行的完整信息)"""
|
|
|
|
|
|
|
|
|
|
|
|
def log_execution_sync(
|
|
|
|
|
|
self,
|
|
|
|
|
|
*,
|
|
|
|
|
|
agent_id: Optional[str] = None,
|
|
|
|
|
|
agent_name: Optional[str] = None,
|
|
|
|
|
|
goal_id: Optional[str] = None,
|
|
|
|
|
|
task_id: Optional[str] = None,
|
|
|
|
|
|
user_id: Optional[str] = None,
|
|
|
|
|
|
session_id: Optional[str] = None,
|
|
|
|
|
|
input_text: Optional[str] = None,
|
|
|
|
|
|
output_text: Optional[str] = None,
|
|
|
|
|
|
output_truncated: bool = False,
|
|
|
|
|
|
success: bool = True,
|
|
|
|
|
|
error_message: Optional[str] = None,
|
|
|
|
|
|
latency_ms: int = 0,
|
|
|
|
|
|
iterations_used: int = 0,
|
|
|
|
|
|
tool_calls_made: int = 0,
|
|
|
|
|
|
tool_chain: Optional[List[Dict[str, Any]]] = None,
|
|
|
|
|
|
llm_calls: Optional[List[Dict[str, Any]]] = None,
|
|
|
|
|
|
steps: Optional[List[Dict[str, Any]]] = None,
|
|
|
|
|
|
model: Optional[str] = None,
|
|
|
|
|
|
provider: Optional[str] = None,
|
|
|
|
|
|
) -> Optional[str]:
|
|
|
|
|
|
"""同步写入执行日志,返回日志 ID。"""
|
|
|
|
|
|
from app.core.database import SessionLocal
|
|
|
|
|
|
from app.models.agent_execution_log import AgentExecutionLog
|
|
|
|
|
|
db: Optional[Session] = None
|
|
|
|
|
|
try:
|
|
|
|
|
|
db = SessionLocal()
|
|
|
|
|
|
entry = AgentExecutionLog(
|
|
|
|
|
|
agent_id=agent_id, agent_name=agent_name,
|
|
|
|
|
|
goal_id=goal_id, task_id=task_id,
|
|
|
|
|
|
user_id=user_id, session_id=session_id,
|
|
|
|
|
|
input_text=input_text[:5000] if input_text else None,
|
|
|
|
|
|
output_text=output_text[:10000] if output_text else None,
|
|
|
|
|
|
output_truncated=output_truncated,
|
|
|
|
|
|
success=success,
|
|
|
|
|
|
error_message=error_message[:2000] if error_message else None,
|
|
|
|
|
|
latency_ms=latency_ms,
|
|
|
|
|
|
iterations_used=iterations_used,
|
|
|
|
|
|
tool_calls_made=tool_calls_made,
|
|
|
|
|
|
tool_chain=tool_chain, llm_calls=llm_calls, steps=steps,
|
|
|
|
|
|
model=model, provider=provider,
|
|
|
|
|
|
)
|
|
|
|
|
|
db.add(entry)
|
|
|
|
|
|
db.commit()
|
|
|
|
|
|
db.refresh(entry)
|
|
|
|
|
|
return str(entry.id)
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
logger.warning("写入 Agent 执行日志失败: %s", e)
|
|
|
|
|
|
if db:
|
|
|
|
|
|
try:
|
|
|
|
|
|
db.rollback()
|
|
|
|
|
|
except Exception:
|
|
|
|
|
|
pass
|
|
|
|
|
|
return None
|
|
|
|
|
|
finally:
|
|
|
|
|
|
if db:
|
|
|
|
|
|
try:
|
|
|
|
|
|
db.close()
|
|
|
|
|
|
except Exception:
|
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
async def log_execution(self, **kwargs) -> Optional[str]:
|
|
|
|
|
|
"""异步写入执行日志(线程池)。"""
|
|
|
|
|
|
loop = asyncio.get_running_loop()
|
|
|
|
|
|
return await loop.run_in_executor(None, lambda: self.log_execution_sync(**kwargs))
|
|
|
|
|
|
|
|
|
|
|
|
def log_execution_fire_and_forget(self, **kwargs):
|
|
|
|
|
|
"""Fire-and-forget 写入。"""
|
|
|
|
|
|
try:
|
|
|
|
|
|
asyncio.ensure_future(self.log_execution(**kwargs))
|
|
|
|
|
|
except Exception:
|
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# 全局单例
|
|
|
|
|
|
execution_logger = ExecutionLoggerService()
|