神器 logging,你真的了解吗?

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
RDS MySQL Serverless 高可用系列,价值2615元额度,1个月
日志服务 SLS,月写入数据量 50GB 1个月
简介: logging 是 python 标准模块,用于记录和处理程序中的日志。功能很强大,官方文档很详细,网上也有大量的说明和教程,但是对很多初次接触的同学来说,存在一些障碍。一是因为标准库文档太过繁琐,需要较高的理论基础,着急用时,常常被文档搞晕。二是大部分说明材料要么是官方文档的罗列,要么是简单的应用,对实际应用帮助不大。今天,我们从应用上的一些问题开始,探讨一下日志神器 logging 极其背后的原理,让它能真正的帮助到我们。

logging 是 python 标准模块,用于记录和处理程序中的日志。

功能很强大,官方文档很详细,网上也有大量的说明和教程,但是对很多初次接触的同学来说,存在一些障碍。

一是因为标准库文档太过繁琐,需要较高的理论基础,着急用时,常常被文档搞晕。

二是大部分说明材料要么是官方文档的罗列,要么是简单的应用,对实际应用帮助不大。

今天,我们从应用上的一些问题开始,探讨一下日志神器 logging 极其背后的原理,让它能真正的帮助到我们。

该用 logging.debug 还是 logger.debug ?

debug 是日志模块中的一个日志等级为 DEBUG 的日志生成方法,还有 infowarningerrorcritial,这里用 debug 做为代表进行说明。

我们常会看到,一会儿用 logging.debug 记录日志,一会儿又用 logger.debug 记录日志,到底该用什么?

先看代码:

import logging
logging.debug('调试信息')
logger = logging.getLogger()
logger.debug('调试信息')

首先 logging 是作为一个模块被引入的。logging.debug 用的是 logging 模块的模块方法。

logger 是用 logging.getLogger() 生成的,是一个 日志对象logger.debug 调用的是 logger 这个日志对象的方法。

上面的代码中 logging.debuglogger.debug 的效果完全是一样的。

这是因为,为了让开发者方便使用,logging 模块提供了一些列模块方法,如 debug,在引入模块后,就可以直接使用。这样开发者就不必关心日志模块的细节,像用 print 一样输出日志。

如果需要对日志输出进行定制化,比如将日志输出到文件中,过滤某些级别的日志,就需要创建或者得到一个实际的日志对象来处理,如上面代码中通过 getLogger 方法得到的日志对象。

我们知道,程序设计里要避免重复的设计,如果模块方法采用一套机制,日志对象上的方法采用另一套机制,就会出现重复造轮子的问题。

所以在使用模块方法,logging 其实创建了一个日志对象 —— root logger

也就是 logging.debug 这个调用,实质上是调用 root logger 的日志方法。

相当于默认情况下 root logger 会作为日志处理对象。

如何获得 root logger 对象呢?

通过不带参数的 logging.getLogger() 方法获得。

那么 logging.debug  和 rootLogger.debug 是一会事,可以理解(但不严谨)为 logging.debugrootlogger.debug 的快捷方式。

日志树

稍加留意就会观察到,程序是有层次结构的,通过相互引用,调用形成一个树状结构。

程序加载的地方是树根,比如 python 中要运行的代码文件,我们称之为 main。从树根开始长出其他枝叶。对于一个模块来说,又会形成一个自己的树。

如何用日志清楚地记录层次结构呢?

虽然直接打印出调用堆栈也可以看到调用结构,不过不太直观,缺乏业务逻辑描述。

而用 print 来打印出层次结构,需要编写大量的代码才能反射出(通过运行状态获取代码状态的一种方式)调用环境。

logging 提供了完毕的解决方案。

前面提到的 root logger 就是整个日志树的根,其他所有的 logger 都是从 root logger 伸展出来的枝叶。只要通过 getLogger(loggername) 方法获得的 logger 对象,都是伸展自 root logger 的。

如何向下伸展呢?

很简单,就像引用模块的层次关系一样,用 . 分隔层次就好了,例如:

logger = logging.getLogger('mod1.mod2.mod3')
logger.debug("调试信息")

语句 logging.getLogger('mod1.mod2.mod3') 实际上创建了三个 logger,名称分别是 mod1、mod1.mod2 和 mod1.mod2.mod3

mod1 为根,mod1.mod2 为子,mod1.mod2.mod3 为孙。

如果在 mod1 上设置了日志处理器(handler),那么其他两个的日志对象都会用到这个处理器。

这样不但记录的日志更清晰而且,可以为同一个根的日志对象设置可以共享的日志处理方式。

这样感觉也不方便,需要些那么多层次,如何才能更方便呢?在下面的 实践参考 里会有说明。

logging.basicConfig 的功与过

说完了日志模块的树状结构,来看看一个很常用的设置方法 basicConfig

它可以方便的设置日志处理和记录方式,如没必要,不用为每个日志对象单独设置。

根据第一节的分析,我们知道,直接使用模块方法,用的其实是 root logger,那么就能明白 basicConfig 设置了 root logger 的日志处理方式。

