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:
lintsinghua 2025-12-12 15:50:48 +08:00
parent f05c0073e1
commit eed111c04d
33 changed files with 60 additions and 314 deletions

View File

@ -61,3 +61,4 @@ CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]

View File

@ -105,3 +105,4 @@ datefmt = %H:%M:%S

View File

@ -92,3 +92,4 @@ else:

View File

@ -27,3 +27,4 @@ def downgrade() -> None:

View File

@ -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 = []

View File

@ -212,3 +212,4 @@ async def remove_project_member(

View File

@ -227,3 +227,4 @@ async def toggle_user_status(

View File

@ -31,3 +31,4 @@ def get_password_hash(password: str) -> str:

View File

@ -14,3 +14,4 @@ class Base:

View File

@ -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():

View File

@ -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):

View File

@ -26,3 +26,4 @@ class InstantAnalysis(Base):

View File

@ -27,3 +27,4 @@ class User(Base):

View File

@ -32,3 +32,4 @@ class UserConfig(Base):

View File

@ -12,3 +12,4 @@ class TokenPayload(BaseModel):

View File

@ -43,3 +43,4 @@ class UserListResponse(BaseModel):

View File

@ -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在调度时调用"""

View File

@ -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",

View File

@ -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:

View File

@ -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"""

View File

@ -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:

View File

@ -147,3 +147,4 @@ class BaiduAdapter(BaseLLMAdapter):

View File

@ -85,3 +85,4 @@ class DoubaoAdapter(BaseLLMAdapter):

View File

@ -88,3 +88,4 @@ class MinimaxAdapter(BaseLLMAdapter):

View File

@ -136,3 +136,4 @@ class BaseLLMAdapter(ABC):

View File

@ -122,3 +122,4 @@ DEFAULT_BASE_URLS: Dict[LLMProvider, str] = {

View File

@ -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

View File

@ -57,3 +57,4 @@ CMD ["serve", "-s", "dist", "-l", "3000"]

View File

@ -20,3 +20,4 @@ export const ProtectedRoute = () => {

View File

@ -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",

View File

@ -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>
);
}

View File

@ -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 {

View File

@ -1,13 +0,0 @@
/**
*
*/
import { LogViewer } from '@/components/debug/LogViewer';
export default function LogsPage() {
return (
<div className="h-screen">
<LogViewer />
</div>
);
}