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)
|
||||
|
||||
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:
|
||||
# 使用 EventManager 的流式接口
|
||||
# 过滤选项
|
||||
|
|
@ -1146,7 +1146,7 @@ async def stream_agent_with_thinking(
|
|||
yield format_sse_event(err_data)
|
||||
|
||||
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)
|
||||
last_sequence = after_sequence
|
||||
poll_interval = 2.0 # 完成的任务轮询可以慢一点
|
||||
|
|
@ -1572,15 +1572,15 @@ async def get_agent_tree(
|
|||
|
||||
# 尝试从内存中获取 Agent 树(运行中的任务)
|
||||
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:
|
||||
from app.services.agent.core import agent_registry
|
||||
|
||||
tree = agent_registry.get_agent_tree()
|
||||
stats = agent_registry.get_statistics()
|
||||
logger.info(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] tree nodes={len(tree.get('nodes', {}))}, root={tree.get('root_agent_id')}")
|
||||
logger.debug(f"[AgentTree API] 节点详情: {list(tree.get('nodes', {}).keys())}")
|
||||
|
||||
# 构建节点列表
|
||||
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 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(
|
||||
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)
|
||||
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
|
||||
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:
|
||||
"""注册到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:
|
||||
logger.warning(f"[AgentTree] {self.config.name} 已注册,跳过 (id={self._agent_id})")
|
||||
logger.debug(f"[AgentTree] {self.config.name} 已注册,跳过 (id={self._agent_id})")
|
||||
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_id=self._agent_id,
|
||||
|
|
@ -335,7 +335,7 @@ class BaseAgent(ABC):
|
|||
self._registered = True
|
||||
|
||||
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:
|
||||
"""设置父Agent ID(在调度时调用)"""
|
||||
|
|
|
|||
|
|
@ -500,10 +500,21 @@ class OrchestratorAgent(BaseAgent):
|
|||
task = params.get("task", "")
|
||||
context = params.get("context", "")
|
||||
|
||||
logger.debug(f"[Orchestrator] _dispatch_agent 被调用: agent_name='{agent_name}', task='{task[:50]}...'")
|
||||
|
||||
# 🔥 尝试大小写不敏感匹配
|
||||
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:
|
||||
available = list(self.sub_agents.keys())
|
||||
logger.warning(f"[Orchestrator] Agent '{agent_name}' 不存在,可用: {available}")
|
||||
return f"错误: Agent '{agent_name}' 不存在。可用的 Agent: {available}"
|
||||
|
||||
# 🔥 检查是否重复调度同一个 Agent
|
||||
|
|
@ -524,11 +535,11 @@ class OrchestratorAgent(BaseAgent):
|
|||
self._dispatched_tasks[agent_name] = dispatch_count + 1
|
||||
|
||||
# 🔥 设置父 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)
|
||||
logger.info(f"[Orchestrator] 设置 parent_id 完成,准备注册 {agent_name}")
|
||||
logger.debug(f"[Orchestrator] 设置 parent_id 完成,准备注册 {agent_name}")
|
||||
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(
|
||||
"dispatch",
|
||||
|
|
|
|||
|
|
@ -267,7 +267,7 @@ class AgentStatePersistence:
|
|||
session.add(checkpoint)
|
||||
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
|
||||
|
||||
except Exception as e:
|
||||
|
|
|
|||
|
|
@ -77,8 +77,8 @@ class AgentRegistry:
|
|||
Returns:
|
||||
注册的节点信息
|
||||
"""
|
||||
logger.info(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] register_agent 被调用: {agent_name} (id={agent_id}, parent={parent_id})")
|
||||
logger.debug(f"[AgentRegistry] 当前节点数: {len(self._agent_graph['nodes'])}, 节点列表: {list(self._agent_graph['nodes'].keys())}")
|
||||
|
||||
with self._lock:
|
||||
node = {
|
||||
|
|
@ -124,8 +124,8 @@ class AgentRegistry:
|
|||
if parent_id is None and self._root_agent_id is None:
|
||||
self._root_agent_id = agent_id
|
||||
|
||||
logger.info(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] 注册完成: {agent_name} ({agent_id}), parent: {parent_id}")
|
||||
logger.debug(f"[AgentRegistry] 注册后节点数: {len(self._agent_graph['nodes'])}, 节点列表: {list(self._agent_graph['nodes'].keys())}")
|
||||
return node
|
||||
|
||||
def unregister_agent(self, agent_id: str) -> None:
|
||||
|
|
@ -145,7 +145,7 @@ class AgentRegistry:
|
|||
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 状态更新 ============
|
||||
|
||||
|
|
@ -287,7 +287,7 @@ class AgentRegistry:
|
|||
self._agent_messages.clear()
|
||||
self._running_agents.clear()
|
||||
self._root_agent_id = None
|
||||
logger.info("Agent registry cleared")
|
||||
logger.debug("Agent registry cleared")
|
||||
|
||||
def cleanup_finished_agents(self) -> int:
|
||||
"""清理已完成的Agent"""
|
||||
|
|
|
|||
|
|
@ -432,13 +432,13 @@ class EventManager:
|
|||
|
||||
# 检查是否是结束事件
|
||||
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
|
||||
except asyncio.QueueEmpty:
|
||||
break
|
||||
|
||||
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:
|
||||
|
|
|
|||
|
|
@ -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 "✅ 启动后端服务..."
|
||||
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 AgentAudit from "@/pages/AgentAudit";
|
||||
import AdminDashboard from "@/pages/AdminDashboard";
|
||||
import LogsPage from "@/pages/LogsPage";
|
||||
import Account from "@/pages/Account";
|
||||
import AuditRules from "@/pages/AuditRules";
|
||||
import PromptManager from "@/pages/PromptManager";
|
||||
|
|
@ -93,12 +92,6 @@ const routes: RouteConfig[] = [
|
|||
element: <RecycleBin />,
|
||||
visible: true,
|
||||
},
|
||||
{
|
||||
name: "系统日志",
|
||||
path: "/logs",
|
||||
element: <LogsPage />,
|
||||
visible: true,
|
||||
},
|
||||
{
|
||||
name: "账号管理",
|
||||
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,
|
||||
Settings,
|
||||
Trash2,
|
||||
FileText,
|
||||
ChevronLeft,
|
||||
ChevronRight,
|
||||
Github,
|
||||
|
|
@ -33,7 +32,6 @@ const routeIcons: Record<string, React.ReactNode> = {
|
|||
"/prompts": <MessageSquare className="w-5 h-5" />,
|
||||
"/admin": <Settings className="w-5 h-5" />,
|
||||
"/recycle-bin": <Trash2 className="w-5 h-5" />,
|
||||
"/logs": <FileText className="w-5 h-5" />,
|
||||
};
|
||||
|
||||
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