149 lines
4.7 KiB
Python
149 lines
4.7 KiB
Python
"""Debug API — 系统日志与运行状态"""
|
|
|
|
import os
|
|
from datetime import datetime, timedelta
|
|
from fastapi import APIRouter, Depends
|
|
from sqlalchemy import text
|
|
|
|
from app.core.deps import get_current_admin
|
|
from app.db.database import get_db
|
|
from app.config import settings, is_trading_hours
|
|
|
|
router = APIRouter(prefix="/api/debug", tags=["debug"])
|
|
|
|
|
|
@router.get("/errors")
|
|
async def get_errors(
|
|
limit: int = 50,
|
|
source: str = None,
|
|
level: str = None,
|
|
days: int = 7,
|
|
_admin: dict = Depends(get_current_admin),
|
|
):
|
|
"""获取错误日志(管理员)"""
|
|
start = (datetime.now() - timedelta(days=days)).strftime("%Y-%m-%d")
|
|
async with get_db() as db:
|
|
conditions = ["created_at >= :start"]
|
|
params = {"start": start}
|
|
|
|
if source:
|
|
conditions.append("source = :source")
|
|
params["source"] = source
|
|
if level:
|
|
conditions.append("level = :level")
|
|
params["level"] = level
|
|
|
|
where = " AND " + " AND ".join(conditions)
|
|
|
|
# 总数
|
|
count_result = await db.execute(
|
|
text(f"SELECT COUNT(*) FROM error_logs WHERE {where}"), params
|
|
)
|
|
total = count_result.scalar() or 0
|
|
|
|
# 查询
|
|
params["limit"] = limit
|
|
result = await db.execute(
|
|
text(
|
|
f"SELECT id, source, level, message, detail, created_at "
|
|
f"FROM error_logs WHERE {where} "
|
|
f"ORDER BY created_at DESC LIMIT :limit"
|
|
),
|
|
params,
|
|
)
|
|
rows = result.fetchall()
|
|
errors = []
|
|
for row in rows:
|
|
r = row._mapping
|
|
errors.append({
|
|
"id": r["id"],
|
|
"source": r["source"],
|
|
"level": r["level"],
|
|
"message": r["message"],
|
|
"detail": r["detail"] or "",
|
|
"created_at": str(r["created_at"]) if r["created_at"] else "",
|
|
})
|
|
|
|
# 可选的 source/level 列表(用于前端过滤)
|
|
sources_result = await db.execute(
|
|
text("SELECT DISTINCT source FROM error_logs ORDER BY source")
|
|
)
|
|
sources = [r[0] for r in sources_result.fetchall()]
|
|
|
|
levels_result = await db.execute(
|
|
text("SELECT DISTINCT level FROM error_logs ORDER BY level")
|
|
)
|
|
levels = [r[0] for r in levels_result.fetchall()]
|
|
|
|
return {
|
|
"total": total,
|
|
"errors": errors,
|
|
"sources": sources,
|
|
"levels": levels,
|
|
}
|
|
|
|
|
|
@router.delete("/errors")
|
|
async def clear_errors(
|
|
days: int = 30,
|
|
_admin: dict = Depends(get_current_admin),
|
|
):
|
|
"""清除旧错误日志(管理员)"""
|
|
cutoff = (datetime.now() - timedelta(days=days)).strftime("%Y-%m-%d")
|
|
async with get_db() as db:
|
|
result = await db.execute(
|
|
text("DELETE FROM error_logs WHERE created_at < :cutoff"),
|
|
{"cutoff": cutoff},
|
|
)
|
|
deleted = result.rowcount
|
|
await db.commit()
|
|
return {"status": "ok", "deleted": deleted}
|
|
|
|
|
|
@router.get("/system")
|
|
async def system_status(_admin: dict = Depends(get_current_admin)):
|
|
"""系统运行状态摘要(管理员)"""
|
|
from app.engine.recommender import _scan_running, _scan_lock
|
|
|
|
async with get_db() as db:
|
|
# 各表数据量
|
|
tables_counts = {}
|
|
for t in ["recommendations", "sector_heat", "market_temperature",
|
|
"recommendation_tracking", "stock_diagnoses",
|
|
"error_logs", "users"]:
|
|
result = await db.execute(text(f"SELECT COUNT(*) FROM {t}"))
|
|
tables_counts[t] = result.scalar() or 0
|
|
|
|
# 最近 24h 错误数
|
|
since = (datetime.now() - timedelta(hours=24)).strftime("%Y-%m-%d %H:%M:%S")
|
|
result = await db.execute(
|
|
text("SELECT COUNT(*) FROM error_logs WHERE created_at >= :since"),
|
|
{"since": since},
|
|
)
|
|
recent_errors = result.scalar() or 0
|
|
|
|
# 最近错误
|
|
result = await db.execute(
|
|
text("SELECT source, message, created_at FROM error_logs ORDER BY created_at DESC LIMIT 5")
|
|
)
|
|
last_errors = [
|
|
{"source": r[0], "message": r[1], "created_at": str(r[2])}
|
|
for r in result.fetchall()
|
|
]
|
|
|
|
# 数据库文件大小
|
|
db_path = settings.database_url.replace("sqlite:///", "")
|
|
db_size_mb = 0
|
|
if os.path.exists(db_path):
|
|
db_size_mb = round(os.path.getsize(db_path) / 1024 / 1024, 2)
|
|
|
|
return {
|
|
"is_trading": is_trading_hours(),
|
|
"scan_running": _scan_running,
|
|
"scan_locked": _scan_lock.locked(),
|
|
"recent_errors": recent_errors,
|
|
"last_errors": last_errors,
|
|
"tables_counts": tables_counts,
|
|
"db_size_mb": db_size_mb,
|
|
}
|