Python logging 模块详解
Python 的 logging
模块提供了一个强大而灵活的日志系统。它是 Python 标准库的一部分,因此可以在任何 Python 程序中使用。logging
模块提供了许多有用的功能,包括日志消息的级别设置、日志消息的格式设置、将日志消息输出到不同的目标,以及处理复杂的日志系统配置。
logging 组成部分
在 logging 模块中主要有四部分:Logger、Handler、Filter 和 Formatter。下面分别对这四部分进行介绍。
Logger
Logger 就是程序可以直接调用的一个日志接口,可以直接向 Logger 写入日志信息。但是 Logger 并不是直接实例化使用的,而是通过 logging.getLogger(name)
来获取对象,事实上 Logger 对象是单例模式,并且 logging 是多线程安全的,也就是在程序的任何地方使用的同名的 Logger 都指向了一个实例。
Handler
Handler 作用于 Logger,是真正的日志处理程序,一个 Logger 可以配置多个 Handler。同样的,一个 Handler 也可以作用在多个 Logger 上。每个 Handler 同样有一个日志级别,也就是说 Logger 可以根据不同的日志级别将日志传递给不同的 Handler,当然也可以相同的级别传递给多个 Handler 这就根据需求来灵活的设置了。
Handler 既有系统定义的,比如将日志输出到标准输出 stdout。也可以自定义日志处理器,比如将日志发送到邮件或者第三方的日志存储介质等。
Filter
Filter 提供了更细粒度的判断,来决定日志是否需要打印。原则上 Handler 获得一个日志就必定会根据日志级别进行统一处理,但是如果 Handler 拥有一个 Filter 的时候就可以对日志进行额外的处理和判断。例如 Filter 能够对来自特定源的日志进行拦截甚至进行修改(包括日志级别的修改,修改后再进行级别判断)。
Logger 和 Handler 都可以安装 Filter 甚至可以安装多个 Filter 串联起来。
Formatter
Formatter 指定了最终某条记录打印的格式。Formatter 会将传递来的信息拼接成一条具体的字符串,默认情况下 Formatter 只会将信息 %(message)s
直接打印出来。Formatter 中有一些自带的属性可以使用,如下表格:
注意的是一个 Handler 只能拥有一个 Formatter。
日志级别
作用
日志级别是一个用于控制和过滤日志消息的重要工具。日志级别可以帮助更有效地管理日志,只看到需要的信息,而忽略不必要的细节。
这个功能在开发和运行大型程序时非常有用。例如,在开发过程中,你可能需要 DEBUG 级别的日志来找出问题。但是在生产环境中,这么详细的日志可能会占用大量的磁盘空间,而且大部分信息可能都是不必要的。因此,在生产环境中,你可能只需要 WARNING 或者 ERROR 级别的日志。
级别
在记录日志时, 日志消息都会关联一个级别(“级别”本质上是一个非负整数)。系统默认提供了6个级别,它们分别是:
级别 | 对应值 |
---|---|
CRITICAL | 50 |
ERROR | 40 |
WARNING | 30 |
INFO | 20 |
DEBUG | 10 |
NOTSET | 0 |
继承关系和处理流程
Logger 继承关系
Logger 对象是有父子关系的,当没有父 Logger 对象时它的父对象是 root,当拥有父对象时父子关系会被修正。举个例子,logging.getLogger("abc.xyz")
会创建两个 Logger 对象,一个是 abc 父对象,一个是 xyz 子对象,同时 abc 没有父对象,所以它的父对象是 root。但是实际上 abc 是一个占位对象(虚的日志对象),可以没有 Handler 来处理日志。但是 root 不是占位对象,如果某一个日志对象打日志时,它的父对象会同时收到日志,所以有些使用者发现创建了一个 Logger 对象时会打两遍日志,就是因为他创建的 Logger 打了一遍日志,同时 root 对象也打了一遍日志。
1)level的继承
子 Logger 写日志时,优先使用本身设置了的 level;如果没有设置,则逐层向上级父 Logger 查询,直到查询到为止。最极端的情况是,使用 root Logger 的默认日志级别 logging.WARNING。
参考源码:
def getEffectiveLevel(self):"""Get the effective level for this logger.Loop through this logger and its parents in the logger hierarchy,looking for a non-zero logging level. Return the first one found."""logger = selfwhile logger:if logger.level:return logger.levellogger = logger.parentreturn NOTSET
2)Handler 的继承
先将日志对象传递给子 Logger 的所有 Handler 处理,处理完毕后,如果该子 Logger 的 propagate 属性没有设置为 False,则将日志对象向上传递给第一个父 Logger,该父 Logger 的所有 Handler 处理完毕后,如果它的 propagate 也没有设置为 False,则继续向上层传递,以此类推。最终的状态,要么遇到一个 Logger,它的 propagate 属性设置为了 False;要么一直传递直到 root Logger 处理完毕。
注意,Handler 不是真正的(类)继承,只是“行为上的继承”,也就是子 Logger 并没有没有绑定父类的 Handler。
参考源码:
def callHandlers(self, record):"""Pass a record to all relevant handlers.Loop through all handlers for this logger and its parents in thelogger hierarchy. If no handler was found, output a one-off errormessage to sys.stderr. Stop searching up the hierarchy whenever alogger with the "propagate" attribute set to zero is found - thatwill be the last logger whose handlers are called."""c = selffound = 0while c:for hdlr in c.handlers:found = found + 1if record.levelno >= hdlr.level:hdlr.handle(record)if not c.propagate:c = None # break outelse:c = c.parentif (found == 0) and raiseExceptions and not self.manager.emittedNoHandlerWarning:sys.stderr.write("No handlers could be found for logger"" \"%s\"\n" % self.name)self.manager.emittedNoHandlerWarning = 1
日志记录的处理流程
在一个给定继承关系的日志配置中,如果其中任何一个 Logger 进行了日志记录,会经历下面几个步骤:
- 首先这条记录会被 Logger 本身的 Handler 进行处理。
- 检查日志级别是否满足最低要求
- 是否有 Filter 进行更细粒度的处理
- 用什么样的 Formatter 作为打印格式
- 默认 Logger 的 propagate 值为 True,也就是在自己进行日志处理的同时,也会把这条日志代理到其父 Logger 进行处理,一直会被代理到 root Logger。
参考下面代码:
import logging# root logger
logging.basicConfig(level=logging.INFO)# parent logger
logger_parent = logging.getLogger("parent")
handler = logging.StreamHandler()
handler.setLevel(logging.CRITICAL) # Set the handler's level to CRITICAL
logger_parent.addHandler(handler)
logger_parent.setLevel(level=logging.CRITICAL)# child logger
logger_child = logging.getLogger("parent.child")
logger_child.addHandler(logging.StreamHandler())
logger_child.setLevel(level=logging.INFO)if __name__ == "__main__":logger_child.info("Hi")
其输出结果为:
Hi
INFO:parent.child:Hi
在上面代码中有三个日志记录器,其继承关系分别是 logger_child 继承 logger_parent 继承 root。
每个日志记录器上设置的日志级别,比如代码 logger_parent.setLevel(level=logging.CRITICAL)
不会影响记录器对日志的代理关系,其影响的是这个记录器在使用时是否处理程序中的日志记录。比如 logger_parent 在使用的时候会忽略 CRITICAL 级别之下的日志。
在上述程序中:
- Hi 这条日志首先会被 logger_child 的 Handler 进行处理。
- 然后这条记录会被代理到 logger_parent,logger_parent 的 handler 会对这条日志做处理,由于 logger_parent 的 Handler 是 CRITICAL 级别的,因此忽略这条日志。
- 接下来这条日志会继续被代理到 root,root 的 Handler 接下来会继续处理这条日志。
因此日志是否会被向上代理,取决于 Logger 上的 propagate 属性。
使用实例
基本日志打印
在下面的例子中,默认的日志级别是 WRANING,因此只有最后一个日志被打印出来。
import logginglogging.debug('this is debug message')
logging.info('this is info message')
logging.warning('this is warning message')# 打印结果:WARNING:root:this is warning message
root 日志配置
在下面的例子中,root 日志记录器可以通过 logging.basicConfig 进行配置,比如配置其默认的日志级别,Handler 或者输出格式等。
import logginglogging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(name)s - %(message)s')if __name__ == '__main__':logging.debug('this is debug message')logging.info('this is info message')logging.warning('this is warning message')'''''
结果:
2024-06-06 17:10:43,873 - root - this is debug message
2024-06-06 17:10:43,873 - root - this is info message
2024-06-06 17:10:43,873 - root - this is warning message
'''
将日志同时输出到文件和 stdout
在下面的配置中,定义了一个文件 Handler 和标准输出 Handler,并将其应用到 logger 日志处理器上,这样日志会同时被输出到文件和标准输出中。
如果你用 FileHandler 写日志,文件的大小会随着时间推移而不断增大。为了避免这种情况出现,可以在你的生成环境中使用 RotatingFileHandler 替代 FileHandler 做日志回滚。
import logginglogger = logging.getLogger(__name__)
logger.setLevel(level=logging.INFO)# 文件 Handler
file_handler = logging.FileHandler("log.txt")
file_handler.setLevel(logging.INFO)
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
file_handler.setFormatter(formatter)# 标准输出 Handler
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.INFO)# 在 logger 中应用这两个 Handler
logger.addHandler(file_handler)
logger.addHandler(console_handler)logger.info("Start print log")
logger.debug("Do something")
logger.warning("Something maybe fail.")
logger.info("Finish")
使用适配器扩展
在下面的例子中通过 logging 提供的 LoggerAdapter 方法可以对日志的字段进行扩展,输出的时候通过适当的 Formatter 进行扩展字段的输出。
import loggingformat_str = '%(levelname)s %(filename)s %(document_id)s %(message)s'
logging.basicConfig(level=logging.INFO, format=format_str)logger = logging.getLogger(__name__)extra = {'document_id': 'doc id'}
logger = logging.LoggerAdapter(logger, extra)logger.info({'name': 'Xiao Ming'})# INFO example_use_adapter.py doc id {'name': 'Xiao Ming'}
捕捉异常并使用 traceback 记录
当程序出现错误的时候,在使用 Logger 进行记录的时候通过设置参数 exc_info=True
可以在日志中记录详细的报错信息。
import logginglogger = logging.getLogger(__name__)try:open('/path/to/does/not/exist', 'rb')
except Exception as e:logger.error('Failed to open file', exc_info=True)'''
Failed to open file
Traceback (most recent call last):File "/Users/crown/Projects/python101/playground/logging_watchtower/example_traceback.py", line 6, in <module>open('/path/to/does/not/exist', 'rb')
FileNotFoundError: [Errno 2] No such file or directory: '/path/to/does/not/exist'
'''
通过配置文件
如果日志的配置比较复杂,可以考虑通过外置配置文件的方式进行 logging 配置,logging 模块支持 json 格式和 yaml 两种格式的配置文件,通过配置文件可以更清晰的进行负责日志的配置。
下面是通过 json 配置文件的方式对 logging 进行配置。
{"version": 1,"disable_existing_loggers": false,"formatters": {"simple": {"format": "%(asctime)s - %(name)s - %(levelname)s - %(message)s"}},"handlers": {"console": {"class": "logging.StreamHandler","level": "DEBUG","formatter": "simple","stream": "ext://sys.stdout"},"info_file_handler": {"class": "logging.handlers.RotatingFileHandler","level": "INFO","formatter": "simple","filename": "info.log","encoding": "utf8"}},"loggers": {"my_module": {"level": "ERROR","handlers": ["info_file_handler"],"propagate": "no"}},"root": {"level": "INFO","handlers": ["console","info_file_handler"]}
}
在 python 中读取配置文件:
{"version": 1,"disable_existing_loggers": false,"formatters": {"simple": {"format": "%(asctime)s - %(name)s - %(levelname)s - %(message)s"}},"handlers": {"console": {"class": "logging.StreamHandler","level": "DEBUG","formatter": "simple","stream": "ext://sys.stdout"},"info_file_handler": {"class": "logging.handlers.RotatingFileHandler","level": "INFO","formatter": "simple","filename": "info.log","encoding": "utf8"}},"loggers": {"my_module": {"level": "ERROR","handlers": ["info_file_handler"],"propagate": "no"}},"root": {"level": "INFO","handlers": ["console","info_file_handler"]}
}
参考文档
[1] python基础学习十 logging模块详细使用 https://www.cnblogs.com/louis-w/p/8567434.html
[2] github django logging https://github.com/django/django/blob/1586a09b7949bbb7b0d84cb74ce1cadc25cbb355/django/utils/log.py#L18
相关文章:

