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…...
Phi-4-mini-reasoning效果验证:在MMLU-Pro数学子集上的实际推理准确率展示
Phi-4-mini-reasoning效果验证:在MMLU-Pro数学子集上的实际推理准确率展示 1. 模型概述 Phi-4-mini-reasoning是一款3.8B参数的轻量级开源模型,由微软Azure AI Foundry团队开发。这款模型专为数学推理、逻辑推导和多步解题等强逻辑任务设计,…...
避开STM32H743的坑:GPIO复用配置常见错误与排查指南(附引脚分配图详解)
避开STM32H743的坑:GPIO复用配置常见错误与排查指南 在STM32H743的开发过程中,GPIO复用配置往往是让开发者又爱又恨的部分。爱它是因为灵活多变的外设复用能力让这颗高性能MCU如虎添翼;恨它则是因为稍有不慎就会陷入各种配置冲突和功能异常的…...
MOVA割草机器人:开启自主决策新时代
随着AI感知技术在户外场景加速落地,MOVA率先推出AI双目视觉割草机器人ViAX系列,实现多传感器融合,让割草机迈入“自主决策时代”,全球销量快速增长。技术跃迁:从自动到自主 AI感知技术向户外场景渗透,割草机…...
无人驾驶车辆轨迹跟踪MPC、LQR、PP算法对比仿真(带说明文档)
✅作者简介:热爱科研的Matlab仿真开发者,擅长毕业设计辅导、数学建模、数据处理、建模仿真、程序设计、完整代码获取、论文复现及科研仿真。🍎 往期回顾关注个人主页:Matlab科研工作室👇 关注我领取海量matlab电子书和…...
别再到处找接口了!手把手教你用阿里云盘+Alist搭建自己的TVBox影视仓(附JSON配置模板)
私有影视仓搭建实战:用阿里云盘Alist打造专属TVBox资源库 每次打开TVBox却发现公共接口失效?第三方资源突然无法访问?与其在各大论坛反复搜索不稳定接口,不如用两小时搭建一个完全私有的影视管理系统。本文将彻底改变你获取影音资…...
Steam API集成:构建智能游戏生态的完整PHP解决方案
Steam API集成:构建智能游戏生态的完整PHP解决方案 【免费下载链接】Steam A composer package to make use of the steam web api. 项目地址: https://gitcode.com/gh_mirrors/stea/Steam 在当今游戏开发和社区管理领域,与Steam平台的深度集成已…...
Instant-NGP实战:5分钟用CUDA加速你的NeRF模型渲染(附代码片段)
Instant-NGP实战:5分钟用CUDA加速你的NeRF模型渲染(附代码片段) 当你在深夜调试NeRF模型,看着进度条缓慢爬行,是否想过——如果能像英伟达演示的那样,在10毫秒内完成一帧高清渲染该多好?去年横空…...
华硕笔记本风扇异常修复终极指南:用G-Helper轻松解决散热问题
华硕笔记本风扇异常修复终极指南:用G-Helper轻松解决散热问题 【免费下载链接】g-helper Lightweight, open-source control tool for ASUS laptops and ROG Ally. Manage performance modes, fans, GPU, battery, and RGB lighting across Zephyrus, Flow, TUF, St…...
openclaw里面如何添加channel
在 OpenClaw 中添加 Channel(消息通道 / 渠道),核心是通过 CLI 命令 或直接编辑 配置文件,将 Telegram、Discord、飞书、WhatsApp 等 IM 平台接入网关(Gateway),并绑定到 Agent。以下是完整、可…...
GPEN多场景实战落地:覆盖个人、企业、政府的图像增强应用
GPEN多场景实战落地:覆盖个人、企业、政府的图像增强应用 1. 引言:从模糊到清晰,AI如何重塑我们的视觉记忆 你有没有翻出过一张老照片,画面里的人脸模糊得只剩下轮廓,想看清细节却无能为力?或者ÿ…...
