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)
posted @ 2026-04-28 21:15  MKYC  阅读(43)  评论(0)    收藏  举报