Python 日志模块 logging
是比较好用的,但似乎官方文档写得不是很好。这里分享一下我是怎么使用的。基础的就不介绍了,只介绍 2 个知识点:第一,日志信息如何从模块传递给 main 脚本;第二,如何使用 filter 选择性处理日志。
logging
日志包含几个互相关联的部分:
- Logger - 产生 LogRecord (日志记录对象)
- Handler - 处理日志,如输出到文件
- Formatter - 将日志信息格式化
- Filter - 按条件过滤或处理日志
由于 Logger 是会按层级传递日志记录的,比如名字为 A.B
的 Logger 会将日志传递给名字为 A
的 Logger,并且所有 Logger 都会将消息往上传递,直到 root Logger,利用这个机制可以将日志信息从模块传递给 main,而不是将 Logger 本身在不同模块传递。
下面是第一个知识点的示例代码,假如有个 main.py
它会调用 child.py
的函数并且 child.py
产生的日志由 main.py
处理。
$ ls
child.py main.py __pycache__
child.py
代码如下。
import logging
_logger = logging.getLogger(name=__name__)
def create_log():
_logger.info("info from child.py")
_logger.error("child.py has some problem")
if __name__ == "__main__":
pass
在 child.py
使用 getLogger
函数创建了个 Logger 并使用文件名命名,这是推荐的创建 Logger 对象方法。之后在 create_log
函数创建了一条 INFO 和一条 ERROR 日志。
main.py
代码如下。
import logging
from child import create_log
logger = logging.getLogger()
logger.setLevel("DEBUG")
formater = logging.Formatter(fmt="[%(levelname)s %(asctime)s] %(message)s")
handler = logging.StreamHandler()
handler.setFormatter(formater)
handler.setLevel("DEBUG")
logger.addHandler(handler)
logger.info("info from main.py")
logger.debug("debug from main.py")
logger.warning("warning from main.py")
create_log()
在 main.py
同样使用 getLogger
函数创建 Logger 但是不输入名字,此时获取的是 root Logger,因此 child.py
的 Logger 会将日志记录传递过来。
使用 setLevel
设置了 Logger 日志等级,低于等级设置的日志将被忽略,比如设置为 ERROR 时那么由于 WARNING, INFO, DEBUG 都是低于 ERROR 等级的,这些日志记录会被忽略。
使用 Formatter
定义了日志信息格式,其格式为 [等级 时间] 信息
。
使用 StreamHandler
创建一个流 Handler 默认流为 stderr 因此会将日志信息输出到标准错误输出。将 Formatter 添加到这个 Handler 并设置 Handler 的日志等级,Handler 将会按照规定格式输出高于等级的日志信息到标准错误输出。
将 Handler 添加到 root Logger 就能处理所有的日志信息了。
运行 main.py
后得到以下屏幕输出。可以看到 child.py
的日志信息顺利传递给了 main.py
并得到处理。
$ python main.py
[INFO 2024-04-22 20:02:34,558] info from main.py
[DEBUG 2024-04-22 20:02:34,558] debug from main.py
[WARNING 2024-04-22 20:02:34,558] warning from main.py
[INFO 2024-04-22 20:02:34,558] info from child.py
[ERROR 2024-04-22 20:02:34,559] child.py has some problem
如果将 Formatter 格式修改为输出模块名 "[%(levelname)s %(module)s] %(message)s" 那么日志将显示为。
$ python main.py
[INFO main] info from main.py
[DEBUG main] debug from main.py
[WARNING main] warning from main.py
[INFO child] info from child.py
[ERROR child] child.py has some problem
第二个知识点示例代码如下。在 main.py
写一个自己的 Filter
类,继承自 logging.Filter
在里面覆盖 filter
方法,设置自己的过滤条件。在示例里设置输出 message 长度小于或等于 20 的日志。
import logging
class MyFilter(logging.Filter):
def __init__(self, name: str = "") -> None:
super().__init__(name)
def filter(self, record: logging.LogRecord) -> bool:
if len(record.msg) > 20:
return False
else:
return True
logger = logging.getLogger()
logger.setLevel("DEBUG")
formater = logging.Formatter(fmt="[%(levelname)s %(module)s] %(message)s")
handler = logging.StreamHandler()
handler.setFormatter(formater)
handler.setLevel("DEBUG")
# 添加 Filter 到 handler
handler.addFilter(MyFilter())
logger.addHandler(handler)
logger.info("a long long long long long info")
logger.info("a short info")
运行输出如下。
$ python main.py
[INFO main] a short info
可以看到实现了想要的过滤效果。
使用 Filter 也可以修改日志,比如。
class MyFilter(logging.Filter):
def __init__(self, name: str = "") -> None:
super().__init__(name)
def filter(self, record: logging.LogRecord) -> bool:
if len(record.msg) > 20:
record.msg = ">20"
else:
record.msg = "<=20"
return True
那么 main.py
运行输出为。
$ python main.py
[INFO main] >20
[INFO main] <=20
网友评论