第六章 日志体系: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、文件路径、类型不匹配)发我,我可以按本章的日志打法给你一份"日志点位设计清单",告诉你这些错误应该在哪些节点打哪些日志,做到一次定位。

相关推荐
laufing2 小时前
flask_restx 创建restful api
python·flask·restful
毕设源码-郭学长2 小时前
【开题答辩全过程】以 基于python电商商城系统为例,包含答辩的问题和答案
开发语言·python
black0moonlight2 小时前
win11 isaacsim 5.1.0 和lab配置
python
知乎的哥廷根数学学派3 小时前
基于多尺度注意力机制融合连续小波变换与原型网络的滚动轴承小样本故障诊断方法(Pytorch)
网络·人工智能·pytorch·python·深度学习·算法·机器学习
网安CILLE3 小时前
PHP四大输出语句
linux·开发语言·python·web安全·网络安全·系统安全·php
jjjddfvv3 小时前
超级简单启动llamafactory!
windows·python·深度学习·神经网络·微调·audiolm·llamafactory
A先生的AI之旅3 小时前
2025顶会TimeDRT快速解读
人工智能·pytorch·python·深度学习·机器学习
程序员小远3 小时前
完整的项目测试方案流程
自动化测试·软件测试·python·功能测试·测试工具·职场和发展·测试用例