Python 日志模块 logging

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
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,277评论 6 503
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,689评论 3 393
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,624评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,356评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,402评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,292评论 1 301
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,135评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,992评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,429评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,636评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,785评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,492评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,092评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,723评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,858评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,891评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,713评论 2 354

推荐阅读更多精彩内容