Python日志(logging)详解
为什么需要日志?
想象一下,你的程序运行了,但是出错了。你只看到:
错误:文件不存在
问题:
- 什么时候发生的?
- 在哪里发生的?
- 当时程序在做什么?
- 之前发生了什么?
这就是为什么需要日志!好的日志能告诉你:
2024-01-15 14:30:25,123 - ERROR - 文件读取失败
文件路径:/data/config.txt
错误原因:文件不存在
调用堆栈:main.py第45行 -> utils.py第120行
程序状态:正在初始化配置
不要再用print()了!
print()的问题:
# 坏习惯
print("开始处理...")
result = process_data(data)
print(f"处理结果:{result}")
if error:
print("错误:处理失败!")
问题:
- 无法区分重要性(哪些是调试信息,哪些是错误?)
- 无法保存到文件
- 生产环境需要关闭,但要一个个删除print
- 没有时间戳,不知道什么时候发生的
logging模块的基本使用
最简单的日志
import logging
# 记录不同级别的日志
logging.debug("这是调试信息")
logging.info("这是一般信息")
logging.warning("这是警告信息")
logging.error("这是错误信息")
logging.critical("这是严重错误")
# 输出:
# WARNING:root:这是警告信息
# ERROR:root:这是错误信息
# CRITICAL:root:这是严重错误
注意:为什么debug和info没有输出?
因为logging的默认级别是WARNING,只记录WARNING及以上级别的日志。
设置日志级别
import logging
# 设置级别为DEBUG,这样所有级别的日志都会输出
logging.basicConfig(level=logging.DEBUG)
logging.debug("开始处理数据")
logging.info("读取文件成功")
logging.warning("内存使用率较高")
logging.error("数据库连接失败")
# 输出:
# DEBUG:root:开始处理数据
# INFO:root:读取文件成功
# WARNING:root:内存使用率较高
# ERROR:root:数据库连接失败
日志级别详解
logging有5个级别,从低到高:
| 级别 | 数值 | 使用场景 | 例子 |
|---|---|---|---|
| DEBUG | 10 | 调试信息,最详细 | "进入函数process_data" |
| INFO | 20 | 程序正常运行信息 | "用户登录成功" |
| WARNING | 30 | 警告,可能有问题 | "磁盘空间不足85%" |
| ERROR | 40 | 错误,某些功能失败 | "数据库连接失败" |
| CRITICAL | 50 | 严重错误,程序要挂了 | "内存耗尽,程序退出" |
级别使用建议:
import logging
def process_order(order_id):
# DEBUG: 详细的调试信息
logging.debug(f"开始处理订单 {order_id}")
try:
# INFO: 正常的业务流程
logging.info(f"验证订单 {order_id}")
validate_order(order_id)
# 更多处理...
except ValidationError as e:
# WARNING: 可恢复的问题
logging.warning(f"订单验证失败但可重试: {e}")
except DatabaseError as e:
# ERROR: 需要关注的问题
logging.error(f"数据库错误: {e}")
except Exception as e:
# CRITICAL: 致命错误
logging.critical(f"未知错误导致订单处理崩溃: {e}")
raise
基本配置:让日志更有用
添加时间戳和级别
import logging
# 基本配置
logging.basicConfig(
level=logging.DEBUG,
format='%(asctime)s - %(levelname)s - %(message)s'
)
# 现在日志包含时间了!
logging.info("程序启动")
logging.error("发生错误")
# 输出:
# 2024-01-15 14:30:25,123 - INFO - 程序启动
# 2024-01-15 14:30:25,124 - ERROR - 发生错误
保存到文件
import logging
# 配置日志保存到文件
logging.basicConfig(
level=logging.DEBUG,
format='%(asctime)s - %(levelname)s - %(message)s',
filename='app.log', # 日志保存到文件
filemode='a' # 追加模式
)
# 这些日志会保存到app.log文件
logging.info("程序启动")
logging.error("发生错误")
# 控制台不会输出,但文件里会有记录
同时输出到控制台和文件
import logging
# 创建logger
logger = logging.getLogger()
logger.setLevel(logging.DEBUG)
# 创建控制台处理器
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.INFO)
# 创建文件处理器
file_handler = logging.FileHandler('app.log')
file_handler.setLevel(logging.DEBUG)
# 设置格式
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
console_handler.setFormatter(formatter)
file_handler.setFormatter(formatter)
# 添加处理器
logger.addHandler(console_handler)
logger.addHandler(file_handler)
# 测试
logger.debug("调试信息") # 只记录到文件
logger.info("一般信息") # 记录到文件和输出到控制台
logger.error("错误信息") # 记录到文件和输出到控制台
日志格式详解
常用格式字段:
| 字段 | 说明 | 例子 |
|---|---|---|
| %(asctime)s | 时间 | 2024-01-15 14:30:25,123 |
| %(levelname)s | 级别 | INFO, ERROR |
| %(message)s | 消息 | "文件读取成功" |
| %(name)s | logger名字 | root, myapp |
| %(filename)s | 文件名 | app.py |
| %(lineno)d | 行号 | 45 |
| %(funcName)s | 函数名 | process_data |
| %(module)s | 模块名 | utils |
自定义格式:
import logging
logging.basicConfig(
level=logging.INFO,
format='[%(asctime)s] [%(levelname)s] [%(filename)s:%(lineno)d] %(message)s',
datefmt='%Y-%m-%d %H:%M:%S'
)
logging.error("数据库连接失败")
# 输出:
# [2024-01-15 14:30:25] [ERROR] [app.py:45] 数据库连接失败
使用不同的logger
创建命名logger
import logging
# 创建不同模块的logger
database_logger = logging.getLogger("database")
api_logger = logging.getLogger("api")
auth_logger = logging.getLogger("auth")
# 为每个logger单独配置
database_logger.setLevel(logging.DEBUG)
api_logger.setLevel(logging.INFO)
# 使用
database_logger.debug("连接数据库...")
api_logger.info("API请求开始")
auth_logger.warning("登录尝试次数过多")
模块化配置示例
# database.py
import logging
logger = logging.getLogger(__name__) # 自动使用模块名
def connect_to_db():
logger.info("尝试连接数据库...")
try:
# 连接数据库...
logger.info("数据库连接成功")
except Exception as e:
logger.error(f"数据库连接失败: {e}")
raise
# app.py
import logging
from database import connect_to_db
# 配置根logger
logging.basicConfig(
level=logging.INFO,
format='%(name)s - %(levelname)s - %(message)s'
)
# 运行
connect_to_db()
记录异常信息
使用exception()方法
import logging
logging.basicConfig(level=logging.ERROR)
try:
1 / 0
except ZeroDivisionError:
logging.exception("除零错误!") # 自动记录异常堆栈
# 输出:
# ERROR:root:除零错误!
# Traceback (most recent call last):
# File "<stdin>", line 2, in <module>
# ZeroDivisionError: division by zero
记录完整异常信息
import logging
import traceback
def risky_operation():
try:
# 可能失败的操作
result = 1 / 0
except Exception as e:
# 记录异常详情
logging.error(f"操作失败: {e}")
logging.error(f"完整堆栈: {traceback.format_exc()}")
raise
risky_operation()
日志轮转(处理大文件)
按大小轮转
import logging
from logging.handlers import RotatingFileHandler
# 创建轮转处理器
handler = RotatingFileHandler(
'app.log', # 文件名
maxBytes=1024 * 1024, # 每个文件最大1MB
backupCount=5 # 保留5个备份
)
# 配置
handler.setLevel(logging.INFO)
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
handler.setFormatter(formatter)
# 使用
logger = logging.getLogger()
logger.addHandler(handler)
# 模拟写入大量日志
for i in range(10000):
logger.info(f"这是第{i}条日志")
按时间轮转
from logging.handlers import TimedRotatingFileHandler
import logging
# 每天轮转一次
handler = TimedRotatingFileHandler(
'app.log',
when='midnight', # 每天午夜
interval=1, # 间隔1天
backupCount=7, # 保留7天
encoding='utf-8'
)
logging.basicConfig(
level=logging.INFO,
handlers=[handler],
format='%(asctime)s - %(levelname)s - %(message)s'
)
实际应用场景
场景1:Web应用日志
import logging
from logging.handlers import RotatingFileHandler
import time
def setup_logging():
"""配置Web应用日志"""
logger = logging.getLogger('webapp')
logger.setLevel(logging.DEBUG)
# 控制台处理器
console = logging.StreamHandler()
console.setLevel(logging.INFO)
# 文件处理器
file_handler = RotatingFileHandler(
'webapp.log',
maxBytes=10 * 1024 * 1024, # 10MB
backupCount=5
)
file_handler.setLevel(logging.DEBUG)
# 错误文件处理器
error_handler = logging.FileHandler('errors.log')
error_handler.setLevel(logging.ERROR)
# 设置格式
formatter = logging.Formatter(
'%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
console.setFormatter(formatter)
file_handler.setFormatter(formatter)
error_handler.setFormatter(formatter)
# 添加处理器
logger.addHandler(console)
logger.addHandler(file_handler)
logger.addHandler(error_handler)
return logger
# 使用
logger = setup_logging()
def handle_request(request):
"""处理HTTP请求"""
start_time = time.time()
logger.info(f"收到请求: {request}")
try:
# 处理请求...
time.sleep(0.1) # 模拟处理时间
response = "OK"
# 记录处理时间
elapsed = (time.time() - start_time) * 1000
logger.info(f"请求处理完成: {response} (耗时{elapsed:.1f}ms)")
return response
except Exception as e:
logger.exception(f"请求处理失败: {e}")
return "ERROR"
# 模拟请求
handle_request("GET /home")
handle_request("POST /login")
场景2:数据分析日志
import logging
import pandas as pd
import numpy as np
class DataAnalyzer:
def __init__(self):
self.logger = logging.getLogger(__name__)
def analyze_data(self, filepath):
"""分析数据文件"""
self.logger.info(f"开始分析文件: {filepath}")
try:
# 读取数据
self.logger.debug("读取CSV文件")
df = pd.read_csv(filepath)
# 数据验证
row_count = len(df)
col_count = len(df.columns)
self.logger.info(f"数据维度: {row_count}行 × {col_count}列")
if row_count == 0:
self.logger.warning("数据文件为空")
return None
# 检查缺失值
missing = df.isnull().sum().sum()
if missing > 0:
self.logger.warning(f"发现{missing}个缺失值")
# 数据分析
self.logger.debug("计算统计信息")
stats = {
'mean': df.select_dtypes(include=[np.number]).mean().to_dict(),
'min': df.min().to_dict(),
'max': df.max().to_dict()
}
self.logger.info("数据分析完成")
return stats
except FileNotFoundError:
self.logger.error(f"文件不存在: {filepath}")
raise
except pd.errors.EmptyDataError:
self.logger.error("文件为空或格式错误")
raise
except Exception as e:
self.logger.exception(f"分析过程出错: {e}")
raise
# 配置日志
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
# 使用
analyzer = DataAnalyzer()
try:
result = analyzer.analyze_data("data.csv")
print(f"分析结果: {result}")
except Exception as e:
print(f"分析失败: {e}")
场景3:监控和警报
import logging
from logging.handlers import SMTPHandler
import psutil
import time
class SystemMonitor:
def __init__(self):
self.logger = logging.getLogger('monitor')
self.setup_logging()
def setup_logging(self):
"""配置日志,包括邮件警报"""
self.logger.setLevel(logging.WARNING)
# 控制台
console = logging.StreamHandler()
console.setLevel(logging.WARNING)
# 文件
file_handler = logging.FileHandler('monitor.log')
file_handler.setLevel(logging.DEBUG)
# 邮件警报(只在严重错误时发送)
mail_handler = SMTPHandler(
mailhost=('smtp.example.com', 587), # 邮件服务器
fromaddr='monitor@example.com', # 发件人
toaddrs=['admin@example.com'], # 收件人
subject='系统监控警报',
credentials=('username', 'password'), # 登录凭证
secure=()
)
mail_handler.setLevel(logging.CRITICAL)
# 设置格式
formatter = logging.Formatter(
'%(asctime)s - %(levelname)s - %(message)s'
)
console.setFormatter(formatter)
file_handler.setFormatter(formatter)
mail_handler.setFormatter(formatter)
self.logger.addHandler(console)
self.logger.addHandler(file_handler)
self.logger.addHandler(mail_handler)
def check_system(self):
"""检查系统状态"""
# CPU使用率
cpu_percent = psutil.cpu_percent(interval=1)
if cpu_percent > 90:
self.logger.warning(f"CPU使用率过高: {cpu_percent}%")
elif cpu_percent > 95:
self.logger.error(f"CPU使用率严重过高: {cpu_percent}%")
# 内存使用率
memory = psutil.virtual_memory()
if memory.percent > 90:
self.logger.critical(f"内存严重不足: {memory.percent}%")
elif memory.percent > 80:
self.logger.warning(f"内存使用率高: {memory.percent}%")
# 磁盘空间
disk = psutil.disk_usage('/')
if disk.percent > 95:
self.logger.critical(f"磁盘空间严重不足: {disk.percent}%")
elif disk.percent > 85:
self.logger.warning(f"磁盘空间不足: {disk.percent}%")
# 网络连接
connections = psutil.net_connections()
established = len([c for c in connections if c.status == 'ESTABLISHED'])
if established > 1000:
self.logger.warning(f"网络连接数过多: {established}")
def run_monitor(self, interval=60):
"""运行监控"""
self.logger.info("系统监控启动")
try:
while True:
self.check_system()
time.sleep(interval)
except KeyboardInterrupt:
self.logger.info("监控停止")
except Exception as e:
self.logger.exception(f"监控出错: {e}")
raise
# 使用
monitor = SystemMonitor()
monitor.run_monitor(interval=300) # 每5分钟检查一次
最佳实践
不要用root logger
# 不好
import logging
logging.info("消息")
# 好
logger = logging.getLogger(__name__)
logger.info("消息")
为不同模块使用不同logger
# database.py
logger = logging.getLogger("app.database")
# api.py
logger = logging.getLogger("app.api")
# auth.py
logger = logging.getLogger("app.auth")
记录有用的信息
# 不好
logger.error("失败")
# 好
logger.error(f"用户登录失败: 用户名={username}, 原因={reason}")
使用恰当的级别
# 用户操作成功 - INFO
logger.info(f"用户{user_id}创建订单{order_id}")
# 可恢复的错误 - WARNING
logger.warning(f"订单{order_id}支付超时,重试中")
# 需要关注的错误 - ERROR
logger.error(f"数据库连接失败,订单{order_id}未保存")
# 致命错误 - CRITICAL
logger.critical(f"内存耗尽,服务即将崩溃")
不要记录敏感信息
# 不好
logger.info(f"用户{username}使用密码{password}登录")
# 好
logger.info(f"用户{username}登录")
logger.debug(f"密码哈希: {password_hash}")
配置文件方式
logging.conf
[loggers]
keys=root,app
[handlers]
keys=consoleHandler,fileHandler,errorHandler
[formatters]
keys=simpleFormatter,detailedFormatter
[logger_root]
level=DEBUG
handlers=consoleHandler
[logger_app]
level=DEBUG
handlers=consoleHandler,fileHandler,errorHandler
qualname=app
propagate=0
[handler_consoleHandler]
class=StreamHandler
level=INFO
formatter=simpleFormatter
args=(sys.stdout,)
[handler_fileHandler]
class=FileHandler
level=DEBUG
formatter=detailedFormatter
args=("app.log", "a")
[handler_errorHandler]
class=FileHandler
level=ERROR
formatter=detailedFormatter
args=("errors.log", "a")
[formatter_simpleFormatter]
format=%(asctime)s - %(levelname)s - %(message)s
datefmt=%Y-%m-%d %H:%M:%S
[formatter_detailedFormatter]
format=%(asctime)s - %(name)s - %(levelname)s - %(filename)s:%(lineno)d - %(message)s
datefmt=%Y-%m-%d %H:%M:%S
在代码中使用配置
import logging.config
# 加载配置文件
logging.config.fileConfig('logging.conf')
# 获取logger
logger = logging.getLogger('app')
# 使用
logger.info("程序启动")
logger.error("发生错误")
常见问题解答
Q1:为什么我的日志没有输出?
import logging
# 可能的原因1:级别设置太高
logging.basicConfig(level=logging.WARNING) # DEBUG和INFO不会输出
# 可能的原因2:重复配置
logging.basicConfig(...) # 只有第一个有效!
logging.basicConfig(...) # 这个不会生效
# 可能的原因3:没有配置
# 直接调用 logging.info() 不会输出,需要先配置
Q2:如何临时关闭日志?
# 方法1:设置很高的级别
logging.disable(logging.CRITICAL) # 关闭所有日志
logging.disable(logging.NOTSET) # 重新开启
# 方法2:关闭特定logger
logger = logging.getLogger('myapp')
logger.disabled = True
Q3:如何记录到多个文件?
import logging
# 创建不同处理器
debug_handler = logging.FileHandler('debug.log')
debug_handler.setLevel(logging.DEBUG)
error_handler = logging.FileHandler('error.log')
error_handler.setLevel(logging.ERROR)
# 配置
logger = logging.getLogger()
logger.addHandler(debug_handler)
logger.addHandler(error_handler)

浙公网安备 33010602011771号