日志系统基础:Python logging模块详解

日志这东西,说白了就是给程序装个黑匣子。

我刚开始写Agent的时候,总觉得日志可有可无。代码跑得挺欢,加日志干嘛?直到有一次,我的Agent在凌晨三点突然抽风,返回了一堆莫名其妙的JSON。我盯着屏幕看了两个小时,愣是没找到问题在哪。嗯,从那以后,我再也不敢轻视日志了。

今天我们就来聊聊Python的logging模块。这是Python标准库自带的日志工具,功能强大,而且不用装任何第三方包。

日志级别:从DEBUG到CRITICAL

Python logging定义了五个日志级别。我习惯把它们想象成「警报的严重程度」:

级别 数值 什么时候用
DEBUG 10 调试信息,比如变量值、函数入口出口
INFO 20 正常操作信息,比如「任务开始」「请求成功」
WARNING 30 有问题但不影响运行,比如「API响应变慢」
ERROR 40 出错了,但程序还能继续跑
CRITICAL 50 严重错误,程序可能撑不住了

你想想看,如果所有信息都打印出来,日志文件会爆炸。所以我们要设置一个阈值。默认是WARNING,也就是说只有WARNING及以上级别的日志才会被输出。

核心原则:生产环境用INFO或WARNING,开发调试用DEBUG。别在生产环境开DEBUG,除非你想被日志刷屏刷到怀疑人生。

日志格式:让日志说话

光有日志内容还不够,格式也很重要。我见过最原始的日志就是一行字符串,连时间戳都没有。这种日志基本等于废纸。

一个合格的日志格式,至少应该包含:

  • 时间戳:精确到毫秒,方便定位问题发生的时间点
  • 日志级别:一眼看出严重程度
  • 模块名:知道是哪个组件出的问题
  • 具体消息:描述发生了什么

举个例子,我常用的格式是这样的:

format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'

输出效果:

2024-01-15 14:23:45,123 - agent.core - INFO - 任务调度器启动成功

你看,一行日志就把「什么时候、谁、什么级别、什么事」说清楚了。

小技巧:在Agent项目中,我习惯把%(name)s设置成模块的完整路径,比如agent.plugins.weather。这样日志一出来,我就能知道是哪个插件在说话。

日志处理器:日志该去哪

日志处理器(Handler)决定了日志的「目的地」。你可以把日志送到:

  • 控制台(StreamHandler)
  • 文件(FileHandler)
  • 网络(SocketHandler)
  • 邮件(SMTPHandler)
  • 甚至HTTP接口(HTTPHandler)

我一般会同时配两个处理器:一个输出到控制台,方便开发时实时查看;一个写入文件,方便事后排查。

import logging

# 创建logger
logger = logging.getLogger('agent.core')
logger.setLevel(logging.DEBUG)

# 控制台处理器
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.INFO)

# 文件处理器
file_handler = logging.FileHandler('agent.log')
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)

这段代码的意思是:控制台只显示INFO及以上级别的日志,但文件里会记录所有DEBUG级别的信息。这样既不会刷屏,又保留了完整的调试信息。

注意:我曾经犯过一个错误——给同一个logger重复添加了两次文件处理器。结果每条日志都写了两遍,日志文件膨胀得飞快。所以记得检查一下,别重复添加。

知识体系:一张图说清楚

下面这张图展示了logging模块的核心组件和它们之间的关系:

Logger 记录器(日志入口) 传递 Handler 处理器(决定输出位置) 格式化 Formatter 格式化器(定义日志格式) 输出目标 控制台 / 文件 / 网络 写入 日志级别过滤机制 Logger.setLevel() 设置全局阈值 Handler.setLevel() 设置处理器级别(更精细控制)

这张图的核心逻辑很简单:Logger接收日志消息,经过级别过滤后,交给Handler。Handler再根据配置的Formatter,把日志写到指定的地方。

实战建议:Agent项目中的日志配置

在Agent项目中,我一般会这样配置日志:

  1. 每个模块一个独立的Logger:比如agent.planneragent.executoragent.memory。这样日志里能清楚看到是哪个模块在干活。
  2. 日志文件按天滚动:用TimedRotatingFileHandler,每天生成一个新文件。避免单个日志文件太大。
  3. 敏感信息脱敏:API Key、用户密码这些,绝对不能出现在日志里。我习惯写一个自定义的Formatter,自动过滤掉敏感字段。
  4. 结构化日志:对于Agent的决策过程,我建议用JSON格式记录。比如:{"action": "call_tool", "tool": "search", "query": "天气", "result": "..."}。这样后续分析起来特别方便。

个人经验:我习惯在Agent的每个关键决策点都打一条INFO日志。比如「选择了哪个工具」「返回了什么结果」。这样出了问题,我只要看日志就能还原Agent的思考过程,比猜来猜去高效多了。

好了,日志系统的基础就聊到这。记住一句话:日志不是写给机器看的,是写给未来的你(或者你的同事)看的。把日志写清楚,就是给自己留条后路。


公众号:蓝海资料掘金营,微信deep3321