python

关注公众号 jb51net

关闭
首页 > 脚本专栏 > python > Python logging模块

Python日志模块logging的新手入门教程

作者:程序员食堂

本文将为大家全面介绍一下Python中的logging模块,从为什么不能用print、四大核心组件、五种日志级别到三种配置方式,并详解日志轮转和常见坑点,帮你构建生产级日志系统

一、为什么不能用 print 代替日志?

很多新手刚学 Python 时,调试代码全靠 print():

print("用户登录成功")
print("数据库连接失败")
print("订单处理完成")

代码量少时没问题,但项目一复杂,print 的缺陷就暴露了:

问题printlogging
无法分级所有信息混在一起五级日志,按需过滤
无法持久化程序结束就没了自动写入文件保存
无法控制输出只能打印到控制台控制台/文件/邮件/网络随意切换
性能差无条件执行字符串拼接级别不够时直接跳过,零开销
无上下文纯字符串,不知道从哪来的时间/模块/行号/级别全记录
多线程不安全输出可能交错混乱内置锁机制,线程安全

一句话总结:print 是往屏幕上吐字符串,logging 是一条完整的"日志生产线"——从产生日志到最终落地,每一步都可控。

二、logging 模块的四大核心组件

Python 的 logging 模块采用分层解耦架构,核心由四大组件构成:

你的代码:logger.info("...")
       │
       ▼
┌───────────────┐  ① Logger:日志入口,级别过滤的第一道关卡
│    Logger     │  getLogger('app.api'),命名空间支持层级继承
└───────┬───────┘
        │
        ▼
┌───────────────┐  ② Filter:比级别更细的过滤,也可给 record 注入字段
│    Filter     │  addFilter()
└───────┬───────┘
        │
        ▼
┌───────────────┐  ③ Handler:决定日志"去哪",级别过滤的第二道关卡
│    Handler    │  Stream / File / Rotating / SMTP / Socket / Queue
└───────┬───────┘
        │
        ▼
┌───────────────┐  ④ Formatter:决定日志"长什么样"
│   Formatter   │  %(asctime)s %(name)s %(levelname)s %(message)s
└───────┬───────┘
        │
        ▼
  控制台 / 文件 / 网络 / 邮件 / 消息队列

1. Logger(日志器)—— 入口和控制中心

Logger 是你直接打交道的对象,主要职责包括:

import logging

# 推荐用 __name__ 作为名称,自动形成层级结构
logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)  # 设置最低日志级别

2. Handler(处理器)—— 决定日志去哪

Handler 决定日志的输出目标,一个 Logger 可以绑定多个 Handler:

Handler用途
StreamHandler输出到控制台
FileHandler输出到文件
RotatingFileHandler按文件大小轮转,防止日志撑爆磁盘
TimedRotatingFileHandler按时间轮转(每天/每小时)
SMTPHandler发送邮件通知
HTTPHandler发送到远程服务器
QueueHandler异步队列处理,适合高并发场景
NullHandler库开发用,避免未配置时输出到 stderr

3. Formatter(格式化器)—— 决定日志长什么样

Formatter 定义日志的输出格式,通过占位符配置:

占位符含义
%(asctime)s日志产生时间
%(name)s日志器名称
%(levelname)s日志级别名称
%(message)s日志内容
%(filename)s产生日志的文件名
%(lineno)d产生日志的代码行号
%(funcName)s产生日志的函数名

4. Filter(过滤器)—— 精细化筛选

Filter 用于比级别更细粒度的过滤,可基于名称、自定义条件等进行筛选,通过 addFilter() 挂载到 Logger 或 Handler 上。

三、五种标准日志级别

Logger 定义了五个标准级别,数值越大越严重:

级别数值用途开发环境生产环境
DEBUG10调试细节,记录变量值、执行流程开启关闭
INFO20正常流程,确认程序按预期运行开启开启
WARNING30潜在问题,但不影响运行开启开启
ERROR40发生错误,功能受影响开启开启
CRITICAL50严重故障,可能导致程序崩溃开启开启

默认级别为 WARNING,只有 ≥ 设定级别的日志才会被处理。

四、Logger 的内部执行流程

当调用 logger.info("msg") 时,内部流程如下:

调用 Logger 方法
    ↓
创建 LogRecord 对象
    ↓
