在任何一个严谨的软件开发项目中,日志(Logging)都是不可或缺的一环。它不仅是调试代码的利器,更是线上问题追踪、性能分析和数据监控的重要依据。相比于随处可见的 print()
语句,Python 内置的 logging
模块提供了更为强大、灵活且标准化的解决方案。 [1][2]
这篇博客将带你由浅入深,全面掌握 logging
模块的使用,从基础配置到高级技巧,再到企业级项目的最佳实践。
很多初学者习惯于使用 print()
来调试,但这在复杂项目中会带来诸多问题:
print()
语句,这既繁琐又容易出错。print()
默认只输出内容,没有时间、模块名、代码行号等关键上下文信息。 [3]print()
总是默认输出到标准输出(控制台),而我们常常需要将日志写入文件,甚至发送到远程服务器。logging
模块完美地解决了以上所有问题,它允许你:
logging.basicConfig()
对于简单的脚本,logging.basicConfig()
是最快捷的入门方式。它能快速配置日志系统。 [8]
import logging
# 基本配置
logging.basicConfig(
level=logging.DEBUG, # 设置记录的最低级别
format='%(asctime)s - %(levelname)s - %(message)s', # 设置日志格式
filename='app.log', # 将日志写入指定文件
filemode='w' # 文件打开模式,'w'为覆盖,'a'为追加
)
logging.debug('这是一条调试信息 (debug)')
logging.info('这是一条常规信息 (info)')
logging.warning('这是一条警告信息 (warning)')
logging.error('这是一条错误信息 (error)')
logging.critical('这是一条严重错误信息 (critical)')
代码解释:
level
: 指定日志记录的最低严重性级别。只有等于或高于此级别的日志才会被处理。 [9] 级别从低到高为:DEBUG
< INFO
< WARNING
< ERROR
< CRITICAL
。format
: 定义每条日志的输出格式。常用的格式化字符串包括:
%(asctime)s
: 日志记录创建的时间。%(name)s
: Logger 的名称。%(levelname)s
: 日志级别的文本名称。%(message)s
: 日志消息本身。%(filename)s
: 产生日志的源文件名。%(lineno)d
: 产生日志的行号。filename
: 如果指定,日志将写入此文件,否则输出到控制台。 [6]filemode
: 如果指定了 filename
,此参数定义文件打开模式,默认为 'a'
(追加)。basicConfig
虽然方便,但不够灵活。要真正发挥 logging
的威力,你需要理解它的四大核心组件:Loggers, Handlers, Formatters, 和 Filters。 [6][10]
Logger (日志记录器):这是应用程序直接交互的接口。你可以通过 logging.getLogger(name)
获取一个 Logger 实例。一个最佳实践是使用 __name__
作为 logger 的名字,这样可以清晰地知道日志来自哪个模块。 [7][11] Logger 之间存在层级关系,例如名为 my_app.utils
的 logger 是 my_app
的子 logger。
Handler (处理器):决定日志的去向。一个 Logger 可以添加多个 Handler,将同一条日志发送到不同地方。 [10] 常见的 Handler 有:
StreamHandler
: 将日志输出到流,如 sys.stdout
(标准输出) 或 sys.stderr
(标准错误)。 [12]FileHandler
: 将日志写入磁盘文件。 [13]RotatingFileHandler
: 按文件大小自动分割日志文件。 [14][15]TimedRotatingFileHandler
: 按时间间隔自动分割日志文件(如每天一个)。 [14][15]Formatter (格式化器):定义最终输出的日志格式。创建一个 Formatter 对象并将其关联到 Handler 上,所有经过该 Handler 的日志都会被格式化。 [12]
Filter (过滤器):提供更精细的日志过滤控制,可以根据特定条件决定哪些日志记录可以被处理。
综合示例:
下面的代码演示了如何手动组装这四大组件,实现将 INFO
级别及以上的日志输出到控制台,同时将 DEBUG
级别及以上的日志写入文件。
import logging
# 1. 创建一个 logger
logger = logging.getLogger('my_app')
logger.setLevel(logging.DEBUG) # 设置 logger 的最低级别
# 2. 创建一个 handler,用于写入日志文件
file_handler = logging.FileHandler('my_app_detailed.log', mode='w')
file_handler.setLevel(logging.DEBUG) # 文件记录所有 DEBUG 级别及以上日志
# 3. 创建另一个 handler,用于输出到控制台
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.INFO) # 控制台只显示 INFO 级别及以上日志
# 4. 定义 handler 的输出格式 (Formatter)
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
file_handler.setFormatter(formatter)
console_handler.setFormatter(formatter)
# 5. 将 handlers 添加到 logger
logger.addHandler(file_handler)
logger.addHandler(console_handler)
# 记录日志
logger.debug('这是一个用于文件记录的 debug 信息。')
logger.info('这条信息会同时出现在文件和控制台。')
logger.warning('这是一条警告,也会出现在文件和控制台。')
logger.error('捕获到一个错误!')
# 记录异常
try:
1 / 0
except ZeroDivisionError:
# exc_info=True 会自动将异常信息添加到日志中
logger.error("计算出错", exc_info=True)
dictConfig
)在大型应用中,硬编码日志配置是不明智的。logging.config.dictConfig()
允许你使用一个字典来定义所有配置,这个字典可以从 YAML 或 JSON 文件加载,极大地提高了灵活性。 [9][16] 这是目前推荐的配置方式。 [17]
配置字典示例 (config.py
):
LOGGING_CONFIG = {
'version': 1,
'disable_existing_loggers': False,
'formatters': {
'standard': {
'format': '%(asctime)s [%(levelname)s] %(name)s: %(message)s'
},
},
'handlers': {
'console': {
'level': 'INFO',
'class': 'logging.StreamHandler',
'formatter': 'standard'
},
'file': {
'level': 'DEBUG',
'class': 'logging.handlers.RotatingFileHandler',
'filename': 'production.log',
'maxBytes': 1024 * 1024 * 5, # 5 MB
'backupCount': 5,
'formatter': 'standard',
}
},
'loggers': {
'': { # root logger
'handlers': ['console', 'file'],
'level': 'WARNING',
'propagate': True
},
'my_app': { # 应用专属 logger
'handlers': ['console', 'file'],
'level': 'DEBUG',
'propagate': False # 防止日志向 root logger 传递
}
}
}
在主程序中加载配置:
import logging.config
from config import LOGGING_CONFIG
logging.config.dictConfig(LOGGING_CONFIG)
# 在其他模块中使用
logger = logging.getLogger('my_app.database')
logger.info("数据库连接成功。")
为了防止日志文件无限增长,消耗所有磁盘空间,必须进行日志分割。 [13][18]
RotatingFileHandler
: 当日志文件达到 maxBytes
大小时,会自动重命名旧文件并创建新文件。backupCount
参数指定保留的旧文件数量。 [13][15] logging.handlers.RotatingFileHandler(filename, mode='a', maxBytes=0, backupCount=0, encoding=None, delay=False, errors=None)
TimedRotatingFileHandler
: 根据时间间隔(如每天、每小时)创建新的日志文件。 [15][19] logging.handlers.TimedRotatingFileHandler(filename, when='h', interval=1, backupCount=0, encoding=None, delay=False, utc=False, atTime=None, errors=None)
其中 when
参数可以设置为 ‘S’, ‘M’, ‘H’, ‘D’, ‘W0’-‘W6’ (周一到周日) 等。 [19]这是一个非常重要的区别,但经常被忽视:
NullHandler
)。 [10] 你只需要获取 logger (logging.getLogger(__name__)
) 并用它记录日志。配置 Handler 是使用你库的应用程序开发者的责任。添加一个 NullHandler
可以防止在应用程序未配置日志时出现 “No handlers could be found…” 的警告。 [10]main
函数)配置日志系统,决定日志的级别、格式和去向。 [20]在现代化的、基于微服务的架构中,将日志以 JSON 等结构化格式输出,可以极大地简化日志的收集、查询和分析过程。 [3][7] 你可以自定义 Formatter 来实现这一点。
切记不要在日志中记录密码、API密钥、用户个人身份信息等敏感数据。 [7][18] 如果必须记录某些可能敏感的信息,应进行脱敏或加密处理。
logging
模块是 Python 开发者的强大盟友。掌握它,你的代码将变得更加健壮、可维护和易于监控。
basicConfig()
开始,满足简单需求。dictConfig
,实现配置与代码分离。现在,是时候在你的下一个项目中,用优雅的 logging
彻底取代杂乱的 print()
了。
Learn more: