程序运行中总会遇到各种情况:正常请求、意料之外的错误、性能瓶颈的蛛丝马迹。仅靠 print() 输出,在项目规模变大后会立刻暴露问题——信息杂乱、无法分级、难以持久化和搜索。logging 模块是标准库内置的专业日志方案,用很小的学习成本换来可维护的日志体系。
为什么不用 print()?
print()无法区分信息重要性,调试时加的一句输出和严重错误的提示混在一起。- 不能方便地输出到文件、只显示部分级别的日志。
- 不能自动添加时间戳、模块名等信息。
- 多线程/多进程下输出容易交错。
logging 模块天然解决了这些问题,并且用法并不复杂。
日志级别:让重要信息自动凸显
logging 定义了五个标准级别,数值上 DEBUG < INFO < WARNING < ERROR < CRITICAL。你可以设置“最低输出级别”,比它级别低的消息会被自动过滤。
import logging
logging.debug('调试信息,变量值为 x=%s', 42)
logging.info('服务正常启动')
logging.warning('磁盘使用超过 80%')
logging.error('数据库连接失败,重试一次')
logging.critical('内存耗尽,进程即将退出')
通常在不同环境选择不同级别:
- 开发时设为
DEBUG,看到全部细节。 - 生产环境设为
INFO或WARNING,避免日志洪水,同时不漏掉关键告警。
基础配置:快速上手
最简单的全局配置用 logging.basicConfig(),通常写在程序入口处。
import logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler('app.log', encoding='utf-8'), # 持续写入文件
logging.StreamHandler() # 同时在控制台输出
]
)
logger = logging.getLogger(__name__)
logger.info('应用启动')
格式化常用字段:
%(asctime)s:时间戳%(name)s:logger 的名字,通常用name对应模块%(levelname)s:级别名(DEBUG/INFO/WARNING/ERROR/CRITICAL)%(message)s:日志消息本身%(filename)s:%(lineno)d:文件名和行号,调试时极其有用
进阶用法:logger 对象与层次结构
在大型项目中,推荐每个模块显式创建自己的 logger,而非直接 logging.info()。logger 名称使用点号分隔的层级(如 myapp.auth),子 logger 默认继承父 logger 的配置,你可以精细控制不同模块的日志级别。
# modules/user.py
import logging
logger = logging.getLogger(__name__) # __name__ = 'modules.user'
logger.debug('用户登录检查')
然后在主入口统一配置:
logging.basicConfig(level=logging.WARNING) # 全局只显示 WARNING 以上
logging.getLogger('modules.user').setLevel(logging.DEBUG) # 但 user 模块显示 DEBUG
日志轮转:防止单个日志文件无限膨胀
生产环境的应用程序一直运行,日志文件可能会大到难以处理。logging.handlers 提供了两种常用轮转方式:
1. 按文件大小轮转 RotatingFileHandler
当文件达到指定大小后,自动将当前文件重命名(如 app.log 变成 app.log.1),并新建一个空的 app.log 继续写入。
from logging.handlers import RotatingFileHandler
handler = RotatingFileHandler(
'app.log', maxBytes=10*1024*1024, backupCount=5, encoding='utf-8'
)
# maxBytes=10MB, 最多保留5个旧文件
2. 按时间轮转 TimedRotatingFileHandler
按天、小时或分钟分割日志,适合按日期归档的场景。
from logging.handlers import TimedRotatingFileHandler
handler = TimedRotatingFileHandler(
'app.log', when='midnight', backupCount=30, encoding='utf-8'
)
# 每天午夜轮转一次,保留最近30天的日志
这些 handler 可以直接加到 basicConfig 的 handlers 参数里,也可以通过 addHandler() 添加。
一个生产级配置示例
结合前面知识,一个常见项目日志配置可能长这样:
import logging
from logging.handlers import RotatingFileHandler
def setup_logging():
logger = logging.getLogger() # 根 logger
logger.setLevel(logging.DEBUG)
# 控制台只输出 INFO 以上
console = logging.StreamHandler()
console.setLevel(logging.INFO)
console.setFormatter(logging.Formatter('%(levelname)s - %(message)s'))
# 文件输出所有 DEBUG 以上,并按大小轮转
file_handler = RotatingFileHandler(
'logs/app.log', maxBytes=5*1024*1024, backupCount=5, encoding='utf-8'
)
file_handler.setLevel(logging.DEBUG)
file_handler.setFormatter(
logging.Formatter(
'%(asctime)s - %(name)s - %(levelname)s - %(filename)s:%(lineno)d - %(message)s'
)
)
logger.addHandler(console)
logger.addHandler(file_handler)
setup_logging()
# 后续模块中直接使用
logger = logging.getLogger(__name__)
实用建议
- 日志不要用字符串拼接,使用
%s格式化,让 logging 模块在不需要输出时可以省去格式化开销。logger.debug('x=%s', x)是高效且安全的。 - 合理控制级别:别把正常用户操作记成
ERROR,否则真正错误会被淹没。访问日志用INFO,业务异常用WARNING或ERROR。 - 避免打印敏感信息:密码、token、手机号等应在日志中脱敏或禁止输出。
- 异步日志:高并发场景下,日志写磁盘可能成为瓶颈,可以用
QueueHandler配合后台线程异步写入,或者用第三方库loguru(其在真项目中的使用也越来越广泛,但标准库 logging 是最基础、无依赖的选择)。
掌握 standard library 的 logging 模块,就足以应对大多数项目的日志需求,而且没有额外依赖,稳定可靠。