263 lines
7.3 KiB
Python
263 lines
7.3 KiB
Python
|
|
"""
|
|||
|
|
运行日志服务
|
|||
|
|
提供统一的日志记录接口,支持分类存储和查询
|
|||
|
|
"""
|
|||
|
|
import json
|
|||
|
|
import logging
|
|||
|
|
from datetime import datetime, timedelta
|
|||
|
|
from typing import Optional
|
|||
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|||
|
|
from sqlalchemy import select, and_, desc, func
|
|||
|
|
from app.models.log import Log, LogType, LogLevel
|
|||
|
|
|
|||
|
|
logger = logging.getLogger(__name__)
|
|||
|
|
|
|||
|
|
# 日志级别映射
|
|||
|
|
LEVEL_MAP = {
|
|||
|
|
"DEBUG": LogLevel.DEBUG,
|
|||
|
|
"INFO": LogLevel.INFO,
|
|||
|
|
"WARNING": LogLevel.WARNING,
|
|||
|
|
"ERROR": LogLevel.ERROR,
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
class LogService:
|
|||
|
|
def __init__(self, db: AsyncSession):
|
|||
|
|
self.db = db
|
|||
|
|
|
|||
|
|
async def log(
|
|||
|
|
self,
|
|||
|
|
message: str,
|
|||
|
|
level: str = "info",
|
|||
|
|
log_type: str = "system",
|
|||
|
|
user_id: Optional[str] = None,
|
|||
|
|
source: Optional[str] = None,
|
|||
|
|
details: Optional[dict] = None,
|
|||
|
|
duration_ms: Optional[int] = None,
|
|||
|
|
) -> Log:
|
|||
|
|
"""记录日志"""
|
|||
|
|
log_entry = Log(
|
|||
|
|
level=level,
|
|||
|
|
type=log_type,
|
|||
|
|
user_id=user_id,
|
|||
|
|
message=message,
|
|||
|
|
source=source,
|
|||
|
|
details=json.dumps(details, ensure_ascii=False) if details else None,
|
|||
|
|
duration_ms=str(duration_ms) if duration_ms else None,
|
|||
|
|
)
|
|||
|
|
self.db.add(log_entry)
|
|||
|
|
await self.db.commit()
|
|||
|
|
await self.db.refresh(log_entry)
|
|||
|
|
return log_entry
|
|||
|
|
|
|||
|
|
async def agent_log(
|
|||
|
|
self,
|
|||
|
|
message: str,
|
|||
|
|
user_id: Optional[str] = None,
|
|||
|
|
source: Optional[str] = None,
|
|||
|
|
details: Optional[dict] = None,
|
|||
|
|
duration_ms: Optional[int] = None,
|
|||
|
|
) -> Log:
|
|||
|
|
"""记录智能体调用日志"""
|
|||
|
|
return await self.log(
|
|||
|
|
message=message,
|
|||
|
|
level="info",
|
|||
|
|
log_type="agent",
|
|||
|
|
user_id=user_id,
|
|||
|
|
source=source,
|
|||
|
|
details=details,
|
|||
|
|
duration_ms=duration_ms,
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
async def system_log(
|
|||
|
|
self,
|
|||
|
|
message: str,
|
|||
|
|
level: str = "info",
|
|||
|
|
source: Optional[str] = None,
|
|||
|
|
details: Optional[dict] = None,
|
|||
|
|
) -> Log:
|
|||
|
|
"""记录系统运行日志"""
|
|||
|
|
return await self.log(
|
|||
|
|
message=message,
|
|||
|
|
level=level,
|
|||
|
|
log_type="system",
|
|||
|
|
user_id=None,
|
|||
|
|
source=source,
|
|||
|
|
details=details,
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
async def chat_log(
|
|||
|
|
self,
|
|||
|
|
message: str,
|
|||
|
|
user_id: str,
|
|||
|
|
details: Optional[dict] = None,
|
|||
|
|
duration_ms: Optional[int] = None,
|
|||
|
|
) -> Log:
|
|||
|
|
"""记录问答日志"""
|
|||
|
|
return await self.log(
|
|||
|
|
message=message,
|
|||
|
|
level="info",
|
|||
|
|
log_type="chat",
|
|||
|
|
user_id=user_id,
|
|||
|
|
source="chat",
|
|||
|
|
details=details,
|
|||
|
|
duration_ms=duration_ms,
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
async def list_logs(
|
|||
|
|
self,
|
|||
|
|
log_type: Optional[str] = None,
|
|||
|
|
level: Optional[str] = None,
|
|||
|
|
user_id: Optional[str] = None,
|
|||
|
|
source: Optional[str] = None,
|
|||
|
|
limit: int = 100,
|
|||
|
|
offset: int = 0,
|
|||
|
|
) -> tuple[list[Log], int]:
|
|||
|
|
"""
|
|||
|
|
查询日志列表
|
|||
|
|
|
|||
|
|
Returns:
|
|||
|
|
(logs, total_count)
|
|||
|
|
"""
|
|||
|
|
conditions = []
|
|||
|
|
|
|||
|
|
if log_type:
|
|||
|
|
conditions.append(Log.type == log_type)
|
|||
|
|
if level:
|
|||
|
|
conditions.append(Log.level == level)
|
|||
|
|
if user_id:
|
|||
|
|
conditions.append(Log.user_id == user_id)
|
|||
|
|
if source:
|
|||
|
|
conditions.append(Log.source == source)
|
|||
|
|
|
|||
|
|
# 统计总数
|
|||
|
|
count_query = select(func.count(Log.id))
|
|||
|
|
if conditions:
|
|||
|
|
count_query = count_query.where(and_(*conditions))
|
|||
|
|
total_result = await self.db.execute(count_query)
|
|||
|
|
total = total_result.scalar() or 0
|
|||
|
|
|
|||
|
|
# 查询列表
|
|||
|
|
query = (
|
|||
|
|
select(Log)
|
|||
|
|
.where(and_(*conditions)) if conditions else select(Log)
|
|||
|
|
).order_by(desc(Log.created_at)).limit(limit).offset(offset)
|
|||
|
|
|
|||
|
|
result = await self.db.execute(query)
|
|||
|
|
logs = list(result.scalars().all())
|
|||
|
|
|
|||
|
|
return logs, total
|
|||
|
|
|
|||
|
|
async def get_recent_logs(
|
|||
|
|
self,
|
|||
|
|
log_type: Optional[str] = None,
|
|||
|
|
hours: int = 24,
|
|||
|
|
limit: int = 100,
|
|||
|
|
) -> list[Log]:
|
|||
|
|
"""获取最近的日志"""
|
|||
|
|
since = datetime.utcnow() - timedelta(hours=hours)
|
|||
|
|
conditions = [Log.created_at >= since]
|
|||
|
|
|
|||
|
|
if log_type:
|
|||
|
|
conditions.append(Log.type == log_type)
|
|||
|
|
|
|||
|
|
query = (
|
|||
|
|
select(Log)
|
|||
|
|
.where(and_(*conditions))
|
|||
|
|
.order_by(desc(Log.created_at))
|
|||
|
|
.limit(limit)
|
|||
|
|
)
|
|||
|
|
result = await self.db.execute(query)
|
|||
|
|
return list(result.scalars().all())
|
|||
|
|
|
|||
|
|
async def get_log_stats(self, hours: int = 24) -> dict:
|
|||
|
|
"""获取日志统计"""
|
|||
|
|
since = datetime.utcnow() - timedelta(hours=hours)
|
|||
|
|
|
|||
|
|
stats = {
|
|||
|
|
"total": 0,
|
|||
|
|
"by_type": {"agent": 0, "system": 0, "chat": 0},
|
|||
|
|
"by_level": {"debug": 0, "info": 0, "warning": 0, "error": 0},
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# 按类型统计
|
|||
|
|
for log_type in ["agent", "system", "chat"]:
|
|||
|
|
query = select(func.count(Log.id)).where(
|
|||
|
|
and_(Log.type == log_type, Log.created_at >= since)
|
|||
|
|
)
|
|||
|
|
result = await self.db.execute(query)
|
|||
|
|
count = result.scalar() or 0
|
|||
|
|
stats["by_type"][log_type] = count
|
|||
|
|
stats["total"] += count
|
|||
|
|
|
|||
|
|
# 按级别统计
|
|||
|
|
for level in ["debug", "info", "warning", "error"]:
|
|||
|
|
query = select(func.count(Log.id)).where(
|
|||
|
|
and_(Log.level == level, Log.created_at >= since)
|
|||
|
|
)
|
|||
|
|
result = await self.db.execute(query)
|
|||
|
|
stats["by_level"][level] = result.scalar() or 0
|
|||
|
|
|
|||
|
|
return stats
|
|||
|
|
|
|||
|
|
|
|||
|
|
# 全局日志记录函数,方便各处调用
|
|||
|
|
_global_db_session = None
|
|||
|
|
|
|||
|
|
|
|||
|
|
def set_log_session(db: AsyncSession):
|
|||
|
|
"""设置全局日志会话"""
|
|||
|
|
global _global_db_session
|
|||
|
|
_global_db_session = db
|
|||
|
|
|
|||
|
|
|
|||
|
|
def get_log_session() -> Optional[AsyncSession]:
|
|||
|
|
"""获取全局日志会话"""
|
|||
|
|
return _global_db_session
|
|||
|
|
|
|||
|
|
|
|||
|
|
async def log_agent_event(
|
|||
|
|
message: str,
|
|||
|
|
user_id: Optional[str] = None,
|
|||
|
|
source: Optional[str] = None,
|
|||
|
|
details: Optional[dict] = None,
|
|||
|
|
duration_ms: Optional[int] = None,
|
|||
|
|
):
|
|||
|
|
"""记录智能体事件到数据库"""
|
|||
|
|
if _global_db_session:
|
|||
|
|
try:
|
|||
|
|
svc = LogService(_global_db_session)
|
|||
|
|
await svc.agent_log(message, user_id, source, details, duration_ms)
|
|||
|
|
except Exception as e:
|
|||
|
|
logger.error(f"Failed to log agent event: {e}")
|
|||
|
|
|
|||
|
|
|
|||
|
|
async def log_system_event(
|
|||
|
|
message: str,
|
|||
|
|
level: str = "info",
|
|||
|
|
source: Optional[str] = None,
|
|||
|
|
details: Optional[dict] = None,
|
|||
|
|
):
|
|||
|
|
"""记录系统事件到数据库"""
|
|||
|
|
if _global_db_session:
|
|||
|
|
try:
|
|||
|
|
svc = LogService(_global_db_session)
|
|||
|
|
await svc.system_log(message, level, source, details)
|
|||
|
|
except Exception as e:
|
|||
|
|
logger.error(f"Failed to log system event: {e}")
|
|||
|
|
|
|||
|
|
|
|||
|
|
async def log_chat_event(
|
|||
|
|
message: str,
|
|||
|
|
user_id: str,
|
|||
|
|
details: Optional[dict] = None,
|
|||
|
|
duration_ms: Optional[int] = None,
|
|||
|
|
):
|
|||
|
|
"""记录聊天事件到数据库"""
|
|||
|
|
if _global_db_session:
|
|||
|
|
try:
|
|||
|
|
svc = LogService(_global_db_session)
|
|||
|
|
await svc.chat_log(message, user_id, details, duration_ms)
|
|||
|
|
except Exception as e:
|
|||
|
|
logger.error(f"Failed to log chat event: {e}")
|