Python 语言进阶 —— 从 logging 到 loguru
认识日志
入门 Python 学习的第一个程序就是 Hello World:
print('Hello World!')
通过使用 print
语句输出 Hello World
,这就是一个最简单的日志记录。在软件开发中,日志记录是至关重要的一环,帮忙开发者、运维者了解程序是否在正常运行。通过分析日志,我们可以了解系统的运行状态,及时发现潜在问题,防患于未然。甚至日志可以记录系统的操作行为,帮助我们更好地理解用户需求,优化产品体验。
日志框架
在 Python 生态系统中,日志记录框架的选择丰富多样,每种框架都有其独特的优势和适用场景。以下介绍几种常用的日志框架,并对它们的优劣进行分析:
框架 | 优点 | 缺点 | 使用场景 |
---|---|---|---|
logging | 1. 无需额外安装 2. 功能强大 3. 灵活度高 | 1. 配置复杂 2. 功能有限(不支持异步、结构化日志等) 3. 代码冗余 | 1. 简单的日志记录需求 2. 依赖 Python 标准库的项目 |
loguru | 1. 简洁易用 2. 功能强大(支持异步、结构化日志、彩色输出等) 3. 代码简洁 | 1. 需要额外安装 2. 灵活性相对较低 | 1. 需要快速上手的项目 2. 对日志功能要求较高的项目 |
structlog | 1. 专注于结构化日志记录 2. 灵活度高(易于扩展和集成) | 1. 学习成本较高 2. 配置复杂 | 1. 需要结构化日志记录的项目 2. 需要与其他日志框架集成的场景 |
Sentry | 1. 错误追踪和日志记录结合 2. 实时监控和告警功能 3. 集成方便(支持多种语言和框架) | 1. 需要额外安装和配置 2. 免费版功能有限,费用较高 | 1. 需要错误追踪和监控的项目 2. 中大型项目或对稳定性要求高的场景 |
其中,logging
和 loguru
在 Python 项目中使用得最为广泛。因此,本文着重介绍这两个库。
logging 模块
Python 语言在标准库中,提供了强大的日志记录标准库:logging
。因为 logging
属于 Python 标准库中的一部分,无需安装,所以导入包就可以直接使用。
import logging
logging.debug('Hello World!')
核心概念
在 logging
模块的设计文档[1][2][3]
中提到 logging
模块的设计基于以下这些核心概念:
- 日志器 Logger:Logger 是日志记录的主体,负责产生日志消息。每个 Logger 都有一个名称,通常使用模块名作为名称(如
__name__
),以便区分不同模块的日志。 - 处理器 Handler:Handler 负责将日志消息发送到指定的目的地,例如控制台、文件、网络等。一个 Logger 可以添加多个 Handler。
- 格式化器 Formatter:Formatter 用于定义日志消息的输出格式,例如时间、日志级别、模块名、消息内容等。
- 过滤器 Filter:Filter 用于对日志消息进行过滤,只有满足条件的日志才会被记录。
- 日志级别 Log Level:logging 定义了6 种日志级别,从低到高分别是:
DEBUG
:调试信息,用于开发阶段。INFO
:常规信息,用于记录程序运行状态。WARNING
:警告信息,表示潜在的问题。ERROR
:错误信息,表示程序出现错误。CRITICAL
:严重错误信息,表示程序可能无法继续运行。
快速入门
import logging
# 创建 Logger
logger = logging.getLogger(__name__)
# 设置日志级别
logger.setLevel(logging.DEBUG)
# 创建 Handler(输出到控制台)
stream_handler = logging.StreamHandler()
stream_handler.setLevel(logging.INFO)
# 创建 Formatter
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
# 将 Formatter 添加到 Handler
stream_handler.setFormatter(formatter)
# 将 Handler 添加到 Logger
logger.addHandler(stream_handler)
# 记录日志
logger.debug('这是一条调试信息')
logger.info('这是一条常规信息')
logger.warning('这是一条警告信息')
logger.error('这是一条错误信息')
logger.critical('这是一条严重错误信息')
在使用 logging
模块的时候,通过 logging.getLogger(__name__)
为每个模块创建一个独立的日志记录器,这样做的好处有两点:
- 区分日志来源:日志输出中会包含模块名称,方便定位日志的来源。
- 模块化配置:可以为不同的模块设置不同的日志级别、Handler 或 Formatter。
如果直接使用 logging.getLogger()
而不传递名称,会返回根日志记录器(Root Logger):
- 根日志记录器是全局的,所有模块共享同一个日志记录器。
- 这样会导致日志输出难以区分来源,且配置可能会相互覆盖。
也就是说通过 logging.getLogger(__name__)
,可以避免全局冲突。
上述案例中,创建一个输出到控制台的 StreamHandler
对象,用于将日志定向输出到控制台。还创建一个 Formatter
对象对输出到控制的日志信息进行格式化。最后,将 Handler
添加到日志记录器中,完成一个自定义的日志输出的设计。
文件配置
logging
模块支持多种配置方式,除了上述使用的代码方式配置日志记录器(Logger)、处理器(Handler)、格式化器(Formatter)等组件以外,还支持使用文件的方式进行配置。文件格式支持
JSON、YAML 或字典的形式配置日志。通过文件配置的方式非常适合复杂的日志配置需求,因为它可以将配置与代码分离,使配置更易于管理和维护。
下表是这三种方式的优缺点对比:
格式 | 优点 | 缺点 |
---|---|---|
JSON | 1. 结构化数据,易于解析 2. 适合复杂配置 | 1. 可读性较差 2. 需要额外文件 |
YAML | 1. 可读性高 2. 适合配置文件 3. 支持注释 | 1. 需要安装 pyyaml 库 2. 对缩进敏感,容易出错 |
字典 | 1. 无需额外文件 2. 适合直接在代码中定义配置 | 1. 配置与代码耦合 2. 不适合复杂配置 |
JSON 格式
JSON 配置文件通常包含以下部分:
version
:配置版本,必须为1
。formatters
:定义日志的格式化器。handlers
:定义日志的处理器(如输出到控制台、文件等)。loggers
:定义日志记录器及其配置。root
:根日志记录器的配置(可选)。
以下是一个完整的 JSON 配置文件示例:
{
"version": 1,
"disable_existing_loggers": false,
"formatters": {
"simple": {
"format": "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
},
"json": {
"format": {
"timestamp": "%(asctime)s",
"name": "%(name)s",
"level": "%(levelname)s",
"message": "%(message)s"
},
"class": "pythonjsonlogger.jsonlogger.JsonFormatter"
}
},
"handlers": {
"console": {
"class": "logging.StreamHandler",
"level": "DEBUG",
"formatter": "simple",
"stream": "ext://sys.stdout"
},
"file": {
"class": "logging.handlers.RotatingFileHandler",
"level": "INFO",
"formatter": "json",
"filename": "app.log",
"maxBytes": 10485760,
"backupCount": 5,
"encoding": "utf8"
}
},
"loggers": {
"my_module": {
"level": "DEBUG",
"handlers": [
"console",
"file"
],
"propagate": false
}
},
"root": {
"level": "WARNING",
"handlers": [
"console"
]
}
}
上述配置的字段含义如下:
-
formatters
定义日志的格式化器: -
simple
:使用简单的文本格式。 json
:使用 JSON 格式输出日志,需要安装python-json-logger
库。-
handlers
定义日志的处理器: -
console
:将日志输出到控制台。 file
:将日志输出到文件,并支持文件轮转(RotatingFileHandler
)。-
loggers
定义模块级别的日志记录器: -
my_module
:为my_module
模块配置日志记录器,输出到控制台和文件。 root
定义根日志记录器的配置。所有未明确配置的日志记录器会继承根日志记录器的配置。
在代码中,通过下面的方式加载配置文件:
import logging.config
import json
# 假如文件的名字为 logging_config.json,加载 JSON 配置文件
with open('logging_config.json', 'r') as f:
config = json.load(f)
logging.config.dictConfig(config)
# 获取日志记录器
logger = logging.getLogger('my_module')
# 记录日志
logger.debug('This is a debug message')
logger.info('This is an info message')
logger.warning('This is a warning message')
logger.error('This is an error message')
YAML 格式
YAML 是一种易读的数据序列化格式,适合用于配置文件。以下是将上面 JSON 配置修改成 YAML 格式:
version: 1
disable_existing_loggers: false
formatters:
simple:
format: "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
json:
format:
timestamp: "%(asctime)s"
name: "%(name)s"
level: "%(levelname)s"
message: "%(message)s"
class: "pythonjsonlogger.jsonlogger.JsonFormatter"
handlers:
console:
class: "logging.StreamHandler"
level: "DEBUG"
formatter: "simple"
stream: "ext://sys.stdout"
file:
class: "logging.handlers.RotatingFileHandler"
level: "INFO"
formatter: "json"
filename: "app.log"
maxBytes: 10485760
backupCount: 5
encoding: "utf8"
loggers:
my_module:
level: "DEBUG"
handlers: [ "console", "file" ]
propagate: false
root:
level: "WARNING"
handlers: [ "console" ]
此时,需要通过如下的方式加载配置文件:
import logging.config
import yaml
# 加载 YAML 配置文件
with open('logging_config.yaml', 'r') as f:
config = yaml.safe_load(f)
logging.config.dictConfig(config)
# 获取日志记录器
logger = logging.getLogger('my_module')
# 记录日志
logger.debug('This is a debug message')
logger.info('This is an info message')
字典格式
如果不想使用外部文件,可以直接在代码中使用 Python 字典定义配置:
import logging.config
config = {
"version": 1,
"disable_existing_loggers": False,
"formatters": {
"simple": {
"format": "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
},
"json": {
"format": {
"timestamp": "%(asctime)s",
"name": "%(name)s",
"level": "%(levelname)s",
"message": "%(message)s"
},
"class": "pythonjsonlogger.jsonlogger.JsonFormatter"
}
},
"handlers": {
"console": {
"class": "logging.StreamHandler",
"level": "DEBUG",
"formatter": "simple",
"stream": "ext://sys.stdout"
},
"file": {
"class": "logging.handlers.RotatingFileHandler",
"level": "INFO",
"formatter": "json",
"filename": "app.log",
"maxBytes": 10485760,
"backupCount": 5,
"encoding": "utf8"
}
},
"loggers": {
"my_module": {
"level": "DEBUG",
"handlers": ["console", "file"],
"propagate": False
}
},
"root": {
"level": "WARNING",
"handlers": ["console"]
}
}
# 加载配置
logging.config.dictConfig(config)
# 获取日志记录器
logger = logging.getLogger('my_module')
# 记录日志
logger.debug('This is a debug message')
logger.info('This is an info message')
高阶功能
日志轮转
当日志文件过大时,可以通过文件轮转(Rotating)将日志分割成多个文件。logging
提供了两种文件轮转处理器:
RotatingFileHandler
:基于文件大小轮转。TimedRotatingFileHandler
:基于时间轮转。
基于文件大小的轮转:
import logging
from logging.handlers import RotatingFileHandler
# 创建日志记录器
logger = logging.getLogger('my_app')
logger.setLevel(logging.DEBUG)
# 创建 RotatingFileHandler
handler = RotatingFileHandler(
filename='app.log', # 日志文件名
maxBytes=10 * 1024 * 1024, # 每个日志文件最大 10MB
backupCount=5, # 保留 5 个备份文件
encoding='utf8'
)
handler.setFormatter(logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s'))
# 添加处理器
logger.addHandler(handler)
# 记录日志
for i in range(10000):
logger.info(f'This is log message {i}')
基于时间的轮转:
import logging
from logging.handlers import TimedRotatingFileHandler
# 创建日志记录器
logger = logging.getLogger('my_app')
logger.setLevel(logging.DEBUG)
# 创建 TimedRotatingFileHandler
handler = TimedRotatingFileHandler(
filename='app.log', # 日志文件名
when='midnight', # 每天午夜轮转
interval=1, # 轮转间隔
backupCount=7, # 保留 7 个备份文件
encoding='utf8'
)
handler.setFormatter(logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s'))
# 添加处理器
logger.addHandler(handler)
# 记录日志
logger.info('This is a log message')
日志转发到网络
logging
支持将日志发送到远程服务器,例如通过 HTTP 或 Socket。
使用 HTTP 发送到服务器:
import logging
import logging.handlers
# 创建日志记录器
logger = logging.getLogger('remote_logger')
logger.setLevel(logging.DEBUG)
# 创建 SocketHandler
handler = logging.handlers.SocketHandler('localhost', 9020) # 发送到本地 9020 端口
logger.addHandler(handler)
# 记录日志
logger.info('This is a log message sent to a remote server')
使用 Socket 发送到服务器:
import logging
import logging.handlers
# 创建日志记录器
logger = logging.getLogger('remote_logger')
logger.setLevel(logging.DEBUG)
# 创建 HTTPHandler
handler = logging.handlers.HTTPHandler(
'localhost:5000', # 服务器地址
'/log', # 日志接收路径
method='POST'
)
logger.addHandler(handler)
# 记录日志
logger.info('This is a log message sent via HTTP')
日志过滤
logging
模块的日志过滤功能允许根据特定的条件控制哪些日志应该被记录。通过过滤器,可以实现对日志更加精细的日志控制,例如:
- 过滤特定级别的日志。
- 过滤包含特定关键词的日志。
- 过滤来自特定模块或日志记录器的日志。
logging
模块中的日志过滤器需要继承 logging.Filter
类,重写 filter(record)
方法。record
是一个日志对象,包含了日志的所有信息,比如日志级别、消息和模块名等。
重写的 filter(record) 方法返回 True 时,日志会被记录。返回 False 时,日志会被忽略。
一个过滤特定级别日志的过滤器实现:
import logging
# 自定义过滤器
class LevelFilter(logging.Filter):
def __init__(self, level):
self.level = level
def filter(self, record):
return record.levelno == self.level # 只允许指定级别的日志通过
# 创建日志记录器
logger = logging.getLogger('level_filter_logger')
logger.setLevel(logging.DEBUG)
# 创建 Handler
handler = logging.StreamHandler()
handler.setFormatter(logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s'))
# 添加过滤器
handler.addFilter(LevelFilter(logging.ERROR)) # 只记录 ERROR 级别的日志
# 添加处理器
logger.addHandler(handler)
# 记录日志
logger.debug('This is a debug message') # 不会被记录
logger.info('This is an info message') # 不会被记录
logger.error('This is an error message') # 会被记录
一个过滤不包含某些关键词的日志过滤器实现:
import logging
# 自定义过滤器
class KeywordFilter(logging.Filter):
def __init__(self, keyword):
self.keyword = keyword
def filter(self, record):
return self.keyword in record.getMessage() # 只允许包含关键词的日志通过
# 创建日志记录器
logger = logging.getLogger('keyword_filter_logger')
logger.setLevel(logging.DEBUG)
# 创建 Handler
handler = logging.StreamHandler()
handler.setFormatter(logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s'))
# 添加过滤器
handler.addFilter(KeywordFilter('important')) # 只记录包含 "important" 的日志
# 添加处理器
logger.addHandler(handler)
# 记录日志
logger.info('This is a normal message') # 不会被记录
logger.info('This is an important message') # 会被记录
异步日志记录
在高并发场景下,同步日志记录可能会影响性能。可以通过QueueHandler
和QueueListener
实现异步日志记录:
import logging
import logging.handlers
import queue
import threading
# 创建日志队列
log_queue = queue.Queue()
# 创建 QueueHandler
queue_handler = logging.handlers.QueueHandler(log_queue)
# 创建日志记录器
logger = logging.getLogger('async_logger')
logger.setLevel(logging.DEBUG)
logger.addHandler(queue_handler)
# 创建 QueueListener
file_handler = logging.FileHandler('async.log')
listener = logging.handlers.QueueListener(log_queue, file_handler)
# 启动监听器
listener.start()
# 记录日志
logger.info('This is an async log message')
# 停止监听器
listener.stop()
动态修改日志配置
在运行时动态修改日志配置,例如根据环境变量调整日志级别:
import logging
import os
# 创建日志记录器
logger = logging.getLogger('dynamic_logger')
logger.setLevel(logging.DEBUG)
# 根据环境变量设置日志级别
log_level = os.getenv('LOG_LEVEL', 'INFO').upper()
logger.setLevel(getattr(logging, log_level))
# 记录日志
logger.debug('This is a debug message')
logger.info('This is an info message')
loguru 库
介绍
loguru
是一个 Python
日志记录 第三方开源库,相比与
Python 原生的 logging
模块,loguru
提供了更简单、更强大、更优雅的日志记录方式。它是对 Python 标准库logging
的替代方案,具有开箱即用、功能丰富、配置灵活等特点。
在 官方文档 中,对 loguru
的介绍十分有趣:
- Loguru 的主要理念是存在且仅存在一个日志记录器。
- 没有繁琐的 Handler、Formatter 或 Filter:一个函数即可掌控全局。
真是醉翁之意不在酒,虽然 loguru
没有直接写明和谁进行对比,但是学习过 Python 日志框架的人都知道官网在说什么。读完官方文档对
loguru
的介绍,可以总结出四个字:简单好用!
安装库
pip install loguru
推荐在虚拟环境中安装依赖库文件,避免对全局环境产生污染。
快速入门
开箱即用是 loguru
最大的特点,所有的核心配置全都被分装到了 logger
对象中,全部使用 add
的方式向 loguru
添加配置:
from loguru import logger
# 开箱即用,不需要过多的配置
logger.debug("That's it, beautiful and simple logging!")
# 通过 add 方法添加配置
logger.add(sys.stderr, format="{time} {level} {message}", filter="my_module", level="INFO")
如果希望将日志信息转发到文件,也是通过 add
方法进行:
logger.add("file_{time}.log")
如果需要轮换日志、删除旧日志或者是在关闭时压缩日志文件,可以这样做:
# 文件过大时自动轮换
logger.add("file_1.log", rotation="500 MB")
# 每天中午 12 点创建新文件
logger.add("file_2.log", rotation="12:00")
# 文件过期后轮换
logger.add("file_3.log", rotation="1 week")
# 一段时间后清理旧日志
logger.add("file_X.log", retention="10 days")
# 压缩日志以节省空间
logger.add("file_Y.log", compression="zip")
看到了吧,一切关于日志的操作在 loguru
中都会变得简单!
格式化日志
Loguru 的日志方法支持类似 str.format() 的格式化方式,既可以使用位置参数,也可以使用关键字参数:
logger.info("If you're using Python {}, prefer {feature} of course!", 3.6, feature="f-strings")
这种方式比传统的 %
格式化更灵活、更易读,也符合 Python 的现代格式化风格(如 f-strings)。
日志格式
Loguru 的默认日志格式如下:
< level > {time: YYYY - MM - DD HH: mm: ss.SSS} | < level > {level} | < cyan > {name} < / cyan >: < cyan > {
function} < / cyan >: < cyan > {line} < / cyan > - < level > {message} < / level >
其中:
{time}
:日志记录的时间,默认格式为YYYY-MM-DD HH:mm:ss.SSS
。{level}
:日志级别(如 INFO、ERROR 等)。{name}
:日志记录器的名称(通常是模块名称)。{function}
:记录日志的函数名称。{line}
:记录日志的代码行号。{message}
:日志消息内容。
Loguru 提供了非常灵活的日志格式配置,允许通过format
参数自定义日志格式。以下是一个简单的自定义格式示例:
logger.add(sys.stderr, format="{time} | {level} | {message}")
输出的日志如下:
2023-10-10 12:00:00 | INFO | This is a log message
Loguru 支持自定义时间格式,使用{time}
字段时可以指定格式:
logger.add(sys.stderr, format="{time:YYYY/MM/DD at HH:mm:ss} | {level} | {message}")
此外,Loguru 支持许多内置字段,例如:
{file}
:记录日志的文件名。{module}
:记录日志的模块名称。{process}
:进程 ID。{thread}
:线程 ID。{elapsed}
:从程序启动到记录日志的时间间隔。
logger.add(sys.stderr, format="{time} | {level} | {file}:{line} | {message}")
输出的日志如下:
2023-10-10 12:00:00 | INFO | example.py:10 | This is a log message
若 Loguru 的内置字段不满足要求,还可以使用 bind()
方法绑定额外的上下文信息,然后通过 {extra}
字段显示这些信息。
logger.add(sys.stderr, format="{time} | {level} | {message} | {extra}")
context_logger = logger.bind(user_id=123, request_id="abc123")
context_logger.info("User logged in")
输出的日志如下:
2023-10-10 12:00:00 | INFO | User logged in | {'user_id': 123, 'request_id': 'abc123'}
Loguru 还支持根据日志级别或其他条件动态调整日志格式:
# 错误日志显示为红色。其他日志显示为默认格式。
def formatter(record):
if record["level"].name == "ERROR":
return "<red>{time} | {level} | {message}</red>\n"
return "{time} | {level} | {message}\n"
logger.add(sys.stderr, format=formatter)
catch 装饰器
Loguru 支持使用装饰器的方式捕获方法可能出现的异常:
from loguru import logger
@logger.catch
def risky_function():
return 1 / 0 # 这会引发一个异常
risky_function()
当异常发生时,Loguru 会捕获异常并记录详细的错误信息,包括堆栈跟踪。
日志颜色
Loguru 会根据日志的级别(如 INFO、WARNING、ERROR 等)自动为日志消息添加颜色。例如,错误日志可能是红色的,而信息日志可能是绿色的。同时,Loguru 也支持自定义日志样式:
from loguru import logger
# 自定义日志格式,使用标记标签定义颜色和样式
logger.add(sys.stderr, format="<green>{time}</green> <level>{message}</level>")
logger.info("This is an info message")
logger.warning("This is a warning message")
logger.error("This is an error message")
Loguru 支持的样式包括:
<bold>
:加粗文本<dim>
:暗淡文本<italic>
:斜体文本<underline>
:下划线文本<strike>
:删除线文本<black>
、<red>
、<green>
、<yellow>
、<blue>
、<magenta>
、<cyan>
、<white>
:文本颜色<bg-black>
、<bg-red>
、<bg-green>
、<bg-yellow>
、<bg-blue>
、<bg-magenta>
、<bg-cyan>
、<bg-white>
:背景颜色
logger.add(sys.stderr, format="<red><bold>{time}</bold></red> <blue>{message}</blue>")
线程安全
默认情况下,Loguru 是线程安全的,但不是多线程安全的。在多线程环境中,可以将日志信息添加到队列中,确保捕获到的日志信息的完整性。
from loguru import logger
# 添加一个文件接收器,并启用队列以确保多进程安全或异步日志记录
logger.add("output.log", enqueue=True)
enqueue=True
:将日志消息放入队列中,确保线程安全、多进程安全以及异步日志记录。日志消息会被后台线程处理,不会阻塞主程序的执行。
序列化
使用 serialize=True
参数,Loguru 会将日志消息转换为 JSON 格式。转换后的格式保留了日志的完整结构化信息,便于后续处理和分析。
from loguru import logger
# 添加一个文件接收器,并启用序列化
logger.add("output.log", serialize=True)
# 记录一些日志
logger.info("This is a serialized log message")
logger.error("An error occurred", details={"code": 500, "message": "Internal Server Error"})
日志文件output.log
中的内容将是 JSON 格式,例如:
{
"text": "2023-10-10 12:00:00 | INFO | __main__:<module>:1 - This is a serialized log message\n",
"record": {
"elapsed": {
"repr": "0:00:00.000123",
"seconds": 0.000123
},
"exception": null,
"extra": {},
"file": {
"name": "example.py",
"path": "/path/to/example.py"
},
"function": "<module>",
"level": {
"icon": "ℹ️",
"name": "INFO",
"no": 20
},
"line": 1,
"message": "This is a serialized log message",
"module": "example",
"name": "__main__",
"process": {
"id": 1234,
"name": "MainProcess"
},
"thread": {
"id": 12345,
"name": "MainThread"
},
"time": {
"repr": "2023-10-10 12:00:00",
"timestamp": 1696939200.0
}
}
}
{
"text": "2023-10-10 12:00:01 | ERROR | __main__:<module>:2 - An error occurred\n",
"record": {
"elapsed": {
"repr": "0:00:01.000456",
"seconds": 1.000456
},
"exception": null,
"extra": {
"details": {
"code": 500,
"message": "Internal Server Error"
}
},
"file": {
"name": "example.py",
"path": "/path/to/example.py"
},
"function": "<module>",
"level": {
"icon": "❌",
"name": "ERROR",
"no": 40
},
"line": 2,
"message": "An error occurred",
"module": "example",
"name": "__main__",
"process": {
"id": 1234,
"name": "MainProcess"
},
"thread": {
"id": 12345,
"name": "MainThread"
},
"time": {
"repr": "2023-10-10 12:00:01",
"timestamp": 1696939201.0
}
}
}
惰性求值
Loguru 的惰性求值(Lazy Evaluation)是一种优化日志记录性能的技术。通过惰性求值,Loguru 可以确保只有在日志实际需要记录时,才会执行这些昂贵的操作,从而避免不必要的性能开销。
惰性求值的核心思想有两点:
- 延迟执行:在日志记录时,如果某些操作(如调用复杂函数或生成大量数据)非常耗时,但这些日志可能在生产环境中不会被记录(例如日志级别较高时),惰性求值可以延迟这些操作的执行。只有在日志级别允许记录时,才会真正执行这些操作。
- 性能优化:避免在不必要的情况下执行昂贵的操作,从而提高程序的运行效率。
Loguru 提供了opt(lazy=True)
方法来实现惰性求值。通过将日志参数封装为可调用对象(如函数或 lambda 表达式),Loguru
会在需要记录日志时才执行这些操作。
from loguru import logger
# 一个耗时的函数
def expensive_operation():
print("Expensive operation is running...")
return "Expensive data"
# 普通日志记录(即使日志级别高于 INFO,expensive_operation() 也会被执行)
logger.info("Data: {}", expensive_operation())
# 使用 opt(lazy=True) 实现惰性求值(只有在日志级别允许时才会执行 expensive_operation())
logger.opt(lazy=True).info("Data: {}", expensive_operation)
如果日志级别高于 INFO(如 WARNING 或 ERROR)时,expensive_operation()
不会被执行,日志也不会记录。如果日志级别为 INFO
或更低,输出的结果如下:
Expensive
operation is running...
2023 - 10 - 10
12: 00:00 | INFO | Data: Expensive
data
惰性求值特别适用于需要记录复杂、耗时操作结果的场景,同时避免在生产环境中执行不必要的操作,比如:
- 调试场景:在开发环境中记录详细的调试信息,但在生产环境中避免执行这些操作。
- 复杂计算:记录某些需要复杂计算的结果,但只在需要时执行计算。
- 大数据生成:记录大量数据时,避免在不必要的情况下生成数据。
状态管理
Loguru 提供了disable()
和enable()
方法,用于控制日志记录器的启用和禁用状态。在项目开发和调试过程中,状态的管理十分重要,可以灵活的配置日志的输出,使调试环境和生产环境日志的输出有所区别。
disable()
方法用于禁用指定模块或日志记录器的日志输出。禁用后,日志函数(如logger.info()
)将不会执行任何操作。enable()
方法用于启用指定模块或日志记录器的日志输出。启用后,日志函数将正常执行并输出日志。
from loguru import logger
# 禁用当前模块的日志记录器
logger.disable(__name__)
# 这条日志不会输出
logger.info("This log message will not be displayed")
# 启用指定模块的日志记录器
logger.enable("my_library")
# 这条日志会正常输出
logger.info("This log message will be displayed")
在某些情况下,还可以根据条件动态启用或禁用日志记录器:
from loguru import logger
# 根据条件启用或禁用日志记录器
if debug_mode:
logger.enable("my_module")
else:
logger.disable("my_module")
# 日志输出取决于 debug_mode 的值
logger.info("This log message depends on debug_mode")