116 lines
3.3 KiB
Python
116 lines
3.3 KiB
Python
|
|
"""审计日志 Hook - Phase 7.2
|
|||
|
|
|
|||
|
|
记录所有工具调用到审计日志。
|
|||
|
|
"""
|
|||
|
|
|
|||
|
|
from typing import Any
|
|||
|
|
|
|||
|
|
from app.agents.tools.hooks.types import (
|
|||
|
|
ExecutionContext,
|
|||
|
|
HookResult,
|
|||
|
|
HookType,
|
|||
|
|
)
|
|||
|
|
from app.agents.tools.manifest import ToolCategory
|
|||
|
|
|
|||
|
|
|
|||
|
|
class AuditLogHook:
|
|||
|
|
"""审计日志 Hook
|
|||
|
|
|
|||
|
|
记录所有工具调用的详细信息,包括:
|
|||
|
|
- 调用时间
|
|||
|
|
- 工具名称
|
|||
|
|
- 输入参数
|
|||
|
|
- 执行结果
|
|||
|
|
- 执行时长
|
|||
|
|
- 用户 ID
|
|||
|
|
"""
|
|||
|
|
|
|||
|
|
def __init__(self, log_path: str | None = None):
|
|||
|
|
"""
|
|||
|
|
Args:
|
|||
|
|
log_path: 日志文件路径,None 则输出到 stdout
|
|||
|
|
"""
|
|||
|
|
self.log_path = log_path
|
|||
|
|
self._logs: list[dict[str, Any]] = []
|
|||
|
|
|
|||
|
|
async def pre_tool_use(self, context: ExecutionContext) -> HookResult:
|
|||
|
|
"""工具执行前记录"""
|
|||
|
|
log_entry = {
|
|||
|
|
"event": "pre_tool",
|
|||
|
|
"tool_name": context.tool_name,
|
|||
|
|
"input": context.tool_input,
|
|||
|
|
"user_id": context.user_id,
|
|||
|
|
"session_id": context.session_id,
|
|||
|
|
}
|
|||
|
|
self._logs.append(log_entry)
|
|||
|
|
self._write_log(log_entry)
|
|||
|
|
return HookResult(
|
|||
|
|
hook_name="audit_log",
|
|||
|
|
success=True,
|
|||
|
|
continue_execution=True,
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
async def post_tool_use(self, context: ExecutionContext, result: Any) -> HookResult:
|
|||
|
|
"""工具执行后记录"""
|
|||
|
|
log_entry = {
|
|||
|
|
"event": "post_tool",
|
|||
|
|
"tool_name": context.tool_name,
|
|||
|
|
"result": str(result)[:500] if result else None,
|
|||
|
|
"duration_ms": (
|
|||
|
|
(context.end_time - context.start_time) * 1000
|
|||
|
|
if context.start_time and context.end_time
|
|||
|
|
else None
|
|||
|
|
),
|
|||
|
|
}
|
|||
|
|
self._logs.append(log_entry)
|
|||
|
|
self._write_log(log_entry)
|
|||
|
|
return HookResult(
|
|||
|
|
hook_name="audit_log",
|
|||
|
|
success=True,
|
|||
|
|
continue_execution=True,
|
|||
|
|
modified_output=result,
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
async def tool_error(self, context: ExecutionContext, error: Exception) -> HookResult:
|
|||
|
|
"""工具出错时记录"""
|
|||
|
|
log_entry = {
|
|||
|
|
"event": "tool_error",
|
|||
|
|
"tool_name": context.tool_name,
|
|||
|
|
"error": str(error),
|
|||
|
|
"error_type": type(error).__name__,
|
|||
|
|
}
|
|||
|
|
self._logs.append(log_entry)
|
|||
|
|
self._write_log(log_entry)
|
|||
|
|
return HookResult(
|
|||
|
|
hook_name="audit_log",
|
|||
|
|
success=False,
|
|||
|
|
continue_execution=True,
|
|||
|
|
error=str(error),
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
def _write_log(self, entry: dict[str, Any]) -> None:
|
|||
|
|
"""写入日志"""
|
|||
|
|
import json
|
|||
|
|
import datetime
|
|||
|
|
|
|||
|
|
entry["timestamp"] = datetime.datetime.now().isoformat()
|
|||
|
|
|
|||
|
|
if self.log_path:
|
|||
|
|
try:
|
|||
|
|
with open(self.log_path, "a", encoding="utf-8") as f:
|
|||
|
|
f.write(json.dumps(entry, ensure_ascii=False) + "\n")
|
|||
|
|
except Exception:
|
|||
|
|
# 日志写入失败不影响主流程
|
|||
|
|
pass
|
|||
|
|
else:
|
|||
|
|
# 输出到 stdout
|
|||
|
|
print(f"[AUDIT] {json.dumps(entry, ensure_ascii=False)}")
|
|||
|
|
|
|||
|
|
def get_logs(self) -> list[dict[str, Any]]:
|
|||
|
|
"""获取所有日志"""
|
|||
|
|
return self._logs.copy()
|
|||
|
|
|
|||
|
|
def clear_logs(self) -> None:
|
|||
|
|
"""清空日志"""
|
|||
|
|
self._logs.clear()
|