这就意味着:

一旦设置了通过 logging.basicConfig 设置了日志处理方式,其他所有日志都很受到影响。

另外 basicConfig 是个一次性方法,即:

只有第一次设置有效,其后设置无效

本来是个一劳永逸的方法。

但用错了地方,就会很麻烦。

看下例子:

__all__ = ['Connection', 'ConnectionPool', 'logger']
warnings.filterwarnings('error', category=pymysql.err.Warning)
# use logging module for easy debug
logging.basicConfig(format='%(asctime)s %(levelname)8s: %(message)s', datefmt='%m-%d %H:%M:%S')
logger = logging.getLogger(__name__)
logger.setLevel('WARNING')

这段代码中,用 logging.basicConfig 对日志做了设置,意思是后面的日志都按照这样的方式输出。

但它是一个底层模块 —— pymysqlpool[1]

pymysqlpool 封装了 pymysql[2] 模块,提供了链接池特性,在多线程处理数据库场景下很有用。

也就是说,pymysqlpool 只会被引用加载,不会作为 main 被加载,这就比较尴尬了,因为 main 中对日志的设置就没有效果。

作为一个服务类模块(相对于业务的底层模块),不要通过 basicConfig 来设置日志模式,要么通过自己专属的日志对象来设置,要么不去设置,统一交给 main 去设置,例如:

logger = logging.getLogger(__name__)
fmt = logging.Formatter("%(asctime)s %(levelname)8s: %(message)s", datefmt='%m-%d %H:%M:%S')
hdl = logging.StreamHandler()
hdl.setFormatter(fmt)
logger.addHandler(hdl)
logger.setLevel('WARNING')

如果为了测试,可以在测试的初始化方法中,使用 basicConfig 来设置,因为测试时,模块往往是被作为程序入库加载的。

实践参考

了解了日志模块的一下特性,和其中的原理之后,这里有几条实践参考。

  1. 不要再子模块中使用 logging.basicConfig 设置日志模式
  2. 强烈建议在任何模块中通过 logger = logging.getLogger(__name__) 来创建日志对象 因为 __name__ 代表的就是模板被加载的引用名称。
    例如 from a.b.c import b  模块 c 中的 __name__ 值就为 a.b.c
    而且这个引用名称刚好符合 logger 定义的层次结构。
  3. 通过命令行参数设置不同类型的日志,见代码:
import logging
import argparse
logger = logging.getLogger(__name__)
def create_args_parse():
    parser = argparse.ArgumentParser(description="参数列表")
    parser.add_argument('-d', '--debug', action='store_true', help='调试模式')
    # 加入其他命令行参数
    return parser
def set_logger(debug):
    formatter = logging.Formatter('%(asctime)s - %(levelname)8s - %(name)s - %(filename)s:%(lineno)d - %(thread)d- %(funcName)s:\t%(message)s')
    if debug:
        hd = logging.StreamHandler()
        logger.setLevel(logging.DEBUG)
        hd.setFormatter(formatter)
    else:
        hd = logging.FileHandler(f'{__name__}.log', 'a', encoding='utf-8')
        logger.setLevel(logging.INFO)
        hd.setFormatter(formatter)
    logger.addHandler(hd)
if __name__ == '__main__':
   parser = create_args_parse()
   args = parser.parse_args()
   debug = args.debug
   set_logger(debug)
   ...

代码有点长,但不难懂。

  • create_args_parse 方法用于解析命令行参数,其中定义了一个 debug 参数,表示开启调试模式
  • set_logger 方法接收一个是否为调试模式的参数,根据是否为调试模式,设置不同的日志模式
  • main 中,首先调用 create_args_parse 获得命令行参数对象,然后从中解析出参数,提取 debug 模式,传送给 set_logger 方法,设置日志模式

这样只需要在运行程序时,加上参数 -d 就可以让日志打印到终端上,不加,日志就会自动去 __main__.log 日志文件中去了。

总结

python 为我们提供了很多便利的功能,有些需要真的用到才能有所体会,所以在遇到问题时,需要多研究一下,找到其中的特点和内在的原理或机制,这样就能更好的应用了。

在我理解了 logging 的原理之后,已经在我的很多项目中发挥了巨大作用,而且再也不必纠结于怎么用,如何更合理等这些问题了。

期望这篇文章也能对你有所帮助,比心。