Logger 级别过滤(是否 ≥ 设定级别?)
    ↓
Filter 过滤(自定义筛选条件)
    ↓
分发至所有绑定的 Handler
    ↓
Handler 级别过滤 → Formatter 格式化 → emit() 输出

关键规则:日志级别需同时 ≥ Logger 级别和 Handler 级别时才会输出。

五、从入门到进阶:三种配置方式

方式一:基础配置(适合简单脚本)

使用 logging.basicConfig() 一行搞定:

import logging

logging.basicConfig(
    level=logging.DEBUG,
    format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
    datefmt="%Y-%m-%d %H:%M:%S",
    filename="app.log",       # 输出到文件
    filemode="a",             # 追加模式
    encoding="utf-8"          # 避免中文乱码
)

logger = logging.getLogger(__name__)
logger.info("服务启动成功")

️ basicConfig() 仅在首次调用时生效,第二次调用无效。

方式二:手动组装(适合正式项目)

显式创建 Logger、Handler 和 Formatter,灵活组合:

import logging

def setup_logger():
    # 1. 创建 Logger
    logger = logging.getLogger(__name__)
    logger.setLevel(logging.DEBUG)
    logger.propagate = False  # 禁止向上传播,避免重复输出

    # 2. 创建控制台 Handler
    console_handler = logging.StreamHandler()
    console_handler.setLevel(logging.INFO)  # 控制台只输出 INFO 及以上

    # 3. 创建文件 Handler
    file_handler = logging.FileHandler("app.log", encoding="utf-8")
    file_handler.setLevel(logging.DEBUG)  # 文件记录所有级别

    # 4. 创建 Formatter
    formatter = logging.Formatter(
        "%(asctime)s | %(levelname)-8s | %(name)s | %(filename)s:%(lineno)d | %(message)s",
        datefmt="%Y-%m-%d %H:%M:%S"
    )

    # 5. 绑定 Formatter 到 Handler
    console_handler.setFormatter(formatter)
    file_handler.setFormatter(formatter)

    # 6. 绑定 Handler 到 Logger
    logger.addHandler(console_handler)
    logger.addHandler(file_handler)

    return logger

# 使用
logger = setup_logger()
logger.debug("调试信息")
logger.info("正常信息")
logger.error("错误信息", exc_info=True)  # 记录完整异常堆栈

方式三:字典配置(适合大型项目)

通过 logging.config.dictConfig() 加载外部配置,支持 JSON/YAML 格式:

import logging.config

config = {
    "version": 1,
    "disable_existing_loggers": False,
    "formatters": {
        "standard": {
            "format": "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
        }
    },
    "handlers": {
        "console": {
            "class": "logging.StreamHandler",
            "level": "DEBUG",
            "formatter": "standard",
            "stream": "ext://sys.stdout"
        },
        "file": {
            "class": "logging.FileHandler",
            "level": "INFO",
            "formatter": "standard",
            "filename": "app.log",
            "encoding": "utf8"
        }
    },
    "loggers": {
        "my_app": {
            "level": "DEBUG",
            "handlers": ["console", "file"],
            "propagate": False
        }
    }
}

logging.config.dictConfig(config)
logger = logging.getLogger("my_app")

六、常用方法速查

方法说明
logger.debug(msg)记录 DEBUG 级别日志
logger.info(msg)记录 INFO 级别日志
logger.warning(msg)记录 WARNING 级别日志
logger.error(msg)记录 ERROR 级别日志
logger.critical(msg)记录 CRITICAL 级别日志
logger.exception(msg)记录 ERROR 级别 + 完整异常堆栈(等价于 error(msg, exc_info=True))
logger.log(level, msg)动态指定日志级别

记录变量的正确写法(使用 %s 占位符,而非 f-string):

#  推荐:延迟格式化,性能更好
logger.info("用户 %s 登录成功,ID: %d", username, user_id)

#  不推荐:f-string 会无条件计算
logger.info(f"用户 {username} 登录成功,ID: {user_id}")

七、日志轮转:避免日志文件撑爆磁盘

当程序长期运行时,单一日志文件会越来越大。logging.handlers 提供了两种轮转机制:

按文件大小轮转(RotatingFileHandler)

from logging.handlers import RotatingFileHandler

