From 3ce376793804c760b55113ae9a7890caad031143 Mon Sep 17 00:00:00 2001 From: vinland100 Date: Fri, 9 Jan 2026 13:23:49 +0800 Subject: [PATCH] Change the log time to China --- backend/app/core/timezone.py | 21 ++++++++++++ backend/app/main.py | 5 ++- backend/app/services/agent/event_manager.py | 7 ++-- .../agent/streaming/stream_handler.py | 4 +-- .../app/services/agent/telemetry/tracer.py | 34 +++++++++---------- backend/app/services/report_generator.py | 7 ++-- .../AgentAudit/hooks/useAgentAuditState.ts | 11 +++--- frontend/src/pages/AgentAudit/index.tsx | 13 +++++++ frontend/src/pages/AgentAudit/types.ts | 2 +- frontend/src/pages/AgentAudit/utils.ts | 21 +++++++++--- 10 files changed, 86 insertions(+), 39 deletions(-) create mode 100644 backend/app/core/timezone.py diff --git a/backend/app/core/timezone.py b/backend/app/core/timezone.py new file mode 100644 index 0000000..d858691 --- /dev/null +++ b/backend/app/core/timezone.py @@ -0,0 +1,21 @@ +from datetime import datetime, timedelta, timezone + +# 中国标准时间 (UTC+8) +CHINA_TZ = timezone(timedelta(hours=8)) + +def get_now(): + """获取当前的中国时间 (UTC+8)""" + return datetime.now(CHINA_TZ) + +def get_now_iso(): + """获取当前中国时间的 ISO 格式字符串""" + return get_now().isoformat() + +def beijing_time(*args): + """ + 用于 logging.Formatter.converter 的转换函数 + """ + if args: + # args[0] 是 timestamp + return datetime.fromtimestamp(args[0], CHINA_TZ).timetuple() + return get_now().timetuple() diff --git a/backend/app/main.py b/backend/app/main.py index 0f674ab..a4a791a 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -8,9 +8,12 @@ from app.api.v1.api import api_router from app.db.session import AsyncSessionLocal from app.db.init_db import init_db +from app.core.timezone import beijing_time + # 配置日志 +logging.Formatter.converter = beijing_time logging.basicConfig( - level=logging.INFO, + level=logging.DEBUG, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', datefmt='%Y-%m-%d %H:%M:%S' ) diff --git a/backend/app/services/agent/event_manager.py b/backend/app/services/agent/event_manager.py index c322c43..49ef772 100644 --- a/backend/app/services/agent/event_manager.py +++ b/backend/app/services/agent/event_manager.py @@ -7,7 +7,7 @@ import asyncio import json import logging from typing import Optional, Dict, Any, List, AsyncGenerator, Callable -from datetime import datetime, timezone +from app.core.timezone import get_now, get_now_iso from dataclasses import dataclass import uuid @@ -281,7 +281,7 @@ class EventManager: ): """添加事件""" event_id = str(uuid.uuid4()) - timestamp = datetime.now(timezone.utc) + timestamp = get_now() event_data = { "id": event_id, @@ -379,6 +379,7 @@ class EventManager: finding_id=event_data["finding_id"], tokens_used=event_data["tokens_used"], event_metadata=sanitize_dict(event_data["metadata"]), # 🔥 清理元数据 + created_at=get_now(), ) db.add(event) await db.commit() @@ -523,7 +524,7 @@ class EventManager: except asyncio.TimeoutError: # 发送心跳 - yield {"event_type": "heartbeat", "timestamp": datetime.now(timezone.utc).isoformat()} + yield {"event_type": "heartbeat", "timestamp": get_now_iso()} except GeneratorExit: # SSE 连接断开 diff --git a/backend/app/services/agent/streaming/stream_handler.py b/backend/app/services/agent/streaming/stream_handler.py index 2c818fb..4582b3d 100644 --- a/backend/app/services/agent/streaming/stream_handler.py +++ b/backend/app/services/agent/streaming/stream_handler.py @@ -8,7 +8,7 @@ import logging from enum import Enum from typing import Any, Dict, Optional, AsyncGenerator, List from dataclasses import dataclass, field -from datetime import datetime, timezone +from app.core.timezone import get_now_iso logger = logging.getLogger(__name__) @@ -68,7 +68,7 @@ class StreamEvent: """流式事件""" event_type: StreamEventType data: Dict[str, Any] = field(default_factory=dict) - timestamp: str = field(default_factory=lambda: datetime.now(timezone.utc).isoformat()) + timestamp: str = field(default_factory=get_now_iso) sequence: int = 0 # 可选字段 diff --git a/backend/app/services/agent/telemetry/tracer.py b/backend/app/services/agent/telemetry/tracer.py index 1cafead..91fa9aa 100644 --- a/backend/app/services/agent/telemetry/tracer.py +++ b/backend/app/services/agent/telemetry/tracer.py @@ -12,7 +12,7 @@ Tracer - 审计追踪器 import csv import json import logging -from datetime import datetime, timezone +from app.core.timezone import get_now, get_now_iso from pathlib import Path from typing import Any, Callable, Dict, List, Optional from uuid import uuid4 @@ -53,7 +53,7 @@ class Tracer: # 运行标识 self.run_name = run_name self.run_id = run_name or f"run-{uuid4().hex[:8]}" - self.start_time = datetime.now(timezone.utc).isoformat() + self.start_time = get_now_iso() self.end_time: Optional[str] = None # 追踪数据 @@ -148,8 +148,8 @@ class Tracer: "type": agent_type, "status": "running", "parent_id": parent_id, - "created_at": datetime.now(timezone.utc).isoformat(), - "updated_at": datetime.now(timezone.utc).isoformat(), + "created_at": get_now_iso(), + "updated_at": get_now_iso(), "tool_executions": [], "findings_count": 0, } @@ -166,13 +166,13 @@ class Tracer: """更新 Agent 状态""" if agent_id in self.agents: self.agents[agent_id]["status"] = status - self.agents[agent_id]["updated_at"] = datetime.now(timezone.utc).isoformat() + self.agents[agent_id]["updated_at"] = get_now_iso() if error_message: self.agents[agent_id]["error_message"] = error_message if status in ["completed", "failed", "stopped"]: - self.agents[agent_id]["finished_at"] = datetime.now(timezone.utc).isoformat() + self.agents[agent_id]["finished_at"] = get_now_iso() # 触发回调 if self.agent_status_callback: @@ -193,7 +193,7 @@ class Tracer: execution_id = self._next_execution_id self._next_execution_id += 1 - now = datetime.now(timezone.utc).isoformat() + now = get_now_iso() # 清理过大的参数 cleaned_args = self._clean_args(args) @@ -226,7 +226,7 @@ class Tracer: """更新工具执行状态""" if execution_id in self.tool_executions: self.tool_executions[execution_id]["status"] = status - self.tool_executions[execution_id]["completed_at"] = datetime.now(timezone.utc).isoformat() + self.tool_executions[execution_id]["completed_at"] = get_now_iso() # 清理过大的结果 if result is not None: @@ -295,7 +295,7 @@ class Tracer: "content": content, "role": role, "agent_id": agent_id, - "timestamp": datetime.now(timezone.utc).isoformat(), + "timestamp": get_now_iso(), "metadata": metadata or {}, } @@ -324,7 +324,7 @@ class Tracer: "vulnerability_type": vulnerability_type, "file_path": file_path, "agent_id": agent_id, - "timestamp": datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%S UTC"), + "timestamp": get_now().strftime("%Y-%m-%d %H:%M:%S") + " CST", } self.vulnerability_reports.append(report) @@ -365,12 +365,12 @@ class Tracer: "scan_completed": True, "content": content, "success": success, - "completed_at": datetime.now(timezone.utc).isoformat(), + "completed_at": get_now_iso(), "total_vulnerabilities": len(self.vulnerability_reports), } self.run_metadata["status"] = "completed" if success else "failed" - self.end_time = datetime.now(timezone.utc).isoformat() + self.end_time = get_now_iso() self.run_metadata["end_time"] = self.end_time logger.info(f"Tracer: Final scan result set, success={success}") @@ -386,7 +386,7 @@ class Tracer: run_dir = self.get_run_dir() if mark_complete: - self.end_time = datetime.now(timezone.utc).isoformat() + self.end_time = get_now_iso() self.run_metadata["end_time"] = self.end_time # 保存最终报告 @@ -410,7 +410,7 @@ class Tracer: with report_file.open("w", encoding="utf-8") as f: f.write("# 安全审计报告\n\n") - f.write(f"**生成时间:** {datetime.now(timezone.utc).strftime('%Y-%m-%d %H:%M:%S UTC')}\n") + f.write(f"**生成时间:** {get_now().strftime('%Y-%m-%d %H:%M:%S')} CST\n") f.write(f"**运行ID:** {self.run_id}\n\n") # 统计信息 @@ -518,11 +518,11 @@ class Tracer: def _calculate_duration(self) -> float: """计算运行时长""" try: - start = datetime.fromisoformat(self.start_time.replace("Z", "+00:00")) + start = datetime.fromisoformat(self.start_time) if self.end_time: - end = datetime.fromisoformat(self.end_time.replace("Z", "+00:00")) + end = datetime.fromisoformat(self.end_time) else: - end = datetime.now(timezone.utc) + end = get_now() return (end - start).total_seconds() except (ValueError, TypeError): return 0.0 diff --git a/backend/app/services/report_generator.py b/backend/app/services/report_generator.py index 5a3826f..a8171c8 100644 --- a/backend/app/services/report_generator.py +++ b/backend/app/services/report_generator.py @@ -5,6 +5,7 @@ PDF 报告生成服务 - 专业审计版 (WeasyPrint) import io import html from datetime import datetime +from app.core.timezone import get_now from typing import List, Dict, Any import math import os @@ -475,8 +476,8 @@ class ReportGenerator: context = { 'title': '代码审计报告', 'subtitle': f'即时分析 | 语言: {language.capitalize()}', - 'generated_at': datetime.now().strftime('%Y-%m-%d %H:%M:%S'), - 'report_id': f"INST-{int(datetime.now().timestamp())}", + 'generated_at': get_now().strftime('%Y-%m-%d %H:%M:%S'), + 'report_id': f"INST-{int(get_now().timestamp())}", 'score': score, 'stats': [ ('问题总数', len(issues)), @@ -493,7 +494,7 @@ class ReportGenerator: context = { 'title': '项目代码审计报告', 'subtitle': f"项目: {project} | 分支: {task.get('branch_name', 'default')}", - 'generated_at': datetime.now().strftime('%Y-%m-%d %H:%M:%S'), + 'generated_at': get_now().strftime('%Y-%m-%d %H:%M:%S'), 'report_id': f"TASK-{task.get('id', '')[:8]}", 'score': score, 'stats': [ diff --git a/frontend/src/pages/AgentAudit/hooks/useAgentAuditState.ts b/frontend/src/pages/AgentAudit/hooks/useAgentAuditState.ts index 8de5d44..30f7cf0 100644 --- a/frontend/src/pages/AgentAudit/hooks/useAgentAuditState.ts +++ b/frontend/src/pages/AgentAudit/hooks/useAgentAuditState.ts @@ -59,10 +59,7 @@ function agentAuditReducer(state: AgentAuditState, action: AgentAuditAction): Ag return { ...state, logs: action.payload }; case 'ADD_LOG': { - const { id: providedId, ...logData } = action.payload; - const newLog = providedId - ? { ...createLogItem(logData), id: providedId } - : createLogItem(logData); + const newLog = createLogItem(action.payload); return { ...state, logs: [...state.logs, newLog] }; } @@ -194,11 +191,11 @@ export function useAgentAuditState() { dispatch({ type: 'SET_AGENT_TREE', payload: tree }); }, []); - const addLog = useCallback((log: Omit): string => { + const addLog = useCallback((log: Omit & { id?: string; time?: string }): string => { const newLog = createLogItem(log); - dispatch({ type: 'SET_LOGS', payload: [...state.logs, newLog] }); + dispatch({ type: 'ADD_LOG', payload: newLog }); return newLog.id; - }, [state.logs]); + }, []); const updateLog = useCallback((id: string, updates: Partial) => { dispatch({ type: 'UPDATE_LOG', payload: { id, updates } }); diff --git a/frontend/src/pages/AgentAudit/index.tsx b/frontend/src/pages/AgentAudit/index.tsx index 7cfe7ca..ff107f6 100644 --- a/frontend/src/pages/AgentAudit/index.tsx +++ b/frontend/src/pages/AgentAudit/index.tsx @@ -199,6 +199,7 @@ function AgentAuditPageContent() { title: event.message?.slice(0, 100) + (event.message && event.message.length > 100 ? '...' : '') || 'Thinking...', content: event.message || (event.metadata?.thought as string) || '', agentName, + time: event.timestamp, } }); processedCount++; @@ -217,6 +218,7 @@ function AgentAuditPageContent() { status: 'running' as const, }, agentName, + time: event.timestamp, } }); processedCount++; @@ -237,6 +239,7 @@ function AgentAuditPageContent() { status: 'completed' as const, }, agentName, + time: event.timestamp, } }); processedCount++; @@ -253,6 +256,7 @@ function AgentAuditPageContent() { title: event.message || (event.metadata?.title as string) || 'Vulnerability found', severity: (event.metadata?.severity as string) || 'medium', agentName, + time: event.timestamp, } }); processedCount++; @@ -271,6 +275,7 @@ function AgentAuditPageContent() { type: 'dispatch', title: event.message || `Event: ${event.event_type}`, agentName, + time: event.timestamp, } }); processedCount++; @@ -284,6 +289,7 @@ function AgentAuditPageContent() { type: 'info', title: event.message || 'Task completed', agentName, + time: event.timestamp, } }); processedCount++; @@ -297,6 +303,7 @@ function AgentAuditPageContent() { type: 'error', title: event.message || 'Task error', agentName, + time: event.timestamp, } }); processedCount++; @@ -310,6 +317,7 @@ function AgentAuditPageContent() { type: 'info', title: event.message || 'Task cancelled', agentName, + time: event.timestamp, } }); processedCount++; @@ -335,6 +343,7 @@ function AgentAuditPageContent() { progressKey: matchedProgress.key, title: event.message, agentName, + time: event.timestamp, } }); } else { @@ -344,6 +353,7 @@ function AgentAuditPageContent() { type: 'info', title: event.message, agentName, + time: event.timestamp, } }); } @@ -374,6 +384,7 @@ function AgentAuditPageContent() { progressKey: matchedProgress.key, title: message, agentName, + time: event.timestamp, } }); } else { @@ -383,6 +394,7 @@ function AgentAuditPageContent() { type: event.event_type === 'error' ? 'error' : 'info', title: message, agentName, + time: event.timestamp, } }); } @@ -406,6 +418,7 @@ function AgentAuditPageContent() { type: 'info', title: event.message, agentName, + time: event.timestamp, } }); processedCount++; diff --git a/frontend/src/pages/AgentAudit/types.ts b/frontend/src/pages/AgentAudit/types.ts index d04562c..1ab9ae4 100644 --- a/frontend/src/pages/AgentAudit/types.ts +++ b/frontend/src/pages/AgentAudit/types.ts @@ -76,7 +76,7 @@ export type AgentAuditAction = | { type: 'ADD_FINDING'; payload: Partial & { id: string } } | { type: 'SET_AGENT_TREE'; payload: AgentTreeResponse } | { type: 'SET_LOGS'; payload: LogItem[] } - | { type: 'ADD_LOG'; payload: Omit & { id?: string } } + | { type: 'ADD_LOG'; payload: Omit & { id?: string; time?: string } } | { type: 'UPDATE_LOG'; payload: { id: string; updates: Partial } } | { type: 'UPDATE_OR_ADD_PROGRESS_LOG'; payload: { progressKey: string; title: string; agentName?: string } } | { type: 'COMPLETE_TOOL_LOG'; payload: { toolName: string; output: string; duration: number } } diff --git a/frontend/src/pages/AgentAudit/utils.ts b/frontend/src/pages/AgentAudit/utils.ts index 5ce0f24..852b463 100644 --- a/frontend/src/pages/AgentAudit/utils.ts +++ b/frontend/src/pages/AgentAudit/utils.ts @@ -73,7 +73,18 @@ export function resetLogIdCounter(): void { * Get current time string for logs */ export function getTimeString(): string { - return new Date().toLocaleTimeString('en-US', { + return formatTimestamp(new Date()); +} + +/** + * Format timestamp (date or string) to HH:mm:ss + */ +export function formatTimestamp(time?: string | Date | number): string { + if (!time) return getTimeString(); + const date = new Date(time); + if (isNaN(date.getTime())) return getTimeString(); + + return date.toLocaleTimeString('en-US', { hour12: false, hour: '2-digit', minute: '2-digit', @@ -84,12 +95,12 @@ export function getTimeString(): string { /** * Create a log item */ -export function createLogItem(item: Omit): LogItem { +export function createLogItem(item: Omit & { id?: string; time?: string }): LogItem { return { ...item, - id: generateLogId(), - time: getTimeString(), - }; + id: item.id || generateLogId(), + time: item.time ? formatTimestamp(item.time) : getTimeString(), + } as LogItem; } /**