Python 日志模塊 logging
是比較好用的顶滩,但似乎官方文檔寫得不是很好。這里分享一下我是怎么使用的寸爆〗嘎常基礎(chǔ)的就不介紹了,只介紹 2 個知識點(diǎn):第一赁豆,日志信息如何從模塊傳遞給 main 腳本仅醇;第二,如何使用 filter 選擇性處理日志魔种。
logging
日志包含幾個互相關(guān)聯(lián)的部分:
- Logger - 產(chǎn)生 LogRecord (日志記錄對象)
- Handler - 處理日志析二,如輸出到文件
- Formatter - 將日志信息格式化
- Filter - 按條件過濾或處理日志
由于 Logger 是會按層級傳遞日志記錄的,比如名字為 A.B
的 Logger 會將日志傳遞給名字為 A
的 Logger务嫡,并且所有 Logger 都會將消息往上傳遞甲抖,直到 root Logger,利用這個機(jī)制可以將日志信息從模塊傳遞給 main心铃,而不是將 Logger 本身在不同模塊傳遞准谚。
下面是第一個知識點(diǎn)的示例代碼,假如有個 main.py
它會調(diào)用 child.py
的函數(shù)并且 child.py
產(chǎn)生的日志由 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
函數(shù)創(chuàng)建了個 Logger 并使用文件名命名,這是推薦的創(chuàng)建 Logger 對象方法愉棱。之后在 create_log
函數(shù)創(chuàng)建了一條 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
函數(shù)創(chuàng)建 Logger 但是不輸入名字奔滑,此時獲取的是 root Logger艾岂,因此 child.py
的 Logger 會將日志記錄傳遞過來。
使用 setLevel
設(shè)置了 Logger 日志等級朋其,低于等級設(shè)置的日志將被忽略王浴,比如設(shè)置為 ERROR 時那么由于 WARNING脆炎, INFO, DEBUG 都是低于 ERROR 等級的氓辣,這些日志記錄會被忽略秒裕。
使用 Formatter
定義了日志信息格式,其格式為 [等級 時間] 信息
钞啸。
使用 StreamHandler
創(chuàng)建一個流 Handler 默認(rèn)流為 stderr 因此會將日志信息輸出到標(biāo)準(zhǔn)錯誤輸出几蜻。將 Formatter 添加到這個 Handler 并設(shè)置 Handler 的日志等級,Handler 將會按照規(guī)定格式輸出高于等級的日志信息到標(biāo)準(zhǔn)錯誤輸出体斩。
將 Handler 添加到 root Logger 就能處理所有的日志信息了梭稚。
運(yùn)行 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
第二個知識點(diǎn)示例代碼如下源武。在 main.py
寫一個自己的 Filter
類,繼承自 logging.Filter
在里面覆蓋 filter
方法想幻,設(shè)置自己的過濾條件粱栖。在示例里設(shè)置輸出 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")
運(yùn)行輸出如下脏毯。
$ python main.py
[INFO main] a short info
可以看到實(shí)現(xiàn)了想要的過濾效果闹究。
使用 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
運(yùn)行輸出為渣淤。
$ python main.py
[INFO main] >20
[INFO main] <=20