Python logging 模块详解
Python 的 logging 模块提供了一个强大而灵活的日志系统。它是 Python 标准库的一部分,因此可以在任何 Python 程序中使用。logging 模块提供了许多有用的功能,包括日志消息的级别设置、日志消息的格式设置、将日志消息输出到不同的目标,以及…...

http://account.battlenet.com.cn
http://account.battlenet.com.cn 魔兽战网 短信验证 查了下,我老早以前账号还在,纪念下,少玩游戏。...

java第二十课 —— 面向对象习题
类与对象练习题 编写类 A01,定义方法 max,实现求某个 double 数组的最大值,并返回。 public class Chapter7{public static void main(String[] args){A01 m new A01();double[] doubleArray null;Double res m.max(doubleArray);if(res !…...
Flask的模块化实践
既作为前端,又作为后端的我,写flask写了那么多行了,其实它们属于不同的模块,比如登录,注册,聊天,用户画像,那我觉得有必要分一下了,系统化的处理一下,不然找个…...

锁存器(Latch)的产生与特点
Latch 是什么 Latch 其实就是锁存器,是一种在异步电路系统中,对输入信号电平敏感的单元,用来存储信息。锁存器在数据未锁存时,输出端的信号随输入信号变化,就像信号通过一个缓冲器,一旦锁存信号有效&#…...

搜维尔科技:「案例」Faceware电影中面部动画的演变历程
面部动画是电影中角色表演的一个重要方面,尤其是在严重依赖电子动画、化妆效果和动作捕捉系统的奇幻电影中。在《龙与地下城:盗贼荣誉》电影中,龙裔角色的面部动画是一个复杂的系统,使该生物在大屏幕上栩栩如生。该系统依赖于一种…...

特征工程技巧—Bert
前段时间在参加比赛,发现有一些比赛上公开的代码,其中的数据预处理步骤值得我们参考。 平常我们见到的都是数据预处理,现在我们来讲一下特征工程跟数据预处理的区别。 数据预处理是指对原始数据进行清洗、转换、缩放等操作,以便为…...
更改 Docker 的默认存储位置
记录一下使用 Docker 遇到的问题,Docker 也用得比较多,最近发现根目录所在磁盘快满了,发现是 Docker 默认会将镜像和容器等数据保存在目录 /var/lib/docker 目录下,我们可以更改 Docker 的默认存储位置,比如改到数据盘…...

搜索与图论:图中点的层次
搜索与图论:图中点的层次 题目描述参考代码 题目描述 输入样例 4 5 1 2 2 3 3 4 1 3 1 4输出样例 1参考代码 #include <cstring> #include <iostream> #include <algorithm>using namespace std;const int N 100010;int n, m; int h[N], e[N]…...
NLP入门——数据预处理:编码规范化
编码规范化 在计算机中,我们需要将字符与字节序列之间建立起映射关系,这个过程被称为编码。有许多不同的编码方式,例如 ASCII、UTF-8、UTF-16 和 GBK 等。这些编码方式会将每个字符编码为一个或多个字节,以便于在计算机、网络和其…...
代码随想录算法训练营第四十八天| 70. 爬楼梯 (进阶)、322. 零钱兑换、279.完全平方数
70. 爬楼梯 (进阶) 题目链接:70. 爬楼梯 (进阶) 文档讲解:代码随想录/爬楼梯 (进阶) 状态:已完成(0遍) 解题过程 这几天博主忙着面试和入职&am…...
c++11 constexpr关键字
constexpr 是 C11 引入的一个关键字,它允许在编译时计算表达式的值,并将这些值存储在程序的常量部分中。这意味着 constexpr 变量和函数可以在编译时进行求值,从而避免了运行时的开销。 constexpr变量 constexpr 变量必须在编译时初始化&am…...
ios 获取图片的一部分区域
可以使用如下的代码: // get part of the image - (UIImage *)getPartOfImage:(UIImage *)img rect:(CGRect)partRect {CGImageRef imageRef img.CGImage;CGImageRef imagePartRef CGImageCreateWithImageInRect(imageRef, partRect);UIImage *retImg [UIImage i…...

数据结构(3)栈、队列、数组
1 栈 1.1 栈的定义 后进先出【LIFO】 1.2 基本操作 元素进栈出栈 只能在栈顶进行!!! 经常考的题: 穿插的进行进栈和出栈 可能有多个选项 1.3 顺序栈 1.3.1 初始化 下标是从0开始的 1.3.2 进栈 更简单的写法: 1.3…...
数据库(入门)
文章目录 一、数据库(DB) 二、数据库管理系统(DBMS) 三、SQL(结构化查询语言) 四、三者的关系 五、端口号(port number) 一、数据库(DB) 定义:按照一定格式存储数据的一些文件的组合。 简单来…...

ChatTTS+Python编程搞定语音报时小程序
文字转语音神器Python编程搞定语音报时小程序 今天一个好哥们发了一个文字转语音的AI神器的短视频。这个神器的网站是[ChatTTS - Text-to-Speech for Conversational Scenarios][https://chattts.com/],如下图所示: 这个开源项目可以从github.com上下载…...

【Mac】Alfred 5 for Mac(苹果效率提升工具)v5.5软件介绍及安装教程
软件介绍 Alfred 是适用于 Mac 操作系统的流行生产力应用程序。它旨在帮助用户在 Mac 电脑上更高效地启动应用程序、搜索文件和文件夹以及执行各种任务。借助 Alfred,用户可以创建自定义键盘快捷方式、设置自定义工作流程并使用热键访问功能。 Alfred for Mac 的一…...
PDF文件处理不再复杂:9个Python库让一切变得简单
大家好,这里是程序员晚枫,2年前发布了一个开源项目:python-office,目前在GitHub上有800⭐,最近在开发新功能时感觉Python知识有点不够用了。 所以打算从2方面补充自己的知识:研究优秀的第三方库和学习Pyth…...

安防视频融合汇聚平台EasyCVR如何实现视频画面自定义标签?
安防视频融合汇聚平台EasyCVR兼容性强,可支持Windows系统、Linux系统以及国产化操作系统等,平台既具备传统安防视频监控的能力,也具备接入AI智能分析的能力,可拓展性强、视频能力灵活,能对外分发RTMP、RTSP、HTTP-FLV、…...

Liunx音频
一. echo -e "\a" echo 通过向控制台喇叭设备发送字符来发声: echo -e "\a"(这里的 -e 选项允许解释反斜杠转义的字符,而 \a 是一个响铃(bell)字符) 二. beep 下载对应的包 yum -y install beep 发声命令 be…...

超短脉冲激光自聚焦效应
前言与目录 强激光引起自聚焦效应机理 超短脉冲激光在脆性材料内部加工时引起的自聚焦效应,这是一种非线性光学现象,主要涉及光学克尔效应和材料的非线性光学特性。 自聚焦效应可以产生局部的强光场,对材料产生非线性响应,可能…...
React Native 导航系统实战(React Navigation)
导航系统实战(React Navigation) React Navigation 是 React Native 应用中最常用的导航库之一,它提供了多种导航模式,如堆栈导航(Stack Navigator)、标签导航(Tab Navigator)和抽屉…...

《Qt C++ 与 OpenCV:解锁视频播放程序设计的奥秘》
引言:探索视频播放程序设计之旅 在当今数字化时代,多媒体应用已渗透到我们生活的方方面面,从日常的视频娱乐到专业的视频监控、视频会议系统,视频播放程序作为多媒体应用的核心组成部分,扮演着至关重要的角色。无论是在个人电脑、移动设备还是智能电视等平台上,用户都期望…...

ServerTrust 并非唯一
NSURLAuthenticationMethodServerTrust 只是 authenticationMethod 的冰山一角 要理解 NSURLAuthenticationMethodServerTrust, 首先要明白它只是 authenticationMethod 的选项之一, 并非唯一 1 先厘清概念 点说明authenticationMethodURLAuthenticationChallenge.protectionS…...
【HTML-16】深入理解HTML中的块元素与行内元素
HTML元素根据其显示特性可以分为两大类:块元素(Block-level Elements)和行内元素(Inline Elements)。理解这两者的区别对于构建良好的网页布局至关重要。本文将全面解析这两种元素的特性、区别以及实际应用场景。 1. 块元素(Block-level Elements) 1.1 基本特性 …...

【Oracle】分区表
个人主页:Guiat 归属专栏:Oracle 文章目录 1. 分区表基础概述1.1 分区表的概念与优势1.2 分区类型概览1.3 分区表的工作原理 2. 范围分区 (RANGE Partitioning)2.1 基础范围分区2.1.1 按日期范围分区2.1.2 按数值范围分区 2.2 间隔分区 (INTERVAL Partit…...

【分享】推荐一些办公小工具
1、PDF 在线转换 https://smallpdf.com/cn/pdf-tools 推荐理由:大部分的转换软件需要收费,要么功能不齐全,而开会员又用不了几次浪费钱,借用别人的又不安全。 这个网站它不需要登录或下载安装。而且提供的免费功能就能满足日常…...

【Java多线程从青铜到王者】单例设计模式(八)
wait和sleep的区别 我们的wait也是提供了一个还有超时时间的版本,sleep也是可以指定时间的,也就是说时间一到就会解除阻塞,继续执行 wait和sleep都能被提前唤醒(虽然时间还没有到也可以提前唤醒),wait能被notify提前唤醒…...
深入解析 ReentrantLock:原理、公平锁与非公平锁的较量
ReentrantLock 是 Java 中 java.util.concurrent.locks 包下的一个重要类,用于实现线程同步,支持可重入性,并且可以选择公平锁或非公平锁的实现方式。下面将详细介绍 ReentrantLock 的实现原理以及公平锁和非公平锁的区别。 ReentrantLock 实现原理 基本架构 ReentrantLo…...
SpringCloud优势
目录 完善的微服务支持 高可用性和容错性 灵活的配置管理 强大的服务网关 分布式追踪能力 丰富的社区生态 易于与其他技术栈集成 完善的微服务支持 Spring Cloud 提供了一整套工具和组件来支持微服务架构的开发,包括服务注册与发现、负载均衡、断路器、配置管理等功能…...