pin_drop当前位置:知识文库 ❯ 图文

Python日志系统logging - Logger配置与最佳实践完整指南

一、为什么使用logging而不是print

在Python开发中,很多初学者习惯使用print()来调试和输出信息。但在生产环境中,logging模块是更专业、更强大的选择。

logging相比print的优势:

  • 日志级别:可以按严重程度分类(DEBUG、INFO、WARNING、ERROR、CRITICAL)

  • 灵活输出:可同时输出到控制台、文件、远程服务器等多个目标

  • 格式控制:自动添加时间戳、文件名、行号、函数名等上下文信息

  • 线程安全:logging是线程安全的,适合多线程环境

  • 可配置性:通过配置文件或代码灵活调整日志行为,无需修改业务逻辑

小贴士

在开发调试阶段,你可以将日志级别设为DEBUG来查看详细运行信息;而在生产环境中,将级别调整为WARNING以上,即可自动过滤掉调试和常规信息,只保留警告和错误。这一切只需修改一行配置,无需改动任何业务代码。


二、Logger、Handler、Formatter核心概念

Python的logging模块由四个核心组件构成:Logger、Handler、Formatter和Filter。理解它们之间的关系是掌握日志系统的关键。

Logger(记录器)

Logger是应用程序直接使用的接口,负责创建日志记录。每个Logger都有一个名称,通常使用__name__来命名,这样可以形成层级结构。

代码示例

import logging

# 创建一个名为"my_app"的Logger
logger = logging.getLogger(__name__)

# 设置Logger的最低日志级别
logger.setLevel(logging.DEBUG)

Handler(处理器)

Handler决定日志输出的目标位置。一个Logger可以有多个Handler,分别将日志发送到不同的地方。

代码示例

import logging

logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)

# 控制台Handler - 输出到终端
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.INFO)

# 文件Handler - 输出到文件
file_handler = logging.FileHandler("app.log", encoding="utf-8")
file_handler.setLevel(logging.DEBUG)

# 按大小轮转的文件Handler
from logging.handlers import RotatingFileHandler
rotating_handler = RotatingFileHandler(
    "app_rotating.log",
    maxBytes=10*1024*1024,  # 10MB
    backupCount=5,
    encoding="utf-8"
)

# 按时间轮转的文件Handler
from logging.handlers import TimedRotatingFileHandler
timed_handler = TimedRotatingFileHandler(
    "app_timed.log",
    when="midnight",
    interval=1,
    backupCount=30,
    encoding="utf-8"
)

# 将Handler添加到Logger
logger.addHandler(console_handler)
logger.addHandler(file_handler)
logger.addHandler(rotating_handler)

Formatter(格式化器)

Formatter控制日志消息的最终输出格式。常用的格式化占位符包括:

代码示例

# 定义日志格式
formatter = logging.Formatter(
    fmt="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
    datefmt="%Y-%m-%d %H:%M:%S"
)

# 常用格式化占位符:
# %(asctime)s    - 人类可读的时间,如 2026-06-13 14:30:00
# %(created)f    - 创建日志的Unix时间戳
# %(filename)s   - 文件名(不含路径)
# %(funcName)s   - 调用日志的函数名
# %(levelname)s  - 日志级别名称(DEBUG, INFO, WARNING等)
# %(levelno)s    - 日志级别数字
# %(lineno)d     - 调用日志的行号
# %(module)s     - 模块名
# %(name)s       - Logger名称
# %(message)s    - 日志消息内容
# %(pathname)s   - 文件的完整路径

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

# 完整示例
import logging

logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)

handler = logging.StreamHandler()
formatter = logging.Formatter(
    "%(asctime)s | %(levelname)-8s | %(name)s:%(lineno)d - %(message)s"
)
handler.setFormatter(formatter)
logger.addHandler(handler)

logger.info("这是一条INFO日志")
logger.error("这是一条ERROR日志")
# 输出示例:
# 2026-06-13 14:30:00 | INFO     | __main__:25 - 这是一条INFO日志
# 2026-06-13 14:30:00 | ERROR    | __main__:26 - 这是一条ERROR日志

三、日志级别详解

日志级别用于区分日志消息的重要程度。Python logging提供了5个标准级别:

日志级别 数值 使用场景 示例
DEBUG 10 调试信息,详细的诊断数据 变量值、函数调用参数
INFO 20 正常运行信息,确认一切如预期 服务启动、任务完成
WARNING 30 警告信息,可能出现问题但仍能继续 配置项缺失使用默认值
ERROR 40 错误信息,某些功能无法执行 文件不存在、网络请求失败
CRITICAL 50 严重错误,程序可能无法继续运行 数据库连接完全中断

代码示例

import logging

logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger(__name__)

logger.debug("这是DEBUG日志 - 仅用于调试")
logger.info("这是INFO日志 - 程序正常运行信息")
logger.warning("这是WARNING日志 - 需要注意的警告")
logger.error("这是ERROR日志 - 发生了错误")
logger.critical("这是CRITICAL日志 - 严重错误")

