日志记录logging模块用法?

访客 python案例 3

Python日志记录终极指南:logging模块从入门到最佳实践

目录导读

  1. 为什么需要日志记录? —— 理解日志在开发中的核心价值
  2. logging模块基础架构 —— 四大核心组件解析
  3. 快速上手:5分钟配置基础日志 —— 从零开始的实战代码
  4. 日志级别与过滤机制 —— DEBUG到CRITICAL的正确使用姿势
  5. 将日志写入文件 —— 文件Handler与轮转策略
  6. 日志格式化进阶 —— 时间、行号、函数名的优雅展示
  7. 多模块日志管理 —— 避免日志重复的命名空间方案
  8. 常见问题与避坑指南 —— 10个必知问答
  9. 生产环境最佳实践 —— 性能、安全与监控联动

为什么需要日志记录?

在软件开发中,日志记录(Logging)是程序运行时的“黑匣子”,它帮助我们追踪错误、分析性能、审计操作,与简单的print()语句相比,logging模块提供:

  • 级别控制:按严重程度过滤信息,开发时输出调试日志,生产只记录警告及以上
  • 持久化存储:将日志写入文件、数据库或远程服务
  • 线程安全:多线程环境下不会出现乱序或数据竞争
  • 可扩展性:自定义Handler、Filter、Formatter

:用print()打印日志和logging模块的主要区别是什么?
:print是标准输出,不能控制级别、不能自动写入文件、生产环境难以管理,logging模块可将日志分级别输出到不同目标(控制台+文件+远程),且支持如RotatingFileHandler自动分割日志文件,避免日志文件无限增长。


logging模块基础架构

Python的logging模块采用组件化设计,由4个核心类构成:

组件 作用 典型用法
Logger 日志记录器(应用程序调用的入口) logger = logging.getLogger(__name__)
Handler 日志处理器(决定日志输出到哪里) StreamHandler(控制台)、FileHandler(文件)
Formatter 日志格式器(定义日志内容的格式) '%(asctime)s - %(levelname)s - %(message)s'
Filter 过滤器(按条件过滤日志记录) 自定义函数过滤特定模块的日志

架构关系:一个Logger可以添加多个Handler,每个Handler绑定一个Formatter和可选的Filter,日志消息从Logger发出,经过Filter筛选,最后由Handler按格式输出。


快速上手:5分钟配置基础日志

最简单的配置——同时输出到控制台和文件:

import logging
# 创建Logger(建议以模块名命名)
logger = logging.getLogger('my_app')
logger.setLevel(logging.DEBUG)  # 设置最低日志级别
# Handler 1: 控制台输出
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.INFO)  # 控制台只显示INFO及以上
# Handler 2: 文件输出
file_handler = logging.FileHandler('app.log', encoding='utf-8')
file_handler.setLevel(logging.DEBUG)
# 设置格式器
formatter = logging.Formatter(
    '%(asctime)s - %(name)s - %(levelname)s - %(message)s',
    datefmt='%Y-%m-%d %H:%M:%S'
)
console_handler.setFormatter(formatter)
file_handler.setFormatter(formatter)
# 将Handler添加到Logger
logger.addHandler(console_handler)
logger.addHandler(file_handler)
# 测试
logger.debug("这是DEBUG级别(仅写入文件)")
logger.info("这是INFO级别(控制台+文件都会记录)")
logger.warning("警告消息")
logger.error("错误消息")

执行后,控制台会显示INFO及以上内容,文件app.log则记录所有DEBUG及以上日志。

:如果不设置Logger.setLevel,会发生什么?
:Logger默认级别为WARNING,即只有WARNING及更高级别的日志才会被处理,即使Handler设置了INFO,如果Logger本身级别为WARNING,DEBUG和INFO消息会被Logger直接丢弃。


日志级别与过滤机制

logging模块定义了5个标准级别(从低到高):

级别 数值 典型场景
DEBUG 10 调试信息,如变量值、函数调用细节
INFO 20 确认程序正常运行,如“服务启动成功”
WARNING 30 潜在问题,如“磁盘空间不足90%”
ERROR 40 功能失效,如“数据库连接失败”
CRITICAL 50 严重错误,如“系统无法继续运行”

过滤机制:消息通过两个过滤器——Logger的级别和Handler的级别,最终只有同时通过两个过滤器的消息才会被输出到对应目标。

自定义级别:可通过logging.addLevelName(15, "TRACE")增加自定义级别,但建议优先使用标准级别。


将日志写入文件——Handler详解

除了基础的FileHandler,生产环境更推荐使用带轮转功能的Handler:

RotatingFileHandler(按文件大小轮转)

from logging.handlers import RotatingFileHandler
handler = RotatingFileHandler(
    'app.log',           # 文件名
    maxBytes=10485760,   # 单个文件最大10MB
    backupCount=5,       # 保留5个备份文件
    encoding='utf-8'
)

TimedRotatingFileHandler(按时间轮转)

from logging.handlers import TimedRotatingFileHandler
handler = TimedRotatingFileHandler(
    'app.log',
    when='midnight',     # 每天午夜新建日志
    interval=1,
    backupCount=7        # 保留7天日志
)

注意:日志轮转能避免单一日志文件无限膨胀,但在高并发写入时,轮转操作可能丢失少量日志,可配合QueueHandler实现异步写入。

:FileHandler默认使用什么编码?中文日志乱码如何解决?
:FileHandler默认使用系统区域编码(如Windows的GBK),建议始终指定encoding='utf-8',并修改Formatter的encoding参数(如果支持)。


