>>>PyPathPython 学习站
首页›进阶篇›kp-037
进阶篇 · Advanced lv.3 进阶 kp-037

日志与配置

前置知识:kp-015、kp-021

1. 一句话定义

logging 是分层树形结构的分级日志系统(logger.info/warning/error),取代 print;配置遵循环境变量 > 配置文件 > 代码默认值的分层,秘密(密钥、密码)永远走环境变量。

2. 为什么重要

print 只能进 stdout 且无级别无结构,服务化后没法检索、没法分级关停;配置硬编码则是部署与安全的头号杀手。这两件小事决定程序"能不能进生产"。

3. 前置知识

kp-016(文件 IO)、kp-015(异常)。

4. 核心概念

python
import logging

logger = logging.getLogger(__name__)     # 每模块一个 logger,以模块路径命名

logger.debug("详细调试 %s", var)          # 惰性格式化:级别不够就不做字符串拼接
logger.info("请求完成", extra={"user_id": 42})
logger.warning("接近配额")
logger.error("失败", exc_info=True)      # 附加 traceback

  • 级别:DEBUG < INFO < WARNING < ERROR < CRITICAL;根 logger 默认 WARNING。
  • 树形传播:pkg.mod 的记录逐级上传到 pkg 与根,由根的 handler 决定输出——配置集中、使用分散。
  • 配置一次成型:

python
logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s %(levelname)s %(name)s %(message)s",
)

5. 原理与机制

配置分层(12-Factor 原则):

python
import os
from dataclasses import dataclass

@dataclass(frozen=True)
class Config:
    debug: bool = os.getenv("APP_DEBUG", "0") == "1"
    db_url: str = os.environ["DATABASE_URL"]      # 必填缺失即报错(fail fast)
    api_key: str = os.environ["API_KEY"]          # 秘密:只从环境来
# 可选叠加:.env 文件(python-dotenv)本地开发用;生产用真实环境变量

结构化日志(JSON 行)在服务端场景逐步替代纯文本:python-json-logger 或自定义 Formatter——机器可检索、可聚合。

6. 关键事实(模型/图示)

text
logger.getLogger("myapp.api")        ↓ 传播
logger.getLogger("myapp")            ↓ 传播
root logger  ──► handler(console/file/网络)──► formatter ──► 落地
异常处理黄金律:在"能处理"的层处理并 log;底层只 raise(kp-015)

7. 直观类比

logging 是医院分诊台:debug 是"内部会诊记录"(默认不给外人看)、info 是"日常门诊"、error 是"急诊铃"——分诊台(root handler)决定哪些级别广播到哪块屏幕。配置分层像房间的可调灯光:装修时给默认亮度(代码默认值),每层楼有总开关(配置文件),住户手里的开关(环境变量)优先级最高。

8. 实例与案例

python
# 库的正确姿势:只用 getLogger + 不配置 handler(配置权交给应用)
# 应用的姿势:入口处 basicConfig / dictConfig 一次成型

9. 常见误区

  1. 在生产用 print —— 无级别、无来源、无法关闭;统一切 logger。
  2. 每个文件都 basicConfig —— 它只对 root 生效且应只调一次;重复调用互相覆盖。
  3. 秘密进代码或配置文件提交 git —— 凭据只进环境变量/密管(Vault、云 KMS);.env 进 .gitignore。
  4. f-string 拼进日志消息 —— logger.debug(f"{heavy()}") 无论级别都会先求值;用 %s 惰性占位。

10. 自测题

  1. 为什么推荐 logging.getLogger(__name__)?
  2. logger.debug(f"{x}") 与 logger.debug("%s", x) 的性能差异根源?
  3. 秘密配置的三条纪律?
参考答案
  1. logger 名即模块路径,自动接入层级树:传播、按模块开关级别、按包配置都成立。
  2. f-string 版在调用前就完成拼接(含 heavy() 求值);%s 版仅当级别放行才格式化。
  3. 不进代码、不进仓库(.gitignore + 历史扫描)、只经环境变量或密管注入。

11. 与其他知识点的关系

  • kp-045 工程规范:日志规范与代码规范一起进 CI/评审。
  • kp-016 文件 IO:FileHandler/RotatingFileHandler 的底层。
  • kp-020 dataclass:Config 对象的现代写法。

12. 延伸阅读

  • logging Cookbook(官方文档中最实用的章节)
  • The Twelve-Factor App(Config 章节)