# 日志级别的工作方式:
# 如果Logger的级别设为WARNING,则只有WARNING、ERROR、CRITICAL会输出
# DEBUG和INFO会被自动过滤

四、配置文件化日志系统

使用配置文件管理日志是最佳实践,它允许你在不修改代码的情况下调整日志行为。Python支持YAML、JSON和字典三种配置方式。

字典配置方式

代码示例

import logging
import logging.config

# 日志配置字典
LOGGING_CONFIG = {
    "version": 1,
    "disable_existing_loggers": False,
    "formatters": {
        "standard": {
            "format": "%(asctime)s | %(levelname)-8s | %(name)s:%(lineno)d - %(message)s",
            "datefmt": "%Y-%m-%d %H:%M:%S"
        },
        "detailed": {
            "format": "%(asctime)s | %(levelname)-8s | %(name)s | %(funcName)s:%(lineno)d | %(message)s",
            "datefmt": "%Y-%m-%d %H:%M:%S"
        },
        "simple": {
            "format": "%(levelname)s - %(message)s"
        }
    },
    "handlers": {
        "console": {
            "class": "logging.StreamHandler",
            "level": "INFO",
            "formatter": "standard",
            "stream": "ext://sys.stdout"
        },
        "file": {
            "class": "logging.handlers.RotatingFileHandler",
            "level": "DEBUG",
            "formatter": "detailed",
            "filename": "logs/app.log",
            "maxBytes": 10485760,
            "backupCount": 5,
            "encoding": "utf8"
        },
        "error_file": {
            "class": "logging.handlers.RotatingFileHandler",
            "level": "ERROR",
            "formatter": "detailed",
            "filename": "logs/error.log",
            "maxBytes": 10485760,
            "backupCount": 5,
            "encoding": "utf8"
        }
    },
    "loggers": {
        "my_app": {
            "level": "DEBUG",
            "handlers": ["console", "file", "error_file"],
            "propagate": False
        },
        "my_app.database": {
            "level": "INFO",
            "handlers": ["file"],
            "propagate": False
        }
    },
    "root": {
        "level": "WARNING",
        "handlers": ["console"]
    }
}

# 应用配置
logging.config.dictConfig(LOGGING_CONFIG)

# 使用Logger
logger = logging.getLogger("my_app")
logger.info("日志系统配置完成")

YAML配置方式

代码示例

# logging_config.yaml
version: 1
disable_existing_loggers: false

formatters:
  standard:
    format: "%(asctime)s | %(levelname)-8s | %(name)s:%(lineno)d - %(message)s"
  simple:
    format: "%(levelname)s - %(message)s"

handlers:
  console:
    class: logging.StreamHandler
    level: INFO
    formatter: standard
    stream: ext://sys.stdout
  file:
    class: logging.handlers.TimedRotatingFileHandler
    level: DEBUG
    formatter: standard
    filename: logs/app.log
    when: midnight
    backupCount: 30
    encoding: utf8

loggers:
  my_app:
    level: DEBUG
    handlers: [console, file]
    propagate: false

root:
  level: WARNING
  handlers: [console]

---

# Python加载YAML配置
import yaml
import logging.config

with open("logging_config.yaml", "r", encoding="utf-8") as f:
    config = yaml.safe_load(f)

logging.config.dictConfig(config)

logger = logging.getLogger("my_app")
logger.info("从YAML配置加载日志系统")

五、完整项目日志示例

下面是一个实际项目中的日志使用示例,展示了如何在一个数据处理应用中正确使用日志系统:

代码示例

# app/__init__.py
import logging
import logging.config
from pathlib import Path

def setup_logging():
    """初始化项目日志系统"""
    # 确保日志目录存在
    log_dir = Path("logs")
    log_dir.mkdir(exist_ok=True)
    
    LOGGING_CONFIG = {
        "version": 1,
        "disable_existing_loggers": False,
        "formatters": {
            "standard": {
                "format": "%(asctime)s | %(levelname)-8s | %(name)s:%(lineno)d - %(message)s",
            },
        },
        "handlers": {
            "console": {
                "class": "logging.StreamHandler",
                "level": "INFO",
                "formatter": "standard",
            },
            "file": {
                "class": "logging.handlers.RotatingFileHandler",
                "level": "DEBUG",
                "formatter": "standard",
                "filename": "logs/app.log",
                "maxBytes": 10 * 1024 * 1024,
                "backupCount": 5,
                "encoding": "utf-8",
            },
        },
        "loggers": {
            "app": {
                "level": "DEBUG",
                "handlers": ["console", "file"],
                "propagate": False,
            },
        },
    }
    
    logging.config.dictConfig(LOGGING_CONFIG)


# app/main.py
import logging
from app import setup_logging

