在 Python 的logging
模块中,它不仅提供了基础的日志功能,还拥有一系列高级配置选项来满足复杂应用的日志管理需求。
说到logging
模块的高级配置,必须提及日志分层
、logging.config配置
、日志异步操作
等关键功能。它们每一项都为开发者提供了强大的调试和监控环境,对于构建可维护和高效的日志系统至关重要。
在接下来的三篇logging高级配置
文章中,我将为读者朋友们介绍 Python logging
模块中的三个高级配置的具体应用:日志分层
、logging.config
以及 日志异步操作
,探讨它们如何优化日志处理流程,并提升应用的整体性能。
本文将首先聚焦于 logging
模块中的日志分层
概念,解析其如何使开发者能够构建具有层级结构的日志记录系统,并高效地管理和过滤日志信息。
模块 | 释义 |
---|---|
logging |
Python 的日志记录工具,标准库 |
logging.getLogger |
获取日志记录器实例 |
导入模块
import logging
import logging.handlers
文章脉络:
日志分层在管理复杂应用的日志中发挥着重要作用。
日志分层在复杂应用程序中提供了高度的组织性和灵活性,允许针对不同模块或组件进行细致的日志管理,从而优化调试、维护效率,并增强整体日志系统的性能和可读性。
将 Python 的 logging
模块中的日志分层类比为 Python 中的类继承
(父类和子类关系),这样可以有助于理解日志记录器(logger)之间的继承和行为传播机制。
继承:在类的继承中,子类继承父类的属性和方法。类似地,在日志分层中,子记录器会继承父记录器的配置,如日志级别和关联的处理器(handlers)。
重写和自定义:就像子类可以重写或扩展父类的方法和属性,子记录器也可以有自己的日志级别和处理器,甚至可以完全覆盖父记录器的设置。
层级关系:正如类可以有多层继承关系,日志记录器也可以形成层级结构,允许复杂和细致的控制。
传播行为:在类继承中,子类的行为可以反映父类的特征,而在日志分层中,日志消息的传播(默认情况下)会从子记录器向上到父记录器,除非显式地将 propagate
属性设置为 False
。
需要注意的是,这只是一个比喻,实际的实现细节和概念上仍有区别。例如,类继承是面向对象编程中的一个核心概念,涉及到更广泛的编程模式,而日志分层主要是关于信息传递和处理的策略。
简单总结如下:
特性 | 主要作用与实现方式 | 备注 |
---|---|---|
组织性 | 为每个模块或组件设置独立的子记录器 | 日志分层核心优势,提供清晰的日志结构 |
继承和覆盖 | 子记录器继承父记录器的配置,可根据需要覆盖 | 日志分层核心优势,提高配置灵活性 |
灵活性 | 为不同模块设置不同的日志级别 | 多日志处理器可实现,但日志分层的优势是更加结构化和直观 |
性能优化 | 控制哪些消息被记录,减少不必要的日志输出 | 多日志处理器可实现,但日志分层的优势是更加结构化和直观 |
问题追踪 | 快速定位大型应用中的问题所在模块 | 日志记录自带buff ,但日志分层的优势在于它使得定位问题变得更加容易 |
在学习了前面的 万字长文 - Python 日志记录器logging 百科全书 之 日志过滤 和 初步了解到日志分层
的作用之后,可能会有读者朋友们有疑问:
?既然能够通过单个记录器和多处理器满足复杂日志需求的情况下,为什么还需要使用分层的日志记录器方法???
以下是我在深入了解日志分层
的作用之后的回答:
分层日志在大型和复杂的应用程序中提供了更好的组织性和灵活性,使得对不同模块或组件的日志管理更加精细和高效。
日志分层的应用场景主要体现在大型、多模块的应用程序和微服务架构中,以及多团队协作的开发项目里,它帮助各个模块独立地控制日志记录,简化问题追踪和调试,提高维护效率。
以下内容来自官方文档:
getLogger()
返回对具有指定名称的记录器实例的引用(如果已提供),或者如果没有则返回 root
。名称是以句点分隔的层次结构。多次调用 getLogger()
具有相同的名称将返回对同一记录器对象的引用。在分层列表中较低的记录器是列表中较高的记录器的子项。例如,给定一个名为 foo
的记录器,名称为 foo.bar
、 和 foo.bam
的记录器都是 foo
子项。
记录器具有 有效等级 的概念。如果未在记录器上显式设置级别,则使用其父记录器的级别作为其有效级别。如果父记录器没有明确的级别设置,则检查 其 父级。依此类推,搜索所有上级元素,直到找到明确设置的级别。根记录器始终具有明确的级别配置(默认情况下为 WARNING
)。在决定是否处理事件时,记录器的有效级别用于确定事件是否传递给记录器相关的处理器。
子记录器将消息传播到与其父级记录器关联的处理器。因此,不必为应用程序使用的所有记录器定义和配置处理器。一般为顶级记录器配置处理器,再根据需要创建子记录器就足够了。(但是,你可以通过将记录器的 propagate 属性设置为 False
来关闭传播。)
我提炼如下,简要说明了关于日志分层中的日志记录器行为和层次结构的核心概念:
记录器实例引用:使用getLogger()
函数可以获取具有特定名称的记录器实例。如果使用相同的名称多次调用getLogger()
,将返回同一个记录器对象的引用。
命名规范:记录器(logger)的名称通常遵循点号分隔的层级结构,类似于 Python 包和模块的命名方式。例如,'foo.database'
表示 foo
下的 database
子模块的记录器。
层级关系:记录器的层级结构通过命名来实现。在这个层级中,一个记录器可以是另一个记录器的子项。例如,如果有一个名为 'foo'
的记录器,那么 'foo.bar'
和 'foo.bar.baz'
就是它的子记录器。
有效级别:如果调用 getLogger()
时不提供名称,将返回根记录器。根记录器是所有记录器的最顶层父记录器,它的默认级别为 WARNING
。
消息传播:子记录器的日志消息会传播到其父记录器的处理器,除非设置了propagate
属性为False
。通常只需为顶级记录器配置处理器,子记录器可以继承这些处理器。
这种层级结构和命名规范为大型和复杂的应用程序提供了一种高效和灵活的日志管理方式。通过恰当地命名和配置记录器,可以轻松地管理应用程序的不同部分所生成的日志,确保日志信息的清晰和有序。
在 Python 的 logging
模块中,如果顶级日志记录器的命名与子记录器的命名不遵循统一的层级结构,会出现以下几个问题:
为了避免这些问题,所以需要使用一致的层级命名约定。例如,顶级日志记录器命名为 'foo'
,那么子记录器应该以 'foo.'
作为前缀,如 'foo.bar'
、'foo.baz'
等,以确保正确的继承和日志消息传播。
可能会有读者朋友们有疑问:
?既然设置
logger.propagate = False
,那为什么还需要使用日志分层呢? 一开就使用 非日志封层的logger
不是更合适吗?
其实要回答这个疑问,就应该重新审视日志分层的作用和应用场景,以及为什么即使在某些情况下禁用了传播,日志分层仍然是有价值的。
使用 logger.propagate = False
的情况,
避免重复日志记录:当在不希望某个子记录器(logger)的日志被其父记录器也处理时。例如,当已经为子记录器配置了特定的处理器(handler)并且不希望同样的日志消息再次由更高级别的记录器处理,这时设置 propagate = False
可以防止日志消息向上传播。
特定日志处理:当我们希望对某个模块或组件的日志进行特殊处理,与应用程序的其他部分区别开来。例如,我们可能有一个记录安全相关日志的记录器,我们不希望这些日志被常规的应用程序日志处理器处理。
独立日志流:在构建库或框架时,可能希望库的日志独立于使用该库的应用程序的日志系统。在这种情况下,可以为库设置一个专用的记录器,并设置 propagate = False
,以防止库的日志消息污染或干扰主应用程序的日志。
性能考虑:在一些性能敏感的应用中,防止不必要的日志传播可以减少一些处理开销,特别是当有大量的日志消息和复杂的日志处理器配置时。
即使在上面的这些情况下,分层结构依然有助于维护清晰的组织架构。我们可以为特定的模块创建专用的子记录器,给予它独立的处理器和格式化器,而不必在每个模块中创建和配置新的独立记录器。
这里是一份能用的伪代码,用于帮助读者朋友们更好的理解
日志分层
的具体应用。
代码:
# -*- coding: utf-8 -*-
import logging
import logging.handlers
import sys
import requests
# 全局基础配置, 日志格式化配置
formatter = logging.Formatter('%(levelname)-7s - %(asctime)s - %(name)s - %(message)s')
class RemoteLogHandler(logging.Handler):
"""自定义远程处理器"""
def __init__(self, remote_url, logger):
super().__init__()
self.remote_url = remote_url
self.error_logger = logger
self.setFormatter(formatter)
def emit(self, record):
# 发送日志记录到远程服务器
log_entry = self.format(record) # 格式化日志记录
try:
response = requests.post(self.remote_url, data=log_entry)
response.raise_for_status()
except Exception as e:
record.msg = f"Original message: {record.msg}, Failed to send log to remote: {str(e)}"
print('error_logger 等级是 ', self.error_logger.level)
self.error_logger.handle(record)
def setup_logger(*handlers, name, level=logging.INFO):
"""
用于设置特定记录器的函数,支持多个处理器.
Args:
*handlers(logging.Handler): 日志处理器
name(str): 日志记录器名称
level(int): 日志等级
Returns:
日志记录器.
"""
logger = logging.getLogger(name)
logger.setLevel(level)
for handler in handlers:
handler.setFormatter(formatter)
logger.addHandler(handler)
return logger
def setup_file_handler(filename, level=logging.DEBUG):
"""
setup file handler
Args:
filename(str): 日志文件的名称
level(int): 日志处理器的级别, 默认为logging.DEBUG
Returns:
"""
file_handler = logging.FileHandler(filename=filename, delay=True)
file_handler.setLevel(level=level)
file_handler.setFormatter(formatter)
return file_handler
def setup_stream_handler(stream=sys.stdout, level=logging.INFO):
stream_handler = logging.StreamHandler(stream=stream)
stream_handler.setLevel(level=level)
stream_handler.setFormatter(formatter)
return stream_handler
def create_remote_log_handler(url, logger, level=logging.INFO):
remote_handler = RemoteLogHandler(url, logger)
remote_handler.setLevel(level=level)
remote_handler.setFormatter(formatter)
return remote_handler
if __name__ == '__main__':
# 顶层日志记录器
# 处理器配置, 文件处理器 和 控制台处理器
file_handler_global = setup_file_handler(filename='ecommerce_global.log')
stream_handler_global = setup_stream_handler(stream=sys.stdout)
global_logger = setup_logger(
file_handler_global, stream_handler_global, name='ecommerce', level=logging.DEBUG
)
# 创建错误记录器和处理器
error_file_handler = setup_file_handler(filename='error.log', level=logging.ERROR)
error_logger = setup_logger(error_file_handler, name='error', level=logging.WARNING)
# 实例化RemoteLogHandler
remote_handler_global = create_remote_log_handler(
url='http://127.0.0.1:5000/submit_log', logger=error_logger, level=logging.ERROR
)
# 订单处理系统记录器和处理器
order_file_handler = setup_file_handler('orders.log', level=logging.WARNING)
order_logger = setup_logger(
order_file_handler, remote_handler_global, name='ecommerce.orders', level=logging.INFO
)
# 支付系统记录器和处理器
payment_file_handler = setup_file_handler(filename='payments.log', level=logging.ERROR)
payment_logger = setup_logger(
payment_file_handler, remote_handler_global, name='ecommerce.payments', level=logging.WARNING
)
#
# 测试打印日志
global_logger.info('Global logger configured')
order_logger.warning('Order logger configured')
payment_logger.error('Payment logger configured')
如果程序没有出错的话,可以看到会创建orders.log
, payments.log
和 ecommerce_global.log
三份日志文件,内容分别如下所示:
ecommerce_global.log
文件,打印的日志是包含orders.log
和 payments.log
的。添加以下两行代码,
order_logger.propagate = False
payment_logger.propagate = False
再次运行结果如下:
这份代码是一个复杂的日志系统的实现,适用于需要将日志信息记录到不同位置(如文件、控制台、远程服务器)的应用程序。展示了如何在 Python 中使用 logging
模块来创建一个分层且灵活的日志处理架构。
自定义远程日志处理器 (RemoteLogHandler
):
error_logger
)来处理这些日志消息。灵活的日志配置函数:
setup_logger
, setup_file_handler
, setup_stream_handler
, create_remote_log_handler
),使得创建和配置日志记录器和处理器更加灵活和简洁。分层日志记录器:
不同功能的日志记录器:
global_logger
用于全局日志记录。order_logger
专门用于订单处理系统的日志。payment_logger
专门用于支付系统的日志。层级命名:
ecommerce.orders
和 ecommerce.payments
表示这些记录器是 ecommerce
的子模块。日志传播:
propagate
属性没有被设置为 False
,那么日志消息会从子记录器传播到父记录器。这意味着 order_logger
和 payment_logger
的日志也可能被 global_logger
所处理,除非显式地关闭了传播。这个日志系统适用于大型或模块化的应用程序,其中需要对不同部分的日志进行精细控制。通过这种方法,我们可以确保不同部分的日志被适当地记录和处理,同时保持日志系统的整洁和可维护性。这对于故障排查、性能监控和安全分析等方面非常有用。
总体来说,这个代码示例展示了一个结构化的日志系统,它既灵活又能够适应不同的日志需求,非常适合复杂的应用场景。
本文详细介绍了 Python logging
模块中的日志分层功能,强调了其在构建复杂应用程序中的重要性。以下是文章的主要要点总结:
propagate
属性进行控制。总体来说,这篇文章为理解和应用 Python 的 logging
模块提供了深入的指导,特别是在构建需要细粒度日志管理的复杂应用时。文章的结构清晰,通过逐步深入的方式,使得读者朋友容易跟进和理解。
本次分享到此结束,
see you~~