From 59b8e7b44f97396a37640a0f28218b05563e268e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=96=87=E8=96=87=E5=AE=89?= Date: Sun, 18 Jan 2026 21:37:11 +0800 Subject: [PATCH] a --- backend/api/main.py | 10 ++++ backend/api/routes/system.py | 70 ++++++++++++++++++++++++++ frontend/src/components/LogMonitor.jsx | 13 +++++ frontend/src/services/api.js | 12 +++++ trading_system/main.py | 25 +++++++-- 5 files changed, 127 insertions(+), 3 deletions(-) diff --git a/backend/api/main.py b/backend/api/main.py index 21b6b05..41db2a7 100644 --- a/backend/api/main.py +++ b/backend/api/main.py @@ -114,6 +114,16 @@ def setup_logging(): # 让 handler 自己按组筛选(error/warning/info),这里只需要放宽到 INFO redis_handler.setLevel(logging.INFO) root_logger.addHandler(redis_handler) + + # 诊断:启动时快速检测一次 Redis 可用性(失败不影响运行) + try: + client = redis_handler._get_redis() # noqa: SLF001(仅用于诊断) + if client is None: + logging.getLogger(__name__).warning(f"⚠ Redis 日志写入未启用。REDIS_URL={redis_url}") + else: + logging.getLogger(__name__).info(f"✓ Redis 日志写入已启用。REDIS_URL={redis_url}") + except Exception: + pass except Exception: pass diff --git a/backend/api/routes/system.py b/backend/api/routes/system.py index 658e52c..774be68 100644 --- a/backend/api/routes/system.py +++ b/backend/api/routes/system.py @@ -125,6 +125,76 @@ class LogsConfigUpdate(BaseModel): include_debug_in_info: Optional[bool] = None +def _beijing_time_str() -> str: + from datetime import datetime, timezone, timedelta + + beijing_tz = timezone(timedelta(hours=8)) + return datetime.now(tz=beijing_tz).strftime("%Y-%m-%d %H:%M:%S") + + +@router.post("/logs/test-write") +async def logs_test_write( + x_admin_token: Optional[str] = Header(default=None, alias="X-Admin-Token"), +) -> Dict[str, Any]: + """ + 写入 3 条测试日志到 Redis(error/warning/info),用于验证“是否写入到同一台 Redis、同一组 key”。 + """ + _require_admin(os.getenv("SYSTEM_CONTROL_TOKEN", "").strip(), x_admin_token) + + client = _get_redis_client_for_logs() + if client is None: + raise HTTPException(status_code=503, detail="Redis 不可用,无法写入测试日志") + + cfg = _read_logs_config(client) + now_ms = int(__import__("time").time() * 1000) + time_str = _beijing_time_str() + + entries = { + "error": {"level": "ERROR", "message": f"[TEST] backend 写入 error 测试日志 @ {time_str}"}, + "warning": {"level": "WARNING", "message": f"[TEST] backend 写入 warning 测试日志 @ {time_str}"}, + "info": {"level": "INFO", "message": f"[TEST] backend 写入 info 测试日志 @ {time_str}"}, + } + + day = _beijing_yyyymmdd() + stats_prefix = _logs_stats_prefix() + + pipe = client.pipeline() + for g in LOG_GROUPS: + key = _logs_key_for_group(g) + max_len = int(cfg["max_len"][g]) + entry = { + "ts": now_ms, + "time": time_str, + "service": "backend", + "level": entries[g]["level"], + "logger": "api.routes.system", + "message": entries[g]["message"], + "hostname": os.getenv("HOSTNAME", ""), + "signature": f"backend|{entries[g]['level']}|test|{entries[g]['message']}", + "count": 1, + } + pipe.lpush(key, json.dumps(entry, ensure_ascii=False)) + if max_len > 0: + pipe.ltrim(key, 0, max_len - 1) + pipe.incr(f"{stats_prefix}:{day}:{g}", 1) + pipe.expire(f"{stats_prefix}:{day}:{g}", 14 * 24 * 3600) + + pipe.execute() + + # 返回写入后的 LLEN,便于你确认 + pipe2 = client.pipeline() + for g in LOG_GROUPS: + pipe2.llen(_logs_key_for_group(g)) + llens = pipe2.execute() + + return { + "message": "ok", + "keys": {g: _logs_key_for_group(g) for g in LOG_GROUPS}, + "llen": {g: int(llens[i] or 0) for i, g in enumerate(LOG_GROUPS)}, + "note": "如果你在前端仍看不到,说明前端请求的后端实例/Redis key/筛选条件不一致。", + } + + def _get_redis_client_for_logs(): """ 获取 Redis 客户端(优先复用 config_manager 的连接;失败则自行创建)。 diff --git a/frontend/src/components/LogMonitor.jsx b/frontend/src/components/LogMonitor.jsx index 6bb42a1..c601235 100644 --- a/frontend/src/components/LogMonitor.jsx +++ b/frontend/src/components/LogMonitor.jsx @@ -113,6 +113,16 @@ export default function LogMonitor() { } } + const writeTest = async () => { + setError('') + try { + await api.writeLogsTest() + await load() + } catch (e) { + setError(e?.message || '写入测试日志失败') + } + } + const goFirstPage = () => setPageStart(0) const goNextPage = () => setPageStart(pageMeta?.next_start || 0) const goPrevPage = () => setPageStart(Math.max(0, pageStart - Number(limit || 200))) @@ -130,6 +140,9 @@ export default function LogMonitor() { + diff --git a/frontend/src/services/api.js b/frontend/src/services/api.js index 9c32137..f7999c1 100644 --- a/frontend/src/services/api.js +++ b/frontend/src/services/api.js @@ -331,4 +331,16 @@ export const api = { } return response.json(); }, + + writeLogsTest: async () => { + const response = await fetch(buildUrl('/api/system/logs/test-write'), { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + }); + if (!response.ok) { + const error = await response.json().catch(() => ({ detail: '写入测试日志失败' })); + throw new Error(error.detail || '写入测试日志失败'); + } + return response.json(); + }, }; diff --git a/trading_system/main.py b/trading_system/main.py index 28b45fa..77ebae8 100644 --- a/trading_system/main.py +++ b/trading_system/main.py @@ -65,10 +65,13 @@ logging.basicConfig( # 追加:将 ERROR 日志写入 Redis(不影响现有文件/控制台日志) try: - if __name__ == '__main__': - from redis_log_handler import RedisErrorLogHandler, RedisLogConfig - else: + # 兼容两种启动方式: + # - 直接运行:python trading_system/main.py + # - 模块运行:python -m trading_system.main + try: from .redis_log_handler import RedisErrorLogHandler, RedisLogConfig + except Exception: + from redis_log_handler import RedisErrorLogHandler, RedisLogConfig redis_cfg = RedisLogConfig( redis_url=getattr(config, "REDIS_URL", "redis://localhost:6379"), @@ -83,6 +86,22 @@ try: # 让 handler 自己按组筛选(error/warning/info),这里只需要放宽到 INFO redis_handler.setLevel(logging.INFO) logging.getLogger().addHandler(redis_handler) + + # 诊断:启动时快速检测一次 Redis 可用性(失败不影响运行) + try: + client = redis_handler._get_redis() # noqa: SLF001(仅用于诊断) + if client is None: + logger = logging.getLogger(__name__) + logger.warning( + f"⚠ Redis 日志写入未启用(无法连接或缺少依赖)。REDIS_URL={getattr(config, 'REDIS_URL', None)}" + ) + else: + logger = logging.getLogger(__name__) + logger.info( + f"✓ Redis 日志写入已启用。REDIS_URL={getattr(config, 'REDIS_URL', None)}" + ) + except Exception: + pass except Exception: # Redis handler 仅用于增强监控,失败不影响交易系统启动 pass