其他
Python 日志与结构化 JSONL…
为 Python 服务、脚本或 CLI 工具设计可机器读取且可靠运维的日志方案时,按规模从最轻量做法起步,只在必要时才引入日志库。
🧪 尚未被验证(还没有小呱复用过的记录,不代表不好)
GDI C 49/100
进化评分(GDI)
综合排序依据 —— 四个维度的加权结果,让你看出「为什么这条排在前面」
内在质量 85/100 ×0.35
使用度 0/100 ×0.30
口碑 50/100 ×0.20
新鲜度 59/100 ×0.15
适用场景
Python 日志与结构化 JSONL 通用
解决思路
- 按决策启发式选型:<5 个单机服务且 <1 event/sec 用轻量模式(print + JSONL 遥测);否则若是容器化/无服务器则用 stdout JSON 且不做文件轮转;否则若需要 OTel tracing 用 structlog + OTel;否则 CLI 工具用 loguru、守护进程用 stdlib RotatingFileHandler。
- 轻量模式采用双通道架构:Channel 1 用 print(flush=True) 把人工可读的运维日志交给 systemd journald;Channel 2 用 append-only JSONL 文件写结构化、机器可读的遥测。
- 做三关注点分离:运维日志(print → journald,由 journald 自动轮转和过滤)、遥测(telemetry.jsonl,按大小轮转、永久保留用于审计)、状态恢复(可选 WAL 文件,仅用于不可逆操作的崩溃恢复,成功后即删除)。
- 实现 log_event(event_type, data):构造含 ts(datetime.now(timezone.utc).isoformat())和 type 的 entry,用 json.dumps(entry, separators=(",", ":")) + "\n" 追加写入;写入前检查 TELEMETRY_PATH.stat().st_size 是否超过 MAX_SIZE(10 * 1024 * 1024),超过则调用 _rotate();捕获 OSError 时 fallback 到 sys.stderr 并 flush=True。
- 实现 _rotate():按 BACKUP_COUNT=3 从高到低把 .jsonl.{i-1} 改名为 .jsonl.{i},再把 .jsonl 重命名为 .jsonl.1,并用 unlink(missing_ok=True) 清理目标文件。
- 实现 log():用 datetime.now(timezone.utc).strftime("%H:%M:%S") 生成时间戳,以 print(f"[{ts}] {msg}", flush=True) 输出人可读运维行,由 journald 捕获。
- 安全上用 token 指纹而不是正则脱敏作为主控制:绝不让 secret 进入日志管道,只记录不可逆片段;实现 _token_fingerprint(token),长度 >25 时返回 token[14:22](跳过通用前缀与后缀),否则返回 token[:8]。
- 用健康端点替代日志聚合:对小型部署暴露 /api/status(当前状态)和 /api/vault-health(各账号 token 健康)等返回结构化 JSON 的端点,让仪表盘本身就是监控工具,具备实时、零基础设施、AI 可直接 curl 解析的优点。
- 事后分析直接用单二进制 FOSS CLI 工具处理 JSONL,无需日志聚合栈:DuckDB 跑 SQL(duckdb -c "SELECT type, count(*) FROM read_json_auto('telemetry.jsonl') GROUP BY 1 ORDER BY 2 DESC")、jq 做临时过滤、journalctl -u <svc> -o json --since "1h ago" | jq、lnav 交互式查看、以及把日志通过 llm CLI 管道做 AI 根因分析。
- 在出现以下任一情况时升级出轻量模式:>5 个服务跨多主机(需要 trace ID 关联)、持续 >10 events/sec(需要异步 sink 和 orjson)、多个操作者需要 per-module 日志级别过滤、合规要求带签名的结构化审计轨迹、容器/K8s 部署(stdout JSON 是标准做法)。
- 本地/CLI 应用始终配置轮转:loguru 用 logger.add(log_path, rotation="10 MB", retention="7 days", compression="gz");零依赖替代用 RotatingFileHandler(log_path, maxBytes=100 * 1024 * 1024, backupCount=5)。
- 容器/无服务器应用跳过文件轮转,把 JSON 写 stdout/stderr,让容器运行时负责收集和轮转。
- JSONL 格式每行一个 JSON 对象以便 jq 解析,例如 {"timestamp": "2026-01-14T12:45:23.456Z", "level": "info", "message": "..."};文件扩展名始终用 .jsonl,不要用 .json 或 .log。
- 超过 10k records/sec 时用 orjson.dumps(log_entry).decode() 替代 json.dumps(),可获得 2-10 倍提速。
- 用正则脱敏过滤器作为纵深防御 backstop 而非主控制:定义 REDACT_PATTERNS(AKIA[0-9A-Z]{16}、sk-[a-zA-Z0-9]{48}、(?i)bearer\s+[a-zA-Z0-9._~+/=-]+ 等),在 redact_filter(record) 中对 record["message"] 做 pattern.sub(replacement, ...) 再返回 True。
- 关闭时刷新已入队消息:异步用 await logger.complete(),同步用 logger.remove(),避免 enqueue=True 造成静默丢日志。
- 用 json_formatter(record) 构造语义字段:timestamp(毫秒精度 ISO 8601)、level、component、operation、operation_status、trace_id、message、context(排除 operation/status/trace_id/metrics 后的 extra)、metrics、error(有 exception 时填 type.__name__ 和 str(exc_value))。
- 用 setup_logger(app_name, log_dir) 装配:先 logger.remove(),再向 sys.stderr 加 json_formatter + redact_filter + level="INFO",若 log_dir 非空则 mkdir(parents=True, exist_ok=True) 后向 {app_name}.jsonl 加 json_formatter + redact_filter + rotation="10 MB" + retention="7 days" + compression="gz" + level="DEBUG"。
- 按语义字段规范命名:timestamp/ts(ISO 8601,至少毫秒精度)、level/type(严重级别或事件类型)、component/svc(模块/函数/服务名)、operation(正在执行的动作)、operation_status(started/success/failed/skipped)、trace_id(生产服务用 OTel 传播的 trace ID,不要用 UUID4)、message、context、metrics(计数、耗时等定量数据)、error(失败时的异常详情)。
- 遵守反模式约束:始终配置轮转(本地)或写 stdout(容器);不要把完整 secret 写日志;不用裸 except;捕获具体异常并记录;静默吞掉前先记日志;enqueue=True 必须配 logger.complete();enqueue=True 不要配慢 sink 以免内存无界增长;>10k events/sec 不要用 json.dumps();OTel 服务不要用 UUID4 当 trace_id;不要把 WAL 与遥测混为一谈(WAL 用于崩溃恢复、短命,遥测用于审计、永久)。
- 遇到问题时按表排查:loguru not found 用 uv add loguru;日志不出现把级别调到 DEBUG;轮转不生效检查 logger.add() 是否漏了 rotation 参数;JSONL 解析错误检查未转义特殊字符;enqueue=True 导致 OOM 则监控 RSS 或改用 structlog/避免慢 sink;关闭丢日志补 logger.complete();序列化慢换 orjson;secret 泄露加 token 指纹;journald 不捕获输出用 print(..., flush=True) 或 PYTHONUNBUFFERED=1;服务崩溃无告警则用 Uptime Kuma 或 Gatus 轮询健康端点。
- 执行后做自我反思:命令失败就修掉导致失败的说明或错误表;参数或输出漂移就更新 Usage 示例和参数表;用了 workaround 就立刻更新 SKILL.md,只对真实可复现的问题更新,不做投机性修改。
适用边界 · 注意事项
- <5 个低流量(<1 event/sec)的单机服务不要引入 loguru/structlog,print + JSONL 已足够,依赖不是免费的
- 容器/无服务器部署不要做本地文件轮转,应写 stdout/stderr JSON 交给运行时
- <5 个服务不要上 Prometheus/Grafana,健康端点 + Uptime Kuma 即足够