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个标准级别:
代码示例
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即可临时提升日志级别,排查完成后再恢复。
本文涉及AI创作
内容由AI创作,请仔细甄别