1. 什么是日志?为什么需要日志? #
日志(Logging) 就是记录程序运行过程中的各种信息,就像写日记一样。当程序运行时,我们可以通过日志了解程序在做什么、发生了什么问题。
1.1 为什么需要日志? #
想象一下,你的程序在用户电脑上运行出错了,但你没有日志,就完全不知道哪里出了问题。有了日志,你就能看到:
- 程序执行到哪一步了
- 哪一行代码出错了
- 错误发生时的数据是什么
- 程序运行是否正常
日志的常见用途:
- 调试程序:找出程序为什么出错
- 监控运行状态:了解程序是否正常运行
- 记录重要操作:比如用户登录、数据修改等
- 问题追踪:当用户报告问题时,通过日志定位问题
2. 前置知识 #
在学习 Logging 模块之前,你需要了解以下 Python 基础知识:
2.1 模块导入 #
Logging 是 Python 标准库的一部分,直接导入即可使用:
# 导入 logging 模块
import logging2.2 字符串格式化 #
日志中经常需要记录变量值,可以使用 f-string(Python 3.6+):
# 使用 f-string 格式化字符串
name = "张三"
age = 25
message = f"用户 {name} 的年龄是 {age}"2.3 文件操作基础 #
日志可以写入文件,需要了解基本的文件操作:
# 打开文件(追加模式)
file = open('log.txt', 'a')
file.write('这是一条日志\n')
file.close()2.4 异常处理 #
记录异常信息是日志的重要用途:
# 基本的异常处理
try:
result = 1 / 0
except Exception as e:
print(f"发生错误: {e}")如果你还不熟悉这些内容,建议先学习 Python 基础教程。
3. 日志级别详解 #
日志级别(Log Level)用来区分日志的重要程度。就像医院的急诊分级一样,不同严重程度的问题用不同的级别。
3.1 日志级别从低到高: #
- DEBUG(调试):最详细的日志,用于开发调试时查看程序内部细节
- INFO(信息):一般信息,记录程序正常运行的状态
- WARNING(警告):警告信息,程序能运行但有潜在问题
- ERROR(错误):错误信息,程序某个功能出错了
- CRITICAL(严重错误):最严重的错误,程序可能崩溃
重要概念:当你设置日志级别后,只有该级别及以上的日志才会被记录。比如设置级别为 WARNING,那么只会记录 WARNING、ERROR、CRITICAL 的日志,DEBUG 和 INFO 会被忽略。
3.2 级别对应的数值: #
# 日志级别实际上是数字
DEBUG = 10
INFO = 20
WARNING = 30
ERROR = 40
CRITICAL = 50数字越大,级别越高。设置级别时,低于该级别的日志都会被过滤掉。
4. 最简单的使用方式 #
对于初学者,最简单的方式是使用 basicConfig() 函数。这个函数可以快速配置日志系统,适合学习和简单项目。
4.1 基本示例 #
下面是一个最简单的日志示例:
# 导入 logging 模块
import logging
# 配置日志级别为 DEBUG(会显示所有级别的日志)
logging.basicConfig(level=logging.DEBUG)
# 记录不同级别的日志
logging.debug('这是一条调试信息')
logging.info('这是一条普通信息')
logging.warning('这是一条警告信息')
logging.error('这是一条错误信息')
logging.critical('这是一条严重错误信息')运行结果:
DEBUG:root:这是一条调试信息
INFO:root:这是一条普通信息
WARNING:root:这是一条警告信息
ERROR:root:这是一条错误信息
CRITICAL:root:这是一条严重错误信息说明:
root是默认的 logger 名称(后面会详细讲解)- 每条日志的格式是:
级别:logger名称:消息内容
4.2 设置不同的日志级别 #
你可以设置不同的日志级别,看看会发生什么:
# 导入 logging 模块
import logging
# 设置日志级别为 WARNING(只显示 WARNING 及以上级别)
logging.basicConfig(level=logging.WARNING)
# 记录不同级别的日志
logging.debug('调试信息 - 不会显示')
logging.info('普通信息 - 不会显示')
logging.warning('警告信息 - 会显示')
logging.error('错误信息 - 会显示')
logging.critical('严重错误 - 会显示')运行结果:
WARNING:root:警告信息 - 会显示
ERROR:root:错误信息 - 会显示
CRITICAL:root:严重错误 - 会显示可以看到,DEBUG 和 INFO 级别的日志被过滤掉了,因为它们的级别低于 WARNING。
4.3 自定义日志格式 #
默认的日志格式可能不够详细,我们可以自定义格式:
# 导入 logging 模块
import logging
# 配置日志格式和级别
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s',
datefmt='%Y-%m-%d %H:%M:%S'
)
# 记录日志
logging.info('程序启动')
logging.warning('内存使用率较高')
logging.error('连接数据库失败')运行结果:
2024-01-15 10:30:45 - INFO - 程序启动
2024-01-15 10:30:45 - WARNING - 内存使用率较高
2024-01-15 10:30:45 - ERROR - 连接数据库失败格式说明:
%(asctime)s:时间戳%(levelname)s:日志级别名称%(message)s:日志消息内容datefmt:时间格式
4.4 将日志写入文件 #
默认情况下,日志会输出到控制台。我们也可以将日志写入文件:
# 导入 logging 模块
import logging
# 配置日志:输出到文件,使用追加模式
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s',
datefmt='%Y-%m-%d %H:%M:%S',
filename='app.log', # 日志文件名
filemode='a' # 'a' 表示追加模式,'w' 表示覆盖模式
)
# 记录日志(这些日志会写入 app.log 文件,不会显示在控制台)
logging.info('程序启动')
logging.warning('这是一个警告')
logging.error('这是一个错误')运行后,会在当前目录生成 app.log 文件,里面包含所有日志记录。
5. 核心概念详解 #
要深入理解 Logging 模块,需要了解几个核心概念。这些概念就像搭积木一样,组合起来就构成了完整的日志系统。
5.1 Logger(记录器) #
Logger 是日志系统的入口,我们通过它来记录日志。可以把它想象成一个"日志记录员"。
特点:
- 每个 Logger 都有一个名称
- 可以设置日志级别
- 可以有多个 Handler(处理器)
5.2 Handler(处理器) #
Handler 决定日志输出到哪里。就像邮递员一样,负责把日志"送"到目的地。
常见的 Handler:
StreamHandler:输出到控制台(终端)FileHandler:输出到文件RotatingFileHandler:输出到文件,支持文件大小轮转
5.3 Formatter(格式器) #
Formatter 决定日志的显示格式。就像给日志"化妆",让它更美观、更易读。
5.4 它们的关系 #
Logger(记录器)
↓
Handler(处理器)→ Formatter(格式器)
↓
输出到控制台/文件工作流程:
- 你调用
logger.info('消息') - Logger 检查级别,如果通过就创建日志记录
- Logger 把记录交给 Handler
- Handler 用 Formatter 格式化日志
- Handler 输出到目标位置(控制台或文件)
6. 创建自定义 Logger #
在实际项目中,我们通常不使用默认的 root logger,而是创建自己的 Logger。这样可以更好地管理不同模块的日志。
6.1 创建简单的 Logger #
# 导入 logging 模块
import logging
# 创建一个名为 'my_app' 的 logger
logger = logging.getLogger('my_app')
# 设置日志级别
logger.setLevel(logging.DEBUG)
# 创建控制台处理器
console_handler = logging.StreamHandler()
# 设置处理器的日志级别
console_handler.setLevel(logging.INFO)
# 创建格式器
formatter = logging.Formatter(
'%(asctime)s - %(name)s - %(levelname)s - %(message)s',
datefmt='%Y-%m-%d %H:%M:%S'
)
# 将格式器添加到处理器
console_handler.setFormatter(formatter)
# 将处理器添加到 logger
logger.addHandler(console_handler)
# 使用 logger 记录日志
logger.debug('这是调试信息')
logger.info('程序启动成功')
logger.warning('内存不足')
logger.error('发生错误')运行结果:
2024-01-15 10:30:45 - my_app - INFO - 程序启动成功
2024-01-15 10:30:45 - my_app - WARNING - 内存不足
2024-01-15 10:30:45 - my_app - ERROR - 发生错误说明:
getLogger('my_app')创建一个名为 'my_app' 的 loggersetLevel()设置 logger 的级别StreamHandler()创建控制台处理器Formatter()创建格式器addHandler()将处理器添加到 logger
6.2 同时输出到控制台和文件 #
在实际项目中,我们通常希望日志既显示在控制台,又保存到文件:
# 导入 logging 模块
import logging
# 创建 logger
logger = logging.getLogger('my_app')
logger.setLevel(logging.DEBUG)
# 创建格式器
formatter = logging.Formatter(
'%(asctime)s - %(name)s - %(levelname)s - %(message)s',
datefmt='%Y-%m-%d %H:%M:%S'
)
# 创建控制台处理器
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.INFO) # 控制台只显示 INFO 及以上级别
console_handler.setFormatter(formatter)
# 创建文件处理器
file_handler = logging.FileHandler('app.log', mode='a', encoding='utf-8')
file_handler.setLevel(logging.DEBUG) # 文件记录所有 DEBUG 及以上级别
file_handler.setFormatter(formatter)
# 将处理器添加到 logger
logger.addHandler(console_handler)
logger.addHandler(file_handler)
# 使用 logger 记录日志
logger.debug('调试信息 - 只在文件中可见')
logger.info('程序启动 - 控制台和文件都可见')
logger.warning('警告信息 - 控制台和文件都可见')
logger.error('错误信息 - 控制台和文件都可见')说明:
- 控制台处理器级别设为 INFO,所以 DEBUG 信息不会在控制台显示
- 文件处理器级别设为 DEBUG,所以所有日志都会写入文件
- 这样可以在控制台看到重要信息,同时在文件中保存详细日志
7. 记录异常信息 #
记录异常信息是日志的重要用途。当程序出错时,我们需要记录完整的错误信息,包括错误类型、错误消息和堆栈跟踪。
7.1 使用 exception() 方法 #
最简单的方式是使用 logger.exception(),它会自动记录异常信息:
# 导入 logging 模块
import logging
# 配置日志
logging.basicConfig(
level=logging.DEBUG,
format='%(asctime)s - %(levelname)s - %(message)s',
datefmt='%Y-%m-%d %H:%M:%S'
)
# 创建 logger
logger = logging.getLogger(__name__)
# 模拟一个会出错的函数
def divide(a, b):
# 尝试除法运算
result = a / b
return result
# 使用 try-except 捕获异常并记录
try:
# 尝试执行可能出错的代码
result = divide(10, 0)
except Exception:
# 记录异常信息(会自动包含堆栈跟踪)
logger.exception('除法运算出错')运行结果:
2024-01-15 10:30:45 - ERROR - 除法运算出错
Traceback (most recent call last):
File "test.py", line 15, in <module>
result = divide(10, 0)
File "test.py", line 8, in divide
result = a / b
ZeroDivisionError: division by zero7.2 使用 error() 方法记录异常 #
也可以使用 logger.error() 配合 exc_info=True 参数:
# 导入 logging 模块
import logging
# 配置日志
logging.basicConfig(
level=logging.DEBUG,
format='%(asctime)s - %(levelname)s - %(message)s',
datefmt='%Y-%m-%d %H:%M:%S'
)
# 创建 logger
logger = logging.getLogger(__name__)
# 模拟一个会出错的函数
def process_data(data):
# 尝试访问不存在的键
value = data['key']
# 使用 try-except 捕获异常
try:
# 尝试处理数据
process_data({})
except Exception as e:
# 记录异常信息
logger.error(f'处理数据时出错: {e}', exc_info=True)说明:
logger.exception()等价于logger.error(..., exc_info=True)exc_info=True会记录完整的异常堆栈信息- 异常信息对于调试非常重要,一定要记录
8. 日志轮转 #
当日志文件变得很大时,会影响程序性能。日志轮转(Log Rotation)可以自动创建新的日志文件,避免单个文件过大。
8.1 按文件大小轮转 #
当日志文件达到指定大小时,自动创建新文件:
# 导入 logging 模块和轮转处理器
import logging
from logging.handlers import RotatingFileHandler
# 创建 logger
logger = logging.getLogger('my_app')
logger.setLevel(logging.DEBUG)
# 创建格式器
formatter = logging.Formatter(
'%(asctime)s - %(levelname)s - %(message)s',
datefmt='%Y-%m-%d %H:%M:%S'
)
# 创建轮转文件处理器
# maxBytes: 文件最大大小(1MB)
# backupCount: 保留的备份文件数量(5个)
rotating_handler = RotatingFileHandler(
'app.log',
maxBytes=1024 * 1024, # 1MB
backupCount=5,
encoding='utf-8'
)
rotating_handler.setLevel(logging.DEBUG)
rotating_handler.setFormatter(formatter)
# 添加处理器
logger.addHandler(rotating_handler)
# 记录日志
for i in range(100):
logger.info(f'这是第 {i} 条日志信息')说明:
- 当日志文件达到 1MB 时,会自动重命名为
app.log.1 - 原来的
app.log.1会变成app.log.2,依此类推 - 最多保留 5 个备份文件(
app.log.1到app.log.5) - 超过 5 个的旧文件会被删除
8.2 按时间轮转 #
每天自动创建新的日志文件:
# 导入 logging 模块和时间轮转处理器
import logging
from logging.handlers import TimedRotatingFileHandler
# 创建 logger
logger = logging.getLogger('my_app')
logger.setLevel(logging.DEBUG)
# 创建格式器
formatter = logging.Formatter(
'%(asctime)s - %(levelname)s - %(message)s',
datefmt='%Y-%m-%d %H:%M:%S'
)
# 创建时间轮转文件处理器
# when: 轮转时间单位('midnight' 表示每天午夜)
# interval: 时间间隔(1 天)
# backupCount: 保留的备份文件数量(7 天)
timed_handler = TimedRotatingFileHandler(
'app.log',
when='midnight',
interval=1,
backupCount=7,
encoding='utf-8'
)
timed_handler.setLevel(logging.DEBUG)
timed_handler.setFormatter(formatter)
# 添加处理器
logger.addHandler(timed_handler)
# 记录日志
logger.info('程序启动')
logger.warning('这是一条警告')说明:
when='midnight'表示每天午夜轮转interval=1表示每 1 个时间单位轮转一次backupCount=7表示保留 7 天的日志文件- 轮转后的文件会自动添加日期后缀,如
app.log.2024-01-15
9. 实际应用示例 #
下面是一个完整的、可以在实际项目中使用的日志配置示例:
# 导入必要的模块
import logging
import sys
from logging.handlers import RotatingFileHandler
def setup_logger(name, log_file='app.log', level=logging.INFO):
"""
设置并返回一个配置好的 logger
参数:
name: logger 的名称(通常使用 __name__)
log_file: 日志文件名
level: 日志级别
返回:
配置好的 logger 对象
"""
# 创建 logger
logger = logging.getLogger(name)
logger.setLevel(level)
# 避免重复添加处理器(如果 logger 已经有处理器,直接返回)
if logger.handlers:
return logger
# 创建格式器
# 格式包含:时间、logger名称、级别、文件名和行号、消息
formatter = logging.Formatter(
'%(asctime)s - %(name)s - %(levelname)s - [%(filename)s:%(lineno)d] - %(message)s',
datefmt='%Y-%m-%d %H:%M:%S'
)
# 创建控制台处理器
console_handler = logging.StreamHandler(sys.stdout)
console_handler.setFormatter(formatter)
console_handler.setLevel(logging.INFO) # 控制台只显示 INFO 及以上级别
# 创建文件处理器(带轮转功能)
# maxBytes: 10MB
# backupCount: 保留 5 个备份文件
file_handler = RotatingFileHandler(
log_file,
maxBytes=10 * 1024 * 1024, # 10MB
backupCount=5,
encoding='utf-8'
)
file_handler.setFormatter(formatter)
file_handler.setLevel(logging.DEBUG) # 文件记录所有 DEBUG 及以上级别
# 添加处理器到 logger
logger.addHandler(console_handler)
logger.addHandler(file_handler)
return logger
# 使用示例
if __name__ == '__main__':
# 创建 logger
logger = setup_logger(__name__)
# 记录不同级别的日志
logger.debug('这是调试信息(只在文件中可见)')
logger.info('程序启动成功')
logger.warning('内存使用率较高')
logger.error('连接数据库失败')
# 记录异常信息
try:
# 模拟一个会出错的代码
result = 1 / 0
except Exception:
logger.exception('发生除零错误')
# 记录带变量的日志
user_id = 123
username = '张三'
logger.info(f'用户 {username} (ID: {user_id}) 登录成功')这个示例的特点:
- 同时输出到控制台和文件
- 控制台显示重要信息,文件保存详细日志
- 支持日志文件轮转,避免文件过大
- 包含文件名和行号,方便定位问题
- 可以记录异常信息
10. 模块级别的 Logger(最佳实践) #
在实际项目中,建议在每个模块中创建自己的 logger,使用模块名作为 logger 名称:
# 导入 logging 模块
import logging
# 在每个模块开头创建 logger(使用 __name__ 作为名称)
logger = logging.getLogger(__name__)
# 定义用户类
class User:
def __init__(self, name):
# 记录对象创建
logger.info(f'创建用户对象: {name}')
self.name = name
def login(self):
# 记录用户登录
logger.info(f'用户 {self.name} 登录')
return True
# 使用示例
if __name__ == '__main__':
# 配置日志(在实际项目中,这通常在程序入口处配置一次)
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
datefmt='%Y-%m-%d %H:%M:%S'
)
# 创建用户对象
user = User('张三')
user.login()运行结果:
2024-01-15 10:30:45 - __main__ - INFO - 创建用户对象: 张三
2024-01-15 10:30:45 - __main__ - INFO - 用户 张三 登录说明:
- 使用
__name__作为 logger 名称,可以自动识别是哪个模块的日志 - 这样在日志中就能清楚地看到每条日志来自哪个模块
- 这是 Python 社区推荐的最佳实践
11. 常用技巧 #
11.1 临时调整日志级别 #
有时候我们需要临时提高或降低日志级别:
# 导入 logging 模块
import logging
# 创建 logger
logger = logging.getLogger(__name__)
# 配置日志
logging.basicConfig(level=logging.INFO)
# 保存原始日志级别
original_level = logger.level
# 临时提高日志级别(只显示 WARNING 及以上)
logger.setLevel(logging.WARNING)
logger.debug('这条调试信息不会显示')
logger.info('这条普通信息不会显示')
logger.warning('这条警告信息会显示')
# 恢复原始日志级别
logger.setLevel(original_level)
logger.info('现在普通信息又会显示了')11.2 禁用第三方库的日志 #
有时候第三方库会产生很多日志,我们可以提高它们的日志级别:
# 导入 logging 模块
import logging
# 禁用 requests 库的 DEBUG 和 INFO 日志(只显示 WARNING 及以上)
logging.getLogger('requests').setLevel(logging.WARNING)
# 禁用 urllib3 库的日志
logging.getLogger('urllib3').setLevel(logging.WARNING)12. 总结 #
Logging 模块是 Python 开发中必不可少的工具。通过本教程,你应该已经掌握了:
12.1 核心知识点 #
- 日志级别:DEBUG < INFO < WARNING < ERROR < CRITICAL
- 基本使用:
basicConfig()快速配置 - 核心概念:Logger、Handler、Formatter
- 自定义配置:创建自己的 logger 和 handler
- 异常记录:使用
logger.exception()记录异常 - 日志轮转:避免日志文件过大
12.2 最佳实践 #
- 使用模块级别的 logger:
logger = logging.getLogger(__name__) - 同时输出到控制台和文件:控制台显示重要信息,文件保存详细日志
- 合理设置日志级别:开发时用 DEBUG,生产环境用 INFO 或 WARNING
- 记录异常信息:使用
logger.exception()记录完整的错误堆栈 - 使用日志轮转:避免日志文件过大影响性能
12.3 学习建议 #
- 从简单的
basicConfig()开始学习 - 逐步理解 Logger、Handler、Formatter 的关系
- 在实际项目中练习使用
- 根据项目需求选择合适的日志级别和格式
记住:好的日志记录习惯会让你的调试工作事半功倍!