跳转至

日志系统

日志架构

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.pygateway/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 进行日志轮转:
/var/log/janpnp/*.log {
    daily
    rotate 30
    compress
    delaycompress
    missingok
    notifempty
    copytruncate
}

固件审计日志

固件发布和激活操作额外记录到 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 格式,每行一个记录,便于 grepjq 和日志采集系统处理。

日志使用建议

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