第六章 日志体系:logging 让排错效率翻倍

第六章 日志体系:logging 让排错效率翻倍

    • [0. 先把话说清楚:日志不是"多打几行字"](#0. 先把话说清楚:日志不是“多打几行字”)
    • [1. 为什么 print 调试会让你越做越慢](#1. 为什么 print 调试会让你越做越慢)
      • [1.1 print 的三大致命缺陷](#1.1 print 的三大致命缺陷)
    • [2. 先给你一个"可交付日志体系"的标准答案](#2. 先给你一个“可交付日志体系”的标准答案)
    • [3. logging 的正确姿势:别在每个文件里乱配](#3. logging 的正确姿势:别在每个文件里乱配)
    • [4. 落地实现:一套可直接复制的 logging 模板](#4. 落地实现:一套可直接复制的 logging 模板)
      • [4.1 `src/myproj/core/logging.py`](#4.1 src/myproj/core/logging.py)
    • [5. 在模块里写日志:三句话就够](#5. 在模块里写日志:三句话就够)
      • [5.1 模块写法模板](#5.1 模块写法模板)
      • [5.2 写日志的"信息密度原则"](#5.2 写日志的“信息密度原则”)
    • [6. 数据工程场景的"必打日志点"(建议照抄)](#6. 数据工程场景的“必打日志点”(建议照抄))
      • [6.1 数据读取阶段](#6.1 数据读取阶段)
      • [6.2 清洗阶段](#6.2 清洗阶段)
      • [6.3 特征/训练阶段](#6.3 特征/训练阶段)
      • [6.4 导出/报告阶段](#6.4 导出/报告阶段)
    • [7. 异常处理:让日志成为"可解释的失败"](#7. 异常处理:让日志成为“可解释的失败”)
      • [7.1 正确姿势:捕获异常 + logger.exception](#7.1 正确姿势:捕获异常 + logger.exception)
    • [8. 日志与 runs:把日志变成"可复现证据链"的一部分](#8. 日志与 runs:把日志变成“可复现证据链”的一部分)
    • [9. 常见坑:你踩一次就会记住,但最好别踩](#9. 常见坑:你踩一次就会记住,但最好别踩)
      • [坑 1:重复 handler 导致日志输出两遍/三遍](#坑 1:重复 handler 导致日志输出两遍/三遍)
      • [坑 2:日志太多,控制台刷屏](#坑 2:日志太多,控制台刷屏)
      • [坑 3:日志没有模块名/行号,定位困难](#坑 3:日志没有模块名/行号,定位困难)
      • [坑 4:只记日志,不记参数与配置快照](#坑 4:只记日志,不记参数与配置快照)
    • [10. 本章最低交付(MDR):你必须交付什么?](#10. 本章最低交付(MDR):你必须交付什么?)
    • [11. 小结:日志体系让你从"调试"进入"运维视角"](#11. 小结:日志体系让你从“调试”进入“运维视角”)

(从"print 调试"升级到"可追溯、可复现、可运维"的工程日志)

你一定经历过这种绝望时刻:

  • 代码在你电脑上能跑,换个数据就崩
  • 报错信息只给你一句 KeyErrorNoneType
  • 你开始疯狂插 print()
    print(df.shape)print(df.head())print(x)
  • 终于跑通了,但两天后问题复现,你又得重新插一遍

这不是你不够努力,而是你的项目缺一个"工程系统":日志体系

在真实交付里,日志不是"锦上添花",而是你能否排错、能否复现、能否验收的关键证据链。

尤其是数据处理与 AI 工程场景:数据漂移、依赖变化、参数变化、外部接口波动......没有日志,你根本不知道问题发生在哪一步。

本章我们只做一件事:
把你的项目从 print 调试升级为 logging 日志体系,让排错效率翻倍。


0. 先把话说清楚:日志不是"多打几行字"

很多人对日志的误解是:"我多输出一些信息就行"。

真正的日志体系至少要解决三件事:

  1. 可定位:问题发生在哪个阶段、哪一条数据、哪个参数
  2. 可追溯:同一次运行的参数、环境、关键产物能对应起来
  3. 可验收:交付时,日志是"证据链",能证明你做了什么、怎么做的

你可以把日志理解为项目的"黑匣子"。出事时靠它还原现场。


1. 为什么 print 调试会让你越做越慢

1.1 print 的三大致命缺陷

  • 不可分级:所有输出一锅端,想看关键点很难
  • 不可追踪:没有时间戳、模块名、行号,无法快速定位
  • 不可收敛:项目一大,你会插满 print,删也不是,不删也不是

而 logging 天然解决这些问题:

  • 有等级(DEBUG/INFO/WARNING/ERROR)
  • 有格式(时间、模块、函数、行号)
  • 可写文件(runs/log.txt 形成证据链)
  • 可统一管理(全项目一套规范)

2. 先给你一个"可交付日志体系"的标准答案

这一章你最终要达到的效果是:

  • 每次运行生成一个 runs/<timestamp>/log.txt
  • 终端输出 INFO(简洁可读)
  • 文件记录 DEBUG(详细可追溯)
  • 错误发生时,日志能告诉你:
    哪一步、什么输入、什么配置、什么异常栈

用 Mermaid 画出来就是这样:
启动脚本/CLI
加载配置 configs + env
初始化日志 logging
运行 ETL/训练/评估
关键节点写 INFO/DEBUG
异常写 ERROR + stacktrace
runs//log.txt
可追溯复现/可验收交付


3. logging 的正确姿势:别在每个文件里乱配

新手最常见的错误是:

在每个 .py 文件里写一套 basicConfig(),结果日志重复输出、格式混乱、级别不受控。

正确做法是:
全项目只在一个地方初始化日志 ,其余模块只负责 logger = logging.getLogger(__name__)

推荐放在:

  • src/myproj/core/logging.py

4. 落地实现:一套可直接复制的 logging 模板

下面这段代码我建议你直接作为项目模板使用。它满足:

  • 控制台输出:简洁 INFO
  • 文件输出:详细 DEBUG
  • 自动写入 runs/<ts>/log.txt
  • 支持日志轮转(可选)
  • 避免重复 handler(常见坑)

4.1 src/myproj/core/logging.py

python 复制代码
from __future__ import annotations

import logging
from pathlib import Path
from typing import Optional


def setup_logging(
    run_dir: str | Path,
    level: str = "INFO",
    console_level: str = "INFO",
    file_level: str = "DEBUG",
    log_name: str = "log.txt",
) -> None:
    """
    初始化项目日志体系:
    - 控制台输出:默认 INFO
    - 文件输出:默认 DEBUG,写入 runs/<ts>/log.txt
    """
    run_dir = Path(run_dir)
    run_dir.mkdir(parents=True, exist_ok=True)
    log_path = run_dir / log_name

    # 根 logger
    logger = logging.getLogger()
    logger.setLevel(logging.DEBUG)  # 根级别设为最低,让 handler 控制输出级别

    # 防止重复添加 handler(Notebook/重复运行时很常见)
    if logger.handlers:
        logger.handlers.clear()

    # 统一格式
    fmt_console = logging.Formatter(
        fmt="%(asctime)s | %(levelname)s | %(message)s",
        datefmt="%H:%M:%S",
    )
    fmt_file = logging.Formatter(
        fmt="%(asctime)s | %(levelname)s | %(name)s:%(lineno)d | %(message)s",
        datefmt="%Y-%m-%d %H:%M:%S",
    )

    # 控制台 handler
    ch = logging.StreamHandler()
    ch.setLevel(getattr(logging, console_level.upper(), logging.INFO))
    ch.setFormatter(fmt_console)

    # 文件 handler
    fh = logging.FileHandler(log_path, encoding="utf-8")
    fh.setLevel(getattr(logging, file_level.upper(), logging.DEBUG))
    fh.setFormatter(fmt_file)

    logger.addHandler(ch)
    logger.addHandler(fh)

    # 记录初始化信息(非常重要,便于验收)
    logger.info("Logging initialized.")
    logger.info(f"Log file: {log_path}")

你只需要在入口脚本(scripts/CLI)里调用一次 setup_logging(),全项目就统一了。


5. 在模块里写日志:三句话就够

5.1 模块写法模板

python 复制代码
import logging

logger = logging.getLogger(__name__)

def clean_df(df):
    logger.info("Start cleaning dataframe.")
    logger.debug(f"Input shape: {df.shape}")
    # ...
    return df

5.2 写日志的"信息密度原则"

日志不是写作文,关键在"能定位问题"。我建议你遵循这个顺序:

  • INFO:阶段性节点(开始/结束/产物路径/关键指标)
  • DEBUG:细节(shape、列名、缺失率、参数)
  • WARNING:非致命异常(缺列但已回退、某些行被丢弃)
  • ERROR:致命异常(直接失败,必须带栈)

6. 数据工程场景的"必打日志点"(建议照抄)

很多人不知道日志该写什么。这里给你一份"数据项目必打点"清单:

6.1 数据读取阶段

  • 文件路径、文件大小(可选)
  • 编码、分隔符、sheet 名(Excel)
  • 读取后 shape、列名

6.2 清洗阶段

  • 缺失值比例(全局 + 关键列)
  • 去重前后行数
  • 异常值处理策略(clip/remove)与处理数量

6.3 特征/训练阶段

  • seed、数据划分比例
  • 训练样本数/验证样本数
  • 关键指标(AUC/F1/RMSE)
  • 模型保存路径

6.4 导出/报告阶段

  • 输出文件路径
  • 产物数量(图表、表格)
  • runs 目录完整性检查

Read
Clean
Feature
Train
Eval
Export
runs/log.txt + metrics.json + artifacts


7. 异常处理:让日志成为"可解释的失败"

很多项目失败时只有一句"报错了"。

交付型项目要求你做到:失败也能解释

7.1 正确姿势:捕获异常 + logger.exception

python 复制代码
import logging
logger = logging.getLogger(__name__)

try:
    # 可能失败的逻辑
    ...
except Exception:
    logger.exception("Pipeline failed with unexpected error.")
    raise

logger.exception() 会自动把 stacktrace 写到日志里,这是排错效率翻倍的关键。


8. 日志与 runs:把日志变成"可复现证据链"的一部分

你在上一章已经做了 runs/<ts>/config_snapshot.json

这章你要补齐:runs/<ts>/log.txt

一套最小可验收的 runs 目录应该像这样:

text 复制代码
runs/2026-01-10_1030/
  config_snapshot.json
  log.txt
  metrics.json
  artifacts/

当你把这些东西交给别人,对方不仅能复现,还能审计你的过程。

这就是"工程化科研/AI 项目"和"随缘 Notebook"的本质区别。


9. 常见坑:你踩一次就会记住,但最好别踩

坑 1:重复 handler 导致日志输出两遍/三遍

解决:初始化时 if logger.handlers: logger.handlers.clear()

坑 2:日志太多,控制台刷屏

解决:控制台 INFO,文件 DEBUG(分级输出)

坑 3:日志没有模块名/行号,定位困难

解决:文件日志格式加 %(name)s:%(lineno)d

坑 4:只记日志,不记参数与配置快照

解决:日志 + config_snapshot 必须成对出现


10. 本章最低交付(MDR):你必须交付什么?

按照专栏学习协议,本章完成后你至少交付:

  1. src/myproj/core/logging.py:统一日志初始化
  2. 任意入口脚本/CLI:调用 setup_logging(run_dir=...)
  3. 任意核心模块:至少 5 条有效日志(INFO/DEBUG/WARNING 各至少一条)
  4. 一次真实运行:生成 runs/<ts>/log.txt
  5. README 增加 "Logs" 小节:告诉别人日志在哪里、怎么看

你做到这 5 条,你的项目排错效率会非常明显地提升。


11. 小结:日志体系让你从"调试"进入"运维视角"

当你掌握 logging,你会突然理解为什么工程师排错那么快:

因为他们不是在"猜",而是在读证据链。

日志体系带来的不是"多输出一点信息",而是:

  • 可定位(哪一步出错)
  • 可追溯(这次运行用的什么参数)
  • 可复现(别人能按日志复原现场)
  • 可验收(交付有证据、有过程、有产物)

下一章我们会进入更"硬核"的效率工具:断点调试(VSCode Debug),让你在运行时直接进入变量现场,把排错效率再提升一个量级。

《断点调试:VSCode 调试全流程(含常见坑)》

如果你愿意,把你现在项目里最常出现的 3 类报错(例如 KeyError、文件路径、类型不匹配)发我,我可以按本章的日志打法给你一份"日志点位设计清单",告诉你这些错误应该在哪些节点打哪些日志,做到一次定位。

相关推荐
人工智能训练2 小时前
【极速部署】Ubuntu24.04+CUDA13.0 玩转 VLLM 0.15.0:预编译 Wheel 包 GPU 版安装全攻略
运维·前端·人工智能·python·ai编程·cuda·vllm
yaoming1682 小时前
python性能优化方案研究
python·性能优化
码云数智-大飞3 小时前
使用 Python 高效提取 PDF 中的表格数据并导出为 TXT 或 Excel
python
biuyyyxxx4 小时前
Python自动化办公学习笔记(一) 工具安装&教程
笔记·python·学习·自动化
极客数模5 小时前
【2026美赛赛题初步翻译F题】2026_ICM_Problem_F
大数据·c语言·python·数学建模·matlab
小鸡吃米…6 小时前
机器学习中的代价函数
人工智能·python·机器学习
Li emily7 小时前
如何通过外汇API平台快速实现实时数据接入?
开发语言·python·api·fastapi·美股
m0_561359677 小时前
掌握Python魔法方法(Magic Methods)
jvm·数据库·python
Ulyanov7 小时前
顶层设计——单脉冲雷达仿真器的灵魂蓝图
python·算法·pyside·仿真系统·单脉冲
2401_838472518 小时前
使用Python进行图像识别:CNN卷积神经网络实战
jvm·数据库·python