日志系统
日志架构
flowchart LR
App["Python logging 模块"] --> Stream["StreamHandler<br/>stdout / stderr"]
App --> File["FileHandler<br/>cloud_server.log"]
App --> Audit["固件审计日志<br/>audit.jsonl"]
日志配置在 server.py 启动时统一初始化:
logging.basicConfig(
level=logging.INFO,
format="[%(asctime)s.%(msecs)03d] %(levelname)s %(message)s",
datefmt="%Y-%m-%d %H:%M:%S",
handlers=[logging.StreamHandler(), logging.FileHandler(config.LOG_FILE, mode="a", encoding="utf-8")],
)
Logger 命名
| Logger | 文件 | 用途 |
|---|---|---|
cloud |
gateway/hub.py、gateway/proxy.py |
核心网关事件 |
cloud.firmware |
routes/api_firmware.py |
固件发布管理 |
子 logger 继承父 logger 的配置。
输出格式
[2026-07-27 14:30:00.123] INFO Cloud API 启动
[2026-07-27 14:30:01.456] INFO [AUTH] 设备注册: janpnp-001 (MyHost@192.168.1.10)
[2026-07-27 14:30:02.789] INFO [WS] 设备连接: janpnp-001
[2026-07-27 14:30:05.234] WARNING [PROXY] 超时 janpnp-001: GET /hardware/position
[2026-07-27 14:30:10.567] ERROR [WS] 异常 [janpnp-001]: connection closed
- 时间戳精确到毫秒,时区为系统本地时
- 日志等级固定宽度为 5 字符
[...]内为模块标签,便于 grep 过滤
日志等级
| 等级 | 用途 | 示例 |
|---|---|---|
| INFO | 正常操作事件 | 启动、设备连接/断开、注册、路由注册 |
| WARNING | 非致命异常 | 代理超时、设备心跳过期、固件不可用 |
| ERROR | 操作失败 | 设备异常断开、配置文件错误、固件验证失败 |
| EXCEPTION | 未预期异常 | 捕获的 Exception 完整堆栈 |
模块标签
| 标签 | 位置 | 使用场景 |
|---|---|---|
[WS] |
hub.py |
WebSocket 连接生命周期 |
[AUTH] |
hub.py |
设备注册与 Token 校验 |
[PROXY] |
proxy.py |
请求代理转发与超时 |
[FW] |
api_firmware.py |
固件清单和镜像状态 |
[FW-ADMIN] |
api_firmware.py |
管理员固件操作审计 |
日志文件
- 路径由
CLOUD_LOG_DIR控制,默认在 API 目录下 - 文件名固定为
cloud_server.log - 使用追加模式(
mode="a"),不自动轮转 - 生产环境建议配置 logrotate 进行日志轮转:
固件审计日志
固件发布和激活操作额外记录到 firmware/slave/audit.jsonl:
{"at":"2026-07-27T14:30:00Z","action":"publish","user":"admin","client":"192.168.1.100","release_id":"260714000-1.26.7.r1","image_version":260714000,"sha256":"abc123..."}
{"at":"2026-07-27T14:31:00Z","action":"activate","user":"admin","client":"192.168.1.100","release_id":"260714000-1.26.7.r1","image_version":260714000,"sha256":"abc123..."}
文件为 JSON Lines 格式,每行一个记录,便于 grep、jq 和日志采集系统处理。
日志使用建议
import logging
logger = logging.getLogger("cloud")
logger.info("[AUTH] 设备注册: %s (%s@%s)", device_id, hostname, ip)
logger.warning("[PROXY] 超时 %s: %s %s", device_id, method, path)
logger.error("[WS] 异常 [%s]: %s", device_id, e)
logger.exception("[FW] manifest invalid client=%s", client) # 含完整堆栈
注意事项
- 不在日志中记录 JWT 令牌、设备 Token 或用户密码
- 固件审计日志不可删除或篡改(追加模式)
- 设备状态上报频率较高,仅在 INFO 级别记录概要事件
logging.exception()自动包含异常堆栈,不需要手动traceback.format_exc()