handler = RotatingFileHandler(
    "app.log",
    maxBytes=10 * 1024 * 1024,  # 单个文件最大 10MB
    backupCount=5,               # 保留最近 5 个历史文件
    encoding="utf-8"
)

按时间轮转(TimedRotatingFileHandler)

from logging.handlers import TimedRotatingFileHandler

handler = TimedRotatingFileHandler(
    "app.log",
    when="D",       # 轮转单位:S(秒)、M(分)、H(时)、D(天)、midnight(午夜)
    interval=1,     # 间隔时间
    backupCount=7,  # 保留最近 7 天日志
    encoding="utf-8"
)

八、新手常见坑点

坑点原因解决方案
debug 日志不输出默认级别为 WARNING手动调用 logger.setLevel(logging.DEBUG)
日志重复打印多次 addHandler 或 propagate 未关闭调用 logger.handlers.clear() 或设置 propagate=False
日志文件乱码未指定编码FileHandler 中指定 encoding="utf-8"
设置级别无效只给 Logger 设了级别,没给 Handler 设Logger 和 Handler 都要设置级别
f-string 性能差即使级别不够也会执行字符串拼接改用 %s 占位符

九、最佳实践总结

用 __name__ 命名 Logger:便于追踪日志来源,自动形成层级结构

避免重复配置:不要在每个模块调用 basicConfig(),应在应用入口集中配置

库开发加 NullHandler:避免未配置时日志输出到 stderr

生产环境慎用 DEBUG:高频路径输出 DEBUG 日志会影响性能

使用 exc_info=True 或 exception():记录完整异常堆栈,是排障的关键信息

用 %s 占位符而非 f-string:避免不必要的性能开销

设置 propagate=False:避免日志被传递到 root Logger,导致重复输出

十、完整生产级示例

import logging
import logging.handlers
import json
from pathlib import Path


class JsonFormatter(logging.Formatter):
    """JSON 格式化器,便于 ELK/Loki 采集分析"""
    def format(self, record):
        log_data = {
            "time": self.formatTime(record),
            "level": record.levelname,
            "logger": record.name,
            "message": record.getMessage(),
            "module": record.module,
            "line": record.lineno,
        }
        if record.exc_info:
            log_data["exception"] = self.formatException(record.exc_info)
        return json.dumps(log_data, ensure_ascii=False)


def setup_production_logging(log_dir="logs"):
    """配置生产级日志体系"""
    log_path = Path(log_dir)
    log_path.mkdir(exist_ok=True)

    root_logger = logging.getLogger()
    root_logger.setLevel(logging.DEBUG)

    # 统一格式
    fmt = logging.Formatter(
        "%(asctime)s | %(levelname)-8s | %(name)s | %(message)s",
        datefmt="%Y-%m-%d %H:%M:%S"
    )

    # 控制台 Handler:只输出 WARNING 及以上
    console = logging.StreamHandler()
    console.setLevel(logging.WARNING)
    console.setFormatter(fmt)

    # 文件 Handler:输出所有级别,按大小轮转
    file_handler = logging.handlers.RotatingFileHandler(
        log_path / "app.log",
        maxBytes=10 * 1024 * 1024,  # 10MB 轮转
        backupCount=5,
        encoding="utf-8",
    )
    file_handler.setLevel(logging.DEBUG)
    file_handler.setFormatter(fmt)

    # 错误专用 Handler
    error_handler = logging.handlers.RotatingFileHandler(
        log_path / "error.log",
        maxBytes=5 * 1024 * 1024,
        backupCount=3,
        encoding="utf-8",
    )
    error_handler.setLevel(logging.ERROR)
    error_handler.setFormatter(fmt)

    root_logger.addHandler(console)
    root_logger.addHandler(file_handler)
    root_logger.addHandler(error_handler)


# 使用
setup_production_logging()
logger = logging.getLogger(__name__)

logger.info("应用启动")
logger.error("请求失败", exc_info=True)

核心思想:Logger 负责"记录什么",Handler 负责"输出到哪",Formatter 负责"长什么样"。这种解耦设计让日志系统既灵活又可扩展。掌握这套体系,你就从"用 print 调试"的新手,迈向了"用 logging 构建可观测系统"的工程师。

以上就是Python日志模块logging的新手入门教程的详细内容,更多关于Python logging模块的资料请关注脚本之家其它相关文章!

您可能感兴趣的文章:
阅读全文