日志格式化进阶

常用格式占位符

formatter = logging.Formatter(
    '%(asctime)s - %(name)s - %(levelname)s - '
    '%(pathname)s:%(lineno)d - %(funcName)s - %(message)s'
)
# 输出示例:2025-04-07 14:30:15 - my_app - ERROR - /home/user/app.py:32 - connect_db - 连接超时

让格式更具可读性——颜色输出

使用第三方库colorlog为不同级别添加颜色(控制台Handler专用):

import colorlog
handler = logging.StreamHandler()
handler.setFormatter(colorlog.ColoredFormatter(
    '%(log_color)s%(levelname)-8s%(reset)s %(blue)s%(message)s',
    log_colors={
        'DEBUG': 'cyan',
        'INFO': 'green',
        'WARNING': 'yellow',
        'ERROR': 'red',
        'CRITICAL': 'red,bg_white',
    }
))

多模块日志管理

在大型项目中,推荐按模块名创建Logger,继承根Logger的配置:

# 模块 user_service.py
import logging
logger = logging.getLogger(__name__)  # 自动获取模块名
def create_user():
    logger.info("创建用户成功")

配置根Logger:在入口文件中配置一次,所有模块自动继承:

# main.py
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
    handlers=[
        logging.StreamHandler(),
        logging.FileHandler('app.log', encoding='utf-8')
    ]
)

命名空间技巧:使用logging.getLogger('myapp.user_service')可对特定子模块单独设置级别。

:为什么我的日志会重复输出多次?
:常见原因是多次配置Handler或多次调用logging.basicConfig,建议在程序入口处仅配置一次根Logger,子模块通过logging.getLogger(__name__)获取Logger(不添加Handler),若仍重复,检查是否在导入模块时触发了配置代码。


常见问题与避坑指南(10个必知问答)

Q1:logging模块是线程安全的吗?
A:是的,默认情况下,logging模块自带线程锁,但多个进程同时写入同一文件时不安全,多进程场景建议使用QueueHandler+QueueListenerConcurrentLogHandler

Q2:如何记录异常堆栈信息?
A:使用logger.exception("描述")自动包含当前异常的完整堆栈,等同于logger.error("描述", exc_info=True)

Q3:日志文件太大如何快速定位问题?
A:结合日志轮转+按模块/级别分区,也可使用logging.handlers.SMTPHandler当ERROR出现时发送邮件报警。

Q4:生产环境应该记录哪些级别?
A:推荐INFO及以上(记录关键流程),ERROR必须记录详细堆栈,DEBUG仅在调试时开启,避免影响性能。

Q5:如何禁用第三方库的日志?
A:设置特定Logger的级别为WARNING或ERROR,如logging.getLogger('urllib3').setLevel(logging.WARNING)

Q6:日志记录会影响程序性能吗?
A:有影响,特别是大量DEBUG日志,建议使用模块级别的条件判断包裹高频日志:if logger.isEnabledFor(logging.DEBUG): logger.debug(...)

Q7:如何在日志中记录请求ID(用于分布式追踪)?
A:使用logging.LoggerAdapter或自定义Filter,在filter()方法中为日志记录添加request_id字段。

Q8:日志格式中的%(name)s代表什么?
A:当前Logger的名称,推荐使用__name__(模块全路径),方便定位日志来源。

Q9logging.basicConfig和手动配置Handler有何区别?
AbasicConfig是便捷方法,仅调用一次,如果需要多个Handler或复杂的Filter,建议手动创建Logger和Handler。

Q10:日志中不应包含哪些信息?
A:禁止记录密码、Token、身份证号等敏感信息,可在Formatter中使用自定义过滤器脱敏。


生产环境最佳实践

异步日志处理

使用QueueHandler+QueueListener将日志写入从主线程分离:

import logging
from logging.handlers import QueueHandler, QueueListener
from queue import Queue
log_queue = Queue(-1)
queue_handler = QueueHandler(log_queue)
file_handler = logging.FileHandler('app.log')
listener = QueueListener(log_queue, file_handler)
logger = logging.getLogger()
logger.addHandler(queue_handler)
listener.start()  # 在独立线程中写入日志
# 程序结束时需调用 listener.stop()

JSON结构化日志

便于日志分析系统(如ELK Stack)解析:

import json
class JsonFormatter(logging.Formatter):
    def format(self, record):
        log_record = {
            'time': self.formatTime(record),
            'level': record.levelname,
            'module': record.name,
            'message': record.getMessage(),
            'extra': getattr(record, 'extra_data', {})
        }
        return json.dumps(log_record, ensure_ascii=False)

环境适应性配置

通过环境变量控制日志级别:

import os
LOG_LEVEL = os.getenv('LOG_LEVEL', 'INFO').upper()
logging.basicConfig(level=getattr(logging, LOG_LEVEL, logging.INFO))

防止日志注入攻击

自定义Filter过滤换行符等特殊字符:

class SanitizeFilter(logging.Filter):
    def filter(self, record):
        record.msg = record.msg.replace('\n', ' ').replace('\r', ' ')
        return True

日志记录是软件可靠性的基石,掌握logging模块的组件化设计、级别控制、多Handler配置、轮转策略以及生产环境优化技巧,能显著提升系统的可观测性和排错效率,建议从简单配置起步,逐步引入异步写入、JSON格式和远程采集,让日志成为你运维排查的得力助手。

(全文完)

标签: 日志模块 基本配置

抱歉,评论功能暂时关闭!