This commit is contained in:
薇薇安 2026-01-18 21:37:11 +08:00
parent 79526151f3
commit 59b8e7b44f
5 changed files with 127 additions and 3 deletions

View File

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

View File

@ -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 条测试日志到 Rediserror/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 的连接失败则自行创建

View File

@ -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() {
<button className="btn" onClick={load} disabled={loading}>
刷新
</button>
<button className="btn" onClick={writeTest} disabled={loading}>
写入测试日志
</button>
</div>
</div>

View File

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

View File

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