chore: reduce logging verbosity and clean up file formatting
- Change logger.info to logger.debug in agent_tasks.py streaming and tree endpoints - Disable SQLAlchemy echo mode in database session configuration - Suppress uvicorn access logs and LiteLLM INFO level logging in main application - Remove LogViewer component and LogsPage from frontend - Add trailing newlines to multiple backend configuration and model files - Update frontend routing to remove logs page reference - Improve application startup logging clarity by filtering verbose third-party logs
This commit is contained in:
parent
f05c0073e1
commit
eed111c04d
|
|
@ -61,3 +61,4 @@ CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -105,3 +105,4 @@ datefmt = %H:%M:%S
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -92,3 +92,4 @@ else:
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -27,3 +27,4 @@ def downgrade() -> None:
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1110,7 +1110,7 @@ async def stream_agent_with_thinking(
|
||||||
event_manager = _running_event_managers.get(task_id)
|
event_manager = _running_event_managers.get(task_id)
|
||||||
|
|
||||||
if event_manager:
|
if event_manager:
|
||||||
logger.info(f"Stream {task_id}: Using in-memory event manager")
|
logger.debug(f"Stream {task_id}: Using in-memory event manager")
|
||||||
try:
|
try:
|
||||||
# 使用 EventManager 的流式接口
|
# 使用 EventManager 的流式接口
|
||||||
# 过滤选项
|
# 过滤选项
|
||||||
|
|
@ -1146,7 +1146,7 @@ async def stream_agent_with_thinking(
|
||||||
yield format_sse_event(err_data)
|
yield format_sse_event(err_data)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
logger.info(f"Stream {task_id}: Task not running, falling back to DB polling")
|
logger.debug(f"Stream {task_id}: Task not running, falling back to DB polling")
|
||||||
# 2. 回退到数据库轮询 (无法获取 thinking_token)
|
# 2. 回退到数据库轮询 (无法获取 thinking_token)
|
||||||
last_sequence = after_sequence
|
last_sequence = after_sequence
|
||||||
poll_interval = 2.0 # 完成的任务轮询可以慢一点
|
poll_interval = 2.0 # 完成的任务轮询可以慢一点
|
||||||
|
|
@ -1572,15 +1572,15 @@ async def get_agent_tree(
|
||||||
|
|
||||||
# 尝试从内存中获取 Agent 树(运行中的任务)
|
# 尝试从内存中获取 Agent 树(运行中的任务)
|
||||||
runner = _running_tasks.get(task_id)
|
runner = _running_tasks.get(task_id)
|
||||||
logger.info(f"[AgentTree API] task_id={task_id}, runner exists={runner is not None}")
|
logger.debug(f"[AgentTree API] task_id={task_id}, runner exists={runner is not None}")
|
||||||
|
|
||||||
if runner:
|
if runner:
|
||||||
from app.services.agent.core import agent_registry
|
from app.services.agent.core import agent_registry
|
||||||
|
|
||||||
tree = agent_registry.get_agent_tree()
|
tree = agent_registry.get_agent_tree()
|
||||||
stats = agent_registry.get_statistics()
|
stats = agent_registry.get_statistics()
|
||||||
logger.info(f"[AgentTree API] tree nodes={len(tree.get('nodes', {}))}, root={tree.get('root_agent_id')}")
|
logger.debug(f"[AgentTree API] tree nodes={len(tree.get('nodes', {}))}, root={tree.get('root_agent_id')}")
|
||||||
logger.info(f"[AgentTree API] 节点详情: {list(tree.get('nodes', {}).keys())}")
|
logger.debug(f"[AgentTree API] 节点详情: {list(tree.get('nodes', {}).keys())}")
|
||||||
|
|
||||||
# 构建节点列表
|
# 构建节点列表
|
||||||
nodes = []
|
nodes = []
|
||||||
|
|
|
||||||
|
|
@ -212,3 +212,4 @@ async def remove_project_member(
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -227,3 +227,4 @@ async def toggle_user_status(
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -31,3 +31,4 @@ def get_password_hash(password: str) -> str:
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,3 +14,4 @@ class Base:
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
|
||||||
from sqlalchemy.orm import sessionmaker
|
from sqlalchemy.orm import sessionmaker
|
||||||
from app.core.config import settings
|
from app.core.config import settings
|
||||||
|
|
||||||
engine = create_async_engine(settings.DATABASE_URL, echo=True, future=True)
|
engine = create_async_engine(settings.DATABASE_URL, echo=False, future=True)
|
||||||
|
|
||||||
AsyncSessionLocal = sessionmaker(
|
AsyncSessionLocal = sessionmaker(
|
||||||
engine, class_=AsyncSession, expire_on_commit=False
|
engine, class_=AsyncSession, expire_on_commit=False
|
||||||
|
|
@ -30,3 +30,4 @@ async def async_session_factory():
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,11 @@ from app.db.init_db import init_db
|
||||||
logging.basicConfig(level=logging.INFO)
|
logging.basicConfig(level=logging.INFO)
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
# 禁用 uvicorn access log 和 LiteLLM INFO 日志
|
||||||
|
logging.getLogger("uvicorn.access").setLevel(logging.WARNING)
|
||||||
|
logging.getLogger("LiteLLM").setLevel(logging.WARNING)
|
||||||
|
logging.getLogger("litellm").setLevel(logging.WARNING)
|
||||||
|
|
||||||
|
|
||||||
@asynccontextmanager
|
@asynccontextmanager
|
||||||
async def lifespan(app: FastAPI):
|
async def lifespan(app: FastAPI):
|
||||||
|
|
|
||||||
|
|
@ -26,3 +26,4 @@ class InstantAnalysis(Base):
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -27,3 +27,4 @@ class User(Base):
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -32,3 +32,4 @@ class UserConfig(Base):
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,3 +12,4 @@ class TokenPayload(BaseModel):
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -43,3 +43,4 @@ class UserListResponse(BaseModel):
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -311,13 +311,13 @@ class BaseAgent(ABC):
|
||||||
|
|
||||||
def _register_to_registry(self, task: Optional[str] = None) -> None:
|
def _register_to_registry(self, task: Optional[str] = None) -> None:
|
||||||
"""注册到Agent注册表(延迟注册,在run时调用)"""
|
"""注册到Agent注册表(延迟注册,在run时调用)"""
|
||||||
logger.info(f"[AgentTree] _register_to_registry 被调用: {self.config.name} (id={self._agent_id}, parent={self.parent_id}, _registered={self._registered})")
|
logger.debug(f"[AgentTree] _register_to_registry 被调用: {self.config.name} (id={self._agent_id}, parent={self.parent_id}, _registered={self._registered})")
|
||||||
|
|
||||||
if self._registered:
|
if self._registered:
|
||||||
logger.warning(f"[AgentTree] {self.config.name} 已注册,跳过 (id={self._agent_id})")
|
logger.debug(f"[AgentTree] {self.config.name} 已注册,跳过 (id={self._agent_id})")
|
||||||
return
|
return
|
||||||
|
|
||||||
logger.info(f"[AgentTree] 正在注册 Agent: {self.config.name} (id={self._agent_id}, parent={self.parent_id})")
|
logger.debug(f"[AgentTree] 正在注册 Agent: {self.config.name} (id={self._agent_id}, parent={self.parent_id})")
|
||||||
|
|
||||||
agent_registry.register_agent(
|
agent_registry.register_agent(
|
||||||
agent_id=self._agent_id,
|
agent_id=self._agent_id,
|
||||||
|
|
@ -335,7 +335,7 @@ class BaseAgent(ABC):
|
||||||
self._registered = True
|
self._registered = True
|
||||||
|
|
||||||
tree = agent_registry.get_agent_tree()
|
tree = agent_registry.get_agent_tree()
|
||||||
logger.info(f"[AgentTree] Agent 注册完成: {self.config.name}, 当前树节点数: {len(tree['nodes'])}")
|
logger.debug(f"[AgentTree] Agent 注册完成: {self.config.name}, 当前树节点数: {len(tree['nodes'])}")
|
||||||
|
|
||||||
def set_parent_id(self, parent_id: str) -> None:
|
def set_parent_id(self, parent_id: str) -> None:
|
||||||
"""设置父Agent ID(在调度时调用)"""
|
"""设置父Agent ID(在调度时调用)"""
|
||||||
|
|
|
||||||
|
|
@ -500,10 +500,21 @@ class OrchestratorAgent(BaseAgent):
|
||||||
task = params.get("task", "")
|
task = params.get("task", "")
|
||||||
context = params.get("context", "")
|
context = params.get("context", "")
|
||||||
|
|
||||||
|
logger.debug(f"[Orchestrator] _dispatch_agent 被调用: agent_name='{agent_name}', task='{task[:50]}...'")
|
||||||
|
|
||||||
|
# 🔥 尝试大小写不敏感匹配
|
||||||
agent = self.sub_agents.get(agent_name)
|
agent = self.sub_agents.get(agent_name)
|
||||||
|
if not agent:
|
||||||
|
# 尝试小写匹配
|
||||||
|
agent_name_lower = agent_name.lower()
|
||||||
|
agent = self.sub_agents.get(agent_name_lower)
|
||||||
|
if agent:
|
||||||
|
agent_name = agent_name_lower
|
||||||
|
logger.debug(f"[Orchestrator] 使用小写匹配: {agent_name}")
|
||||||
|
|
||||||
if not agent:
|
if not agent:
|
||||||
available = list(self.sub_agents.keys())
|
available = list(self.sub_agents.keys())
|
||||||
|
logger.warning(f"[Orchestrator] Agent '{agent_name}' 不存在,可用: {available}")
|
||||||
return f"错误: Agent '{agent_name}' 不存在。可用的 Agent: {available}"
|
return f"错误: Agent '{agent_name}' 不存在。可用的 Agent: {available}"
|
||||||
|
|
||||||
# 🔥 检查是否重复调度同一个 Agent
|
# 🔥 检查是否重复调度同一个 Agent
|
||||||
|
|
@ -524,11 +535,11 @@ class OrchestratorAgent(BaseAgent):
|
||||||
self._dispatched_tasks[agent_name] = dispatch_count + 1
|
self._dispatched_tasks[agent_name] = dispatch_count + 1
|
||||||
|
|
||||||
# 🔥 设置父 Agent ID 并注册到注册表(动态 Agent 树)
|
# 🔥 设置父 Agent ID 并注册到注册表(动态 Agent 树)
|
||||||
logger.info(f"[Orchestrator] 准备调度 {agent_name} Agent, agent._registered={agent._registered}")
|
logger.debug(f"[Orchestrator] 准备调度 {agent_name} Agent, agent._registered={agent._registered}")
|
||||||
agent.set_parent_id(self._agent_id)
|
agent.set_parent_id(self._agent_id)
|
||||||
logger.info(f"[Orchestrator] 设置 parent_id 完成,准备注册 {agent_name}")
|
logger.debug(f"[Orchestrator] 设置 parent_id 完成,准备注册 {agent_name}")
|
||||||
agent._register_to_registry(task=task)
|
agent._register_to_registry(task=task)
|
||||||
logger.info(f"[Orchestrator] {agent_name} 注册完成,agent._registered={agent._registered}")
|
logger.debug(f"[Orchestrator] {agent_name} 注册完成,agent._registered={agent._registered}")
|
||||||
|
|
||||||
await self.emit_event(
|
await self.emit_event(
|
||||||
"dispatch",
|
"dispatch",
|
||||||
|
|
|
||||||
|
|
@ -267,7 +267,7 @@ class AgentStatePersistence:
|
||||||
session.add(checkpoint)
|
session.add(checkpoint)
|
||||||
await session.commit()
|
await session.commit()
|
||||||
|
|
||||||
logger.info(f"Saved agent state to database: {state.agent_id}")
|
logger.debug(f"Saved agent state to database: {state.agent_id}")
|
||||||
return True
|
return True
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|
|
||||||
|
|
@ -77,8 +77,8 @@ class AgentRegistry:
|
||||||
Returns:
|
Returns:
|
||||||
注册的节点信息
|
注册的节点信息
|
||||||
"""
|
"""
|
||||||
logger.info(f"[AgentRegistry] register_agent 被调用: {agent_name} (id={agent_id}, parent={parent_id})")
|
logger.debug(f"[AgentRegistry] register_agent 被调用: {agent_name} (id={agent_id}, parent={parent_id})")
|
||||||
logger.info(f"[AgentRegistry] 当前节点数: {len(self._agent_graph['nodes'])}, 节点列表: {list(self._agent_graph['nodes'].keys())}")
|
logger.debug(f"[AgentRegistry] 当前节点数: {len(self._agent_graph['nodes'])}, 节点列表: {list(self._agent_graph['nodes'].keys())}")
|
||||||
|
|
||||||
with self._lock:
|
with self._lock:
|
||||||
node = {
|
node = {
|
||||||
|
|
@ -124,8 +124,8 @@ class AgentRegistry:
|
||||||
if parent_id is None and self._root_agent_id is None:
|
if parent_id is None and self._root_agent_id is None:
|
||||||
self._root_agent_id = agent_id
|
self._root_agent_id = agent_id
|
||||||
|
|
||||||
logger.info(f"[AgentRegistry] 注册完成: {agent_name} ({agent_id}), parent: {parent_id}")
|
logger.debug(f"[AgentRegistry] 注册完成: {agent_name} ({agent_id}), parent: {parent_id}")
|
||||||
logger.info(f"[AgentRegistry] 注册后节点数: {len(self._agent_graph['nodes'])}, 节点列表: {list(self._agent_graph['nodes'].keys())}")
|
logger.debug(f"[AgentRegistry] 注册后节点数: {len(self._agent_graph['nodes'])}, 节点列表: {list(self._agent_graph['nodes'].keys())}")
|
||||||
return node
|
return node
|
||||||
|
|
||||||
def unregister_agent(self, agent_id: str) -> None:
|
def unregister_agent(self, agent_id: str) -> None:
|
||||||
|
|
@ -145,7 +145,7 @@ class AgentRegistry:
|
||||||
if e["from"] != agent_id and e["to"] != agent_id
|
if e["from"] != agent_id and e["to"] != agent_id
|
||||||
]
|
]
|
||||||
|
|
||||||
logger.info(f"Unregistered agent: {agent_id}")
|
logger.debug(f"Unregistered agent: {agent_id}")
|
||||||
|
|
||||||
# ============ Agent 状态更新 ============
|
# ============ Agent 状态更新 ============
|
||||||
|
|
||||||
|
|
@ -287,7 +287,7 @@ class AgentRegistry:
|
||||||
self._agent_messages.clear()
|
self._agent_messages.clear()
|
||||||
self._running_agents.clear()
|
self._running_agents.clear()
|
||||||
self._root_agent_id = None
|
self._root_agent_id = None
|
||||||
logger.info("Agent registry cleared")
|
logger.debug("Agent registry cleared")
|
||||||
|
|
||||||
def cleanup_finished_agents(self) -> int:
|
def cleanup_finished_agents(self) -> int:
|
||||||
"""清理已完成的Agent"""
|
"""清理已完成的Agent"""
|
||||||
|
|
|
||||||
|
|
@ -432,13 +432,13 @@ class EventManager:
|
||||||
|
|
||||||
# 检查是否是结束事件
|
# 检查是否是结束事件
|
||||||
if event_type in ["task_complete", "task_error", "task_cancel"]:
|
if event_type in ["task_complete", "task_error", "task_cancel"]:
|
||||||
logger.info(f"Task {task_id} already completed, sent {buffered_count} buffered events")
|
logger.debug(f"Task {task_id} already completed, sent {buffered_count} buffered events")
|
||||||
return
|
return
|
||||||
except asyncio.QueueEmpty:
|
except asyncio.QueueEmpty:
|
||||||
break
|
break
|
||||||
|
|
||||||
if buffered_count > 0:
|
if buffered_count > 0:
|
||||||
logger.info(f"Drained {buffered_count} buffered events for task {task_id}")
|
logger.debug(f"Drained {buffered_count} buffered events for task {task_id}")
|
||||||
|
|
||||||
# 然后实时推送新事件
|
# 然后实时推送新事件
|
||||||
try:
|
try:
|
||||||
|
|
|
||||||
|
|
@ -147,3 +147,4 @@ class BaiduAdapter(BaseLLMAdapter):
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -85,3 +85,4 @@ class DoubaoAdapter(BaseLLMAdapter):
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -88,3 +88,4 @@ class MinimaxAdapter(BaseLLMAdapter):
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -136,3 +136,4 @@ class BaseLLMAdapter(ABC):
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -122,3 +122,4 @@ DEFAULT_BASE_URLS: Dict[LLMProvider, str] = {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -24,5 +24,5 @@ uv run alembic upgrade head
|
||||||
|
|
||||||
# 启动服务
|
# 启动服务
|
||||||
echo "✅ 启动后端服务..."
|
echo "✅ 启动后端服务..."
|
||||||
uv run uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload
|
uv run uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload --no-access-log
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -57,3 +57,4 @@ CMD ["serve", "-s", "dist", "-l", "3000"]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,3 +20,4 @@ export const ProtectedRoute = () => {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,6 @@ import AuditTasks from "@/pages/AuditTasks";
|
||||||
import TaskDetail from "@/pages/TaskDetail";
|
import TaskDetail from "@/pages/TaskDetail";
|
||||||
import AgentAudit from "@/pages/AgentAudit";
|
import AgentAudit from "@/pages/AgentAudit";
|
||||||
import AdminDashboard from "@/pages/AdminDashboard";
|
import AdminDashboard from "@/pages/AdminDashboard";
|
||||||
import LogsPage from "@/pages/LogsPage";
|
|
||||||
import Account from "@/pages/Account";
|
import Account from "@/pages/Account";
|
||||||
import AuditRules from "@/pages/AuditRules";
|
import AuditRules from "@/pages/AuditRules";
|
||||||
import PromptManager from "@/pages/PromptManager";
|
import PromptManager from "@/pages/PromptManager";
|
||||||
|
|
@ -93,12 +92,6 @@ const routes: RouteConfig[] = [
|
||||||
element: <RecycleBin />,
|
element: <RecycleBin />,
|
||||||
visible: true,
|
visible: true,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: "系统日志",
|
|
||||||
path: "/logs",
|
|
||||||
element: <LogsPage />,
|
|
||||||
visible: true,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: "账号管理",
|
name: "账号管理",
|
||||||
path: "/account",
|
path: "/account",
|
||||||
|
|
|
||||||
|
|
@ -1,269 +0,0 @@
|
||||||
/**
|
|
||||||
* 日志查看器组件
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { useState, useMemo } from 'react';
|
|
||||||
import { logger, LogLevel, LogCategory, LogEntry } from '@/shared/utils/logger';
|
|
||||||
import { useLogs, useLogStats } from '@/shared/hooks/useLogger';
|
|
||||||
import { Button } from '@/components/ui/button';
|
|
||||||
import { Input } from '@/components/ui/input';
|
|
||||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
|
||||||
import { Badge } from '@/components/ui/badge';
|
|
||||||
import { Trash2, Search, RefreshCw, FileJson, FileSpreadsheet } from 'lucide-react';
|
|
||||||
import { toast } from 'sonner';
|
|
||||||
|
|
||||||
export function LogViewer() {
|
|
||||||
const [levelFilter, setLevelFilter] = useState<LogLevel | 'ALL'>('ALL');
|
|
||||||
const [categoryFilter, setCategoryFilter] = useState<LogCategory | 'ALL'>('ALL');
|
|
||||||
const [searchQuery, setSearchQuery] = useState('');
|
|
||||||
const [selectedLog, setSelectedLog] = useState<LogEntry | null>(null);
|
|
||||||
|
|
||||||
const filter = useMemo(() => ({
|
|
||||||
level: levelFilter !== 'ALL' ? levelFilter : undefined,
|
|
||||||
category: categoryFilter !== 'ALL' ? categoryFilter : undefined,
|
|
||||||
search: searchQuery || undefined,
|
|
||||||
}), [levelFilter, categoryFilter, searchQuery]);
|
|
||||||
|
|
||||||
const rawLogs = useLogs(filter);
|
|
||||||
// 反转日志顺序,最新的在最上面
|
|
||||||
const logs = useMemo(() => [...rawLogs].reverse(), [rawLogs]);
|
|
||||||
const stats = useLogStats();
|
|
||||||
|
|
||||||
const handleClearLogs = () => {
|
|
||||||
if (confirm('确定要清空所有日志吗?')) {
|
|
||||||
logger.clearLogs();
|
|
||||||
toast.success('日志已清空');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleDownloadJson = () => {
|
|
||||||
logger.downloadLogs('json');
|
|
||||||
toast.success('日志已导出为JSON');
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleDownloadCsv = () => {
|
|
||||||
logger.downloadLogs('csv');
|
|
||||||
toast.success('日志已导出为CSV');
|
|
||||||
};
|
|
||||||
|
|
||||||
const getLevelColor = (level: LogLevel) => {
|
|
||||||
const colors = {
|
|
||||||
[LogLevel.DEBUG]: 'bg-gray-500',
|
|
||||||
[LogLevel.INFO]: 'bg-blue-500',
|
|
||||||
[LogLevel.WARN]: 'bg-yellow-500',
|
|
||||||
[LogLevel.ERROR]: 'bg-red-500',
|
|
||||||
[LogLevel.FATAL]: 'bg-red-900',
|
|
||||||
};
|
|
||||||
return colors[level];
|
|
||||||
};
|
|
||||||
|
|
||||||
const getCategoryColor = (category: LogCategory) => {
|
|
||||||
const colors = {
|
|
||||||
[LogCategory.USER_ACTION]: 'bg-green-500',
|
|
||||||
[LogCategory.API_CALL]: 'bg-purple-500',
|
|
||||||
[LogCategory.SYSTEM]: 'bg-blue-500',
|
|
||||||
[LogCategory.CONSOLE_ERROR]: 'bg-red-500',
|
|
||||||
};
|
|
||||||
return colors[category];
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="flex h-full flex-col gap-4 p-4">
|
|
||||||
{/* 统计信息 */}
|
|
||||||
<div className="grid grid-cols-2 gap-4 md:grid-cols-4">
|
|
||||||
<div className="rounded-lg border bg-card p-4">
|
|
||||||
<div className="text-sm text-muted-foreground">总日志数</div>
|
|
||||||
<div className="text-2xl font-bold">{stats.total}</div>
|
|
||||||
</div>
|
|
||||||
<div className="rounded-lg border bg-card p-4">
|
|
||||||
<div className="text-sm text-muted-foreground">错误数</div>
|
|
||||||
<div className="text-2xl font-bold text-red-500">{stats.errors}</div>
|
|
||||||
</div>
|
|
||||||
<div className="rounded-lg border bg-card p-4">
|
|
||||||
<div className="text-sm text-muted-foreground">当前显示</div>
|
|
||||||
<div className="text-2xl font-bold">{logs.length}</div>
|
|
||||||
</div>
|
|
||||||
<div className="rounded-lg border bg-card p-4">
|
|
||||||
<div className="text-sm text-muted-foreground">最新日志</div>
|
|
||||||
<div className="text-sm">
|
|
||||||
{logs.length > 0 ? new Date(logs[logs.length - 1].timestamp).toLocaleTimeString() : '-'}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* 工具栏 */}
|
|
||||||
<div className="flex flex-wrap items-center gap-2">
|
|
||||||
<div className="relative flex-1 min-w-[200px]">
|
|
||||||
<Search className="absolute left-2 top-2.5 h-4 w-4 text-muted-foreground" />
|
|
||||||
<Input
|
|
||||||
placeholder="搜索日志..."
|
|
||||||
value={searchQuery}
|
|
||||||
onChange={(e) => setSearchQuery(e.target.value)}
|
|
||||||
className="pl-8"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Select value={levelFilter} onValueChange={(v) => setLevelFilter(v as any)}>
|
|
||||||
<SelectTrigger className="w-32">
|
|
||||||
<SelectValue placeholder="所有级别" />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
<SelectItem value="ALL">所有级别</SelectItem>
|
|
||||||
{Object.values(LogLevel).map(level => (
|
|
||||||
<SelectItem key={level} value={level}>{level}</SelectItem>
|
|
||||||
))}
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
|
|
||||||
<Select value={categoryFilter} onValueChange={(v) => setCategoryFilter(v as any)}>
|
|
||||||
<SelectTrigger className="w-40">
|
|
||||||
<SelectValue placeholder="所有分类" />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
<SelectItem value="ALL">所有分类</SelectItem>
|
|
||||||
{Object.values(LogCategory).map(category => (
|
|
||||||
<SelectItem key={category} value={category}>{category}</SelectItem>
|
|
||||||
))}
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
|
|
||||||
<Button variant="outline" size="icon" onClick={() => window.location.reload()} title="刷新页面">
|
|
||||||
<RefreshCw className="h-4 w-4" />
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
<Button variant="outline" size="icon" onClick={handleDownloadJson} title="导出为JSON格式">
|
|
||||||
<FileJson className="h-4 w-4" />
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
<Button variant="outline" size="icon" onClick={handleDownloadCsv} title="导出为CSV格式">
|
|
||||||
<FileSpreadsheet className="h-4 w-4" />
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
<Button variant="destructive" size="icon" onClick={handleClearLogs} title="清空日志">
|
|
||||||
<Trash2 className="h-4 w-4" />
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* 日志列表 */}
|
|
||||||
<div className="flex flex-1 flex-col gap-4 overflow-hidden lg:flex-row">
|
|
||||||
<div className="flex-1 flex flex-col rounded-lg border bg-card overflow-hidden">
|
|
||||||
<div className="flex-1 overflow-auto">
|
|
||||||
<div className="p-2">
|
|
||||||
{logs.length === 0 ? (
|
|
||||||
<div className="flex h-40 items-center justify-center text-muted-foreground">
|
|
||||||
没有找到日志
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
logs.map((log) => (
|
|
||||||
<div
|
|
||||||
key={log.id}
|
|
||||||
className={`mb-2 rounded-lg border p-3 transition-colors ${selectedLog?.id === log.id ? 'bg-accent' : ''
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
<div className="flex flex-wrap items-start justify-between gap-2">
|
|
||||||
<div className="flex flex-wrap items-start gap-2">
|
|
||||||
<Badge className={`${getLevelColor(log.level)} text-white`}>
|
|
||||||
{log.level}
|
|
||||||
</Badge>
|
|
||||||
<Badge className={`${getCategoryColor(log.category)} text-white`}>
|
|
||||||
{log.category}
|
|
||||||
</Badge>
|
|
||||||
<span className="text-xs text-muted-foreground">
|
|
||||||
{new Date(log.timestamp).toLocaleString()}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
size="sm"
|
|
||||||
onClick={() => setSelectedLog(log)}
|
|
||||||
className="h-7 text-xs"
|
|
||||||
>
|
|
||||||
详情
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
<div className="mt-2 text-sm line-clamp-2">{log.message}</div>
|
|
||||||
</div>
|
|
||||||
))
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* 日志详情 */}
|
|
||||||
{selectedLog && (
|
|
||||||
<div className="flex w-full flex-col rounded-lg border bg-card lg:w-[600px] overflow-hidden">
|
|
||||||
<div className="flex items-center justify-between border-b p-4 flex-shrink-0">
|
|
||||||
<h3 className="text-lg font-semibold">日志详情</h3>
|
|
||||||
<Button
|
|
||||||
variant="ghost"
|
|
||||||
size="sm"
|
|
||||||
onClick={() => setSelectedLog(null)}
|
|
||||||
className="h-8 w-8 p-0"
|
|
||||||
>
|
|
||||||
✕
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
<div className="flex-1 overflow-auto">
|
|
||||||
<div className="p-4 space-y-4 text-sm">
|
|
||||||
<div>
|
|
||||||
<div className="mb-1 font-medium text-muted-foreground">ID</div>
|
|
||||||
<div className="rounded bg-muted p-2 font-mono text-xs break-all">{selectedLog.id}</div>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<div className="mb-1 font-medium text-muted-foreground">时间</div>
|
|
||||||
<div className="rounded bg-muted p-2">{new Date(selectedLog.timestamp).toLocaleString()}</div>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<div className="mb-1 font-medium text-muted-foreground">级别</div>
|
|
||||||
<Badge className={`${getLevelColor(selectedLog.level)} text-white`}>
|
|
||||||
{selectedLog.level}
|
|
||||||
</Badge>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<div className="mb-1 font-medium text-muted-foreground">分类</div>
|
|
||||||
<Badge className={`${getCategoryColor(selectedLog.category)} text-white`}>
|
|
||||||
{selectedLog.category}
|
|
||||||
</Badge>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<div className="mb-1 font-medium text-muted-foreground">消息</div>
|
|
||||||
<div className="rounded bg-muted p-3 whitespace-pre-wrap break-words overflow-auto max-h-96">
|
|
||||||
{selectedLog.message}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{selectedLog.data && (
|
|
||||||
<div>
|
|
||||||
<div className="mb-1 font-medium text-muted-foreground">数据</div>
|
|
||||||
<pre className="overflow-auto rounded bg-muted p-3 text-xs max-h-96 whitespace-pre-wrap break-words">
|
|
||||||
{JSON.stringify(selectedLog.data, null, 2)}
|
|
||||||
</pre>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{selectedLog.stack && (
|
|
||||||
<div>
|
|
||||||
<div className="mb-1 font-medium text-muted-foreground">堆栈跟踪</div>
|
|
||||||
<pre className="overflow-auto rounded bg-muted p-3 text-xs max-h-96 whitespace-pre-wrap break-words">
|
|
||||||
{selectedLog.stack}
|
|
||||||
</pre>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{selectedLog.url && (
|
|
||||||
<div>
|
|
||||||
<div className="mb-1 font-medium text-muted-foreground">URL</div>
|
|
||||||
<div className="rounded bg-muted p-3 break-all text-xs">{selectedLog.url}</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{selectedLog.userAgent && (
|
|
||||||
<div>
|
|
||||||
<div className="mb-1 font-medium text-muted-foreground">User Agent</div>
|
|
||||||
<div className="rounded bg-muted p-3 break-all text-xs">{selectedLog.userAgent}</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -10,7 +10,6 @@ import {
|
||||||
ListTodo,
|
ListTodo,
|
||||||
Settings,
|
Settings,
|
||||||
Trash2,
|
Trash2,
|
||||||
FileText,
|
|
||||||
ChevronLeft,
|
ChevronLeft,
|
||||||
ChevronRight,
|
ChevronRight,
|
||||||
Github,
|
Github,
|
||||||
|
|
@ -33,7 +32,6 @@ const routeIcons: Record<string, React.ReactNode> = {
|
||||||
"/prompts": <MessageSquare className="w-5 h-5" />,
|
"/prompts": <MessageSquare className="w-5 h-5" />,
|
||||||
"/admin": <Settings className="w-5 h-5" />,
|
"/admin": <Settings className="w-5 h-5" />,
|
||||||
"/recycle-bin": <Trash2 className="w-5 h-5" />,
|
"/recycle-bin": <Trash2 className="w-5 h-5" />,
|
||||||
"/logs": <FileText className="w-5 h-5" />,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
interface SidebarProps {
|
interface SidebarProps {
|
||||||
|
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
/**
|
|
||||||
* 日志查看页面
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { LogViewer } from '@/components/debug/LogViewer';
|
|
||||||
|
|
||||||
export default function LogsPage() {
|
|
||||||
return (
|
|
||||||
<div className="h-screen">
|
|
||||||
<LogViewer />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
Loading…
Reference in New Issue