相关实践学习
每个IT人都想学的“Web应用上云经典架构”实战
本实验从Web应用上云这个最基本的、最普遍的需求出发,帮助IT从业者们通过“阿里云Web应用上云解决方案”,了解一个企业级Web应用上云的常见架构,了解如何构建一个高可用、可扩展的企业级应用架构。
MySQL数据库入门学习
本课程通过最流行的开源数据库MySQL带你了解数据库的世界。   相关的阿里云产品:云数据库RDS MySQL 版 阿里云关系型数据库RDS(Relational Database Service)是一种稳定可靠、可弹性伸缩的在线数据库服务,提供容灾、备份、恢复、迁移等方面的全套解决方案,彻底解决数据库运维的烦恼。 了解产品详情: https://www.aliyun.com/product/rds/mysql 
目录
相关文章
|
网络安全 开发工具 git
git 出现错误:kex_exchange_identification: read: Connection reset by peer fatal
git 出现错误:kex_exchange_identification: read: Connection reset by peer fatal
git 出现错误:kex_exchange_identification: read: Connection reset by peer fatal
|
9月前
|
机器学习/深度学习 文字识别 自然语言处理
医疗行业化验单智能识别技术探讨:OCR与表格识别的应用
本文探讨了OCR与表格识别技术在医疗化验单处理中的应用,通过自动化数据提取和录入,显著提高了效率和准确性,降低了人工劳动强度和错误率。技术实现包括图像预处理、文字识别和表格解析等核心算法的优化,支持与医院信息管理系统集成,未来将向跨模态数据融合、多语言适配及数据安全方向发展。
943 9
|
存储 Kubernetes 调度
k8s常见的排错指南Node,svc,Pod等以及K8s网络不通问题
k8s常见的排错指南Node,svc,Pod等以及K8s网络不通问题
4741 1
|
JavaScript 数据可视化 BI
echarts的使用 超好用的报表制作、数据的图形化展示
您提供的链接是关于echarts使用的博客文章,它介绍了如何使用echarts进行数据的图形化展示,包括制作报表和图表。echarts是一个强大的数据可视化工具,能够创建折线图、柱状图、饼图等多种图表类型。文章还提供了一个Demo演示和项目结构的图片,以及官网链接供读者参考。
echarts的使用 超好用的报表制作、数据的图形化展示
|
前端开发 JavaScript Java
SpringBoot+JQuery+Ajax实现表单数据传输和单文件或多文件的上传
关于如何在SpringBoot项目中结合JQuery和Ajax实现表单数据的传输以及单文件或多文件上传的教程。文章提供了完整的前后端示例代码,包括项目的`pom.xml`依赖配置、SpringBoot的启动类`App.java`、静态资源配置`ResourceConfig.java`、配置文件`application.yml`、前端HTML页面(单文件上传和多文件上传加表单内容)以及后端控制器`UserController.java`。文章最后展示了运行结果的截图。
681 0
SpringBoot+JQuery+Ajax实现表单数据传输和单文件或多文件的上传
|
11月前
|
安全 搜索推荐 UED
众店’模式:创新积分驱动的消费生态
### 2 + 1 拼购模式 创新的2 + 1拼购模式通过扶持区和拼团区的结合,激励用户邀请新用户,形成稳定的团队结构。拼团成功后,用户可享受优惠并获得奖励,形成循环购物体验。奖励机制包括直推奖、滑落奖、成团奖和扶持奖,促进用户增长和团队稳定,提升用户体验,实现商家和消费者的双赢。 ### “众店” 模式 “众店”模式通过线上和线下的多种渠道获取积分,积分可用于抵扣现金、参与分红、提升会员等级和兑换商品或服务。积分管理设有有效期、转移与合并功能,并采取安全防护措施。该模式构建了消费生态圈,实现消费者、商家和供应商的三方共赢,整合流量,扩大影响力。
|
人工智能 数据挖掘
人机协作:大模型与AIGC技术的融入
【1月更文挑战第15天】人机协作:大模型与AIGC技术的融入
572 2
人机协作:大模型与AIGC技术的融入
|
机器学习/深度学习 PyTorch 算法框架/工具
数据平衡与采样:使用 DataLoader 解决类别不平衡问题
【8月更文第29天】在机器学习项目中,类别不平衡问题非常常见,特别是在二分类或多分类任务中。当数据集中某个类别的样本远少于其他类别时,模型可能会偏向于预测样本数较多的类别,导致少数类别的预测性能较差。为了解决这个问题,可以采用不同的策略来平衡数据集,包括过采样(oversampling)、欠采样(undersampling)以及合成样本生成等方法。本文将介绍如何利用 PyTorch 的 `DataLoader` 来处理类别不平衡问题,并给出具体的代码示例。
2485 2
|
SQL 关系型数据库 MySQL
MySQL中的12个SQL编写规范
SQL良好习惯提升查询清晰度、效率和安全性,包括使用EXPLAIN分析查询计划、DELETE/UPDATE时加LIMIT限制影响范围、为表和字段添加注释、关键字大写缩进、指定INSERT字段名、先测试后执行、表含主键及时间戳字段、Update/Delete需Where条件、用InnoDB引擎、避免SELECT *,选择UTF8字符集和规范索引命名。
MySQL中的12个SQL编写规范
|
缓存 分布式计算 算法
优化Hadoop MapReduce性能的最佳实践
【8月更文第28天】Hadoop MapReduce是一个用于处理大规模数据集的软件框架,适用于分布式计算环境。虽然MapReduce框架本身具有很好的可扩展性和容错性,但在某些情况下,任务执行可能会因为各种原因导致性能瓶颈。本文将探讨如何通过调整配置参数和优化算法逻辑来提高MapReduce任务的效率。
1223 0