logger = logging.getLogger(__name__)

def process_data(data):
    """处理数据"""
    logger.info("开始处理 %d 条数据", len(data))
    
    for i, item in enumerate(data):
        logger.debug("处理第 %d 条数据: %s", i + 1, item)
        try:
            # 实际处理逻辑
            result = item * 2
            logger.debug("处理结果: %s", result)
        except Exception as e:
            logger.error("处理第 %d 条数据时出错: %s", i + 1, str(e), exc_info=True)
            continue
    
    logger.info("数据处理完成")
    return result

def main():
    setup_logging()
    logger.info("应用程序启动")
    
    try:
        data = [1, 2, 3, 4, 5]
        result = process_data(data)
        logger.info("最终结果: %s", result)
    except Exception as e:
        logger.critical("应用程序发生严重错误: %s", str(e), exc_info=True)
        raise
    finally:
        logger.info("应用程序退出")

if __name__ == "__main__":
    main()

代码示例

# 运行程序后的日志输出示例:
# 2026-06-13 14:30:00 | INFO     | app.main:32 - 应用程序启动
# 2026-06-13 14:30:00 | INFO     | app.main:15 - 开始处理 5 条数据
# 2026-06-13 14:30:00 | INFO     | app.main:29 - 数据处理完成
# 2026-06-13 14:30:00 | INFO     | app.main:35 - 最终结果: 10
# 2026-06-13 14:30:00 | INFO     | app.main:39 - 应用程序退出

六、注意事项与最佳实践

注意1:使用logging.getLogger(__name__)而非logging.getLogger()。使用__name__可以让Logger名称与模块路径一致,便于在日志中定位问题来源,也方便针对不同模块设置不同的日志级别。

注意2:使用参数化日志而非字符串拼接。应该写logger.info("处理 %d 条数据", count)而不是logger.info(f"处理 {count} 条数据")。前者在日志级别被过滤时不会执行字符串格式化,性能更好。

小贴士:异常日志的正确写法

记录异常时,使用logger.error("错误信息", exc_info=True)而不是手动拼接traceback。exc_info=True会自动附加完整的异常堆栈信息,包括异常类型、消息和调用栈,这对于排查生产环境问题至关重要。也可以使用logger.exception("错误信息"),它等价于logger.error(..., exc_info=True),但只能在except块中使用。


七、小结

  • 使用logging替代print:logging提供日志级别、多目标输出、格式控制等强大功能

  • 理解四大组件:Logger负责创建日志,Handler负责输出目标,Formatter负责输出格式

  • 使用配置文件:通过dictConfig或YAML配置日志,实现配置与代码分离

  • 参数化日志消息:使用logger.info("msg %s", arg)而非f-string,提升性能


八、练习题

练习1

编写一个日志配置模块,同时输出日志到控制台和文件,要求文件按天轮转保留30天,控制台只显示WARNING以上级别,使用字典配置方式实现。

练习2

编写一个模拟的Web服务程序,使用logging记录请求处理过程。要求包含请求开始、处理完成、异常捕获等日志,并为不同模块(路由、数据库、认证)设置不同的Logger。

常见问题

logging和print有什么区别?为什么不用print?

logging相比print有诸多优势:支持分级输出(DEBUG/INFO/WARNING/ERROR/CRITICAL),可以灵活配置输出目标(控制台、文件、网络),自动添加时间戳和上下文信息,线程安全,且可以通过配置动态调整日志级别而无需修改代码。在生产环境中,logging是专业标准。

如何避免日志文件无限增长占满磁盘?

使用RotatingFileHandler或TimedRotatingFileHandler。RotatingFileHandler按文件大小轮转(如达到10MB时创建新文件),TimedRotatingFileHandler按时间轮转(如每天零点创建新文件)。两者都可以设置backupCount参数,自动删除过期的旧日志文件。

logger.info("msg %s", arg)和logger.info(f"msg {arg}")哪个好?

推荐使用logger.info("msg %s", arg)。因为当该日志级别被过滤时(例如INFO级别设为WARNING时),前者不会执行字符串格式化操作,性能更好。而f-string无论日志是否输出都会先执行格式化,造成不必要的性能开销。

如何在生产环境中动态修改日志级别?

可以通过信号处理或HTTP接口实现动态修改。例如监听SIGUSR1信号:signal.signal(signal.SIGUSR1, lambda s,f: logging.getLogger().setLevel(logging.DEBUG))。这样在需要排查问题时,发送kill -USR1 PID即可临时提升日志级别,排查完成后再恢复。

标签: Python logging 日志系统 Logger配置 Handler Formatter Python最佳实践

本文涉及AI创作

内容由AI创作,请仔细甄别

list快速访问

上一篇: Python项目结构规范 - pyproject.toml配置与目录组织完整指南 下一篇: Python配置文件管理 - INI/YAML/TOML与环境变量最佳实践

poll相关推荐