第三讲:日志基础——从概念到实战

说实话,我刚开始写代码那会儿,根本不知道日志是什么。

出了问题怎么办?print()大法走天下。后来项目越来越大,print出来的东西满天飞,找问题像大海捞针。直到有一次线上事故,我花了整整一个通宵才定位到bug……嗯,从那以后,我老老实实学了日志。

什么是日志?

日志,说白了就是程序运行时的“黑匣子”。

它记录着系统里发生的每一件重要的事:谁在什么时候干了什么,结果如何,有没有出错。你想想看,没有日志的系统就像一架没有飞行记录仪的飞机——出了事你根本不知道原因。

核心观点:日志不是写给机器看的,是写给人看的。尤其是未来的你——那个凌晨三点被电话叫醒的你。

日志级别——你的“报警分级系统”

我见过不少新手把所有信息都打在一个级别上。结果呢?正常信息和致命错误混在一起,根本没法看。

Python的logging模块内置了五个级别,从低到高分别是:

级别 数值 什么时候用
DEBUG 10 调试细节,比如变量值、函数入口出口。生产环境一般不开启。
INFO 20 正常操作的信息,比如“用户登录成功”、“订单已创建”。
WARNING 30 虽然能跑,但有点不对劲。比如“磁盘剩余空间不足10%”。
ERROR 40 出错了,但程序还能继续跑。比如“数据库连接失败,重试中”。
CRITICAL 50 系统要挂了!比如“内存耗尽,无法分配新对象”。

我的习惯:开发时把日志级别设成DEBUG,上线后调到INFO或WARNING。CRITICAL级别的日志,我会额外发短信或打电话报警——这玩意儿不能等。

Python logging模块——五分钟上手

Python自带的logging模块,功能很强大。但别被它的配置吓到,我们先从最简单的开始。

最简示例:直接打印日志

import logging

logging.debug("这是调试信息")
logging.info("这是普通信息")
logging.warning("这是警告")
logging.error("这是错误")
logging.critical("这是致命错误")

运行一下,你会发现只有WARNING及以上的日志被打印出来了。为什么?因为默认的日志级别是WARNING。

注意:默认的日志输出格式很简陋,只有消息本身,没有时间、没有文件名、没有行号。这在生产环境里基本没法用。

配置日志格式——让日志“说人话”

我个人习惯用basicConfig快速配置,像这样:

import logging

logging.basicConfig(
    level=logging.DEBUG,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
    datefmt='%Y-%m-%d %H:%M:%S'
)

logging.info("系统启动成功")

输出效果:

2025-01-15 14:30:22 - root - INFO - 系统启动成功

你看,时间、模块名、级别、消息,一目了然。

记录到文件——别让日志飘散在风中

我曾经犯过一个错:日志只打印到控制台,结果程序崩溃后,日志也跟着没了。后来我学乖了,一定要写文件。

import logging

logging.basicConfig(
    level=logging.INFO,
    filename='app.log',
    filemode='a',  # 追加模式
    format='%(asctime)s - %(levelname)s - %(message)s'
)

logging.info("这条日志会写入文件")

避坑指南:我曾经在生产环境用filemode='w',结果每次重启程序,之前的日志全被清空了。嗯,血的教训。生产环境一定要用'a'(追加模式)。

日志架构的“最小可行模型”

下面这张图,是我做小项目时常用的日志架构。别看简单,够用。

你的应用程序 Logger 控制台 Handler 文件 Handler 你的代码 → Logger → Handler → 输出目标

流程很简单:你的代码调用logging.info(),Logger对象收到消息,然后交给Handler去处理——可以同时输出到控制台和文件。

一个完整的实战例子

把上面学到的串起来,写一个能用的日志工具:

import logging

def setup_logger():
    """配置日志系统"""
    logger = logging.getLogger("my_app")
    logger.setLevel(logging.DEBUG)
    
    # 控制台 Handler
    console_handler = logging.StreamHandler()
    console_handler.setLevel(logging.INFO)
    
    # 文件 Handler
    file_handler = logging.FileHandler("app.log", mode='a')
    file_handler.setLevel(logging.DEBUG)
    
    # 统一的格式
    formatter = logging.Formatter(
        '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
    )
    console_handler.setFormatter(formatter)
    file_handler.setFormatter(formatter)
    
    logger.addHandler(console_handler)
    logger.addHandler(file_handler)
    
    return logger

# 使用
log = setup_logger()
log.info("应用启动")
log.debug("调试信息只在文件里出现")
log.error("出错了,但控制台和文件都会记录")

我的建议:控制台只显示INFO及以上,文件记录所有DEBUG信息。这样开发时看控制台不烦,查问题时翻文件有细节。

小结

日志这东西,说白了就是给未来的自己留条后路。

  • 五个级别:DEBUG、INFO、WARNING、ERROR、CRITICAL
  • 用basicConfig快速上手,用getLogger做精细控制
  • 控制台+文件双输出,生产环境必备
  • 格式里一定要带时间、级别、模块名

下一讲,我们会深入logging模块的架构,聊聊Logger、Handler、Formatter、Filter这四大组件是怎么配合的。到时候你会发现,原来日志系统也可以设计得很优雅。