別再手動配置logging了大家都在用loguru

在部署一些定時運行或者長期運行的任務(wù)時,為了留存一些導(dǎo)致程序出現(xiàn)異常或錯誤的信息,通常會才用日志的方式來進行記錄這些信息。

Python 中用到日志記錄,那就不可避免地會用到內(nèi)置的 logging標(biāo)準(zhǔn)庫 。雖然logging 庫采用的是模塊化設(shè)計,你可以設(shè)置不同的 handler 來進行組合,但是在配置上通常較為繁瑣;而且如果不是特別處理,在一些多線程或多進程的場景下使用 logging還會導(dǎo)致日志記錄會出現(xiàn)錯亂或是丟失的情況。

但有這么一個庫,它不僅能夠減少繁瑣的配置過程還能實現(xiàn)和logging類似的功能,同時還能保證日志記錄的線程進程安全,又能夠和logging 相兼容,并進一步追蹤異常也能進行代碼回溯。這個庫叫loguru——一個專為像我這樣懶人而生日志記錄庫。

loguru 庫的使用可以說是十分簡單,我們直接可以通過導(dǎo)入它本身封裝好的logger 類就可以直接進行調(diào)用。

#!pip install loguru
from loguru import logger

logger 本身就是一個已經(jīng)實例化好的對象,如果沒有特殊的配置需求,那么自身就已經(jīng)帶有通用的配置參數(shù);同時它的用法和 logging庫輸出日志時的用法一致

In [1]: from loguru import logger 
   ...:  
   ...: logger.debug("debug message"    ) 
   ...: logger.info("info level message") 
   ...: logger.warning("warning level message") 
   ...: logger.critical("critical level message")                                                                                                                                               
2020-10-07 14:23:09.637 | DEBUG    | __main__:<module>:3 - debug message
2020-10-07 14:23:09.637 | INFO     | __main__:<module>:4 - info level message
2020-10-07 14:23:09.638 | WARNING  | __main__:<module>:5 - warning level message
2020-10-07 14:23:09.638 | CRITICAL | __main__:<module>:6 - critical level message

當(dāng)你在IDE 或終端里運行時會發(fā)現(xiàn),loguru 還為輸出的日志信息帶上了不同的顏色樣式(schema),使得結(jié)果更加美觀。

1.png

當(dāng)然,loguru 也像logging一樣為我們提供了其他可配置的部分,但相比于 logging 每次要導(dǎo)入特定的handler再設(shè)定一些formatter來說是更為「傻瓜化」了。

配置

使用基本的add() 方法就可以對logger 進行簡單的配置,這些配置有點類似于使用 logging 時的 handler。這里簡單提及一下比較常用的幾個。

寫入日志

在不指定任何參數(shù)時,logger 默認(rèn)采用 sys.stderr 標(biāo)準(zhǔn)錯誤輸出將日志輸出到控制臺(console)中;但在linux 服務(wù)器上我們有時不僅讓其輸出,還要以文件的形式進行留存,那么只需要在第一個參數(shù)中傳入一個你想要留存文件的路徑字符串即可。就像這樣:

from loguru import logger
import os

logger.add(os.path.expanduser("~/Desktop/testlog.log"))
logger.info("hello, world!")

這樣在你的桌面上就會直接出現(xiàn)相應(yīng)的testlog.log日志文件了。

但是如果你沒有自己要是用logging沒有預(yù)先封裝來操作,那估計你得寫成這樣:

import logging
import os
import sys
from logging import handlers

log = logging.getLogger(__name__)
log.setLevel(logging.DEBUG)
fmt = logging.Formatter("%(asctime)s | %(levelname)s | %(message)s")
LOGFILE = os.path.expanduser("~/Desktop/testlog.log")

console_handler = logging.StreamHandler(sys.stderr)
console_handler.setFormatter(fmt)
log.addHandler(console_handler)

file_handler = handlers.RotatingFileHandler(LOGFILE)
file_handler.setFormatter(fmt)
log.addHandler(file_handler)

log.info("hello, world")

碼字不易廢話兩句:有需要python學(xué)習(xí)資料的或者有技術(shù)問題交流點擊下方鏈接即可
https://docs.qq.com/doc/DTGpFa2lVeE9jUkRv

日志留存、壓縮與清理

通常來說如果程序或服務(wù)的量級較大,那么就可以通過集成的日志平臺或數(shù)據(jù)庫來對日志信息進行存儲和留存,后續(xù)有需要的話也方便進行日志分析。

但對我們個人或者一些中小型項目來說,通常只需要以文件的形式留存輸出的日志即可。

盡管我們需要將日志寫入到相應(yīng)的文件中,如果是少量的日志那還好,但是如果是日志輸出或記錄時間較長的情況,那么單個日志文件就十分之大,倘若仍然是將日志都寫入到一個文件中,那么當(dāng)日志中的內(nèi)容增長到一定數(shù)量時我們想要讀取并查找相應(yīng)的部分時就十分困難。這時候我們就需要對日志文件進行留存、壓縮,甚至在必要時及時進行清理。

基于以上,我們可以通過對rotation 、compressionretention 三個參數(shù)進行設(shè)定來滿足我們的需要:

rotation 參數(shù)能夠幫助我們將日志記錄以大小、時間等方式進行分割或劃分:

mport os
from loguru import logger

LOG_DIR = os.path.expanduser("~/Desktop/logs")
LOG_FILE = os.path.join(LOG_DIR, "file_{time}.log")
if os.path.exits(LOG_DIR):
    os.mkdir(LOG_DIR)

logger.add(LOG_FILE, rotation = "200KB")
for n in range(10000):
    logger.info(f"test - {n}")

最后呈現(xiàn)如下:

2.png

隨著分割文件的數(shù)量越來越多之后,我們也可以進行壓縮對日志進行留存,這里就要使用到 compression參數(shù),該參數(shù)只要你傳入通用的壓縮文件擴展名即可,如zip、tar、gz等。

import os
from loguru import logger

LOG_DIR = os.path.expanduser("~/Desktop/logs")
LOG_FILE = os.path.join(LOG_DIR, "file_{time}.log")
if os.path.exits(LOG_DIR):
    os.mkdir(LOG_DIR)

logger.add(LOG_FILE, rotation = "200KB", compression="zip")
for n in range(10000):
    logger.info(f"test - {n}")

從結(jié)果可以看到,只要是滿足了rotation分割后的日志文件都被直接壓縮成了zip 文件,文件大小由原本的 200kb 直接減少至10kb,對于一些磁盤空間吃緊的Linux服務(wù)器來說是則是很有必要的。

3.png

當(dāng)然了,如果你不想對日志進行留存,或者只想保留一段時間內(nèi)的日志并對超期的日志進行刪除,那么直接使用 retention 參數(shù)就好了。

這里我們可以將之前的結(jié)果隨意復(fù)制 N 多份在logs文件夾中,然后再執(zhí)行一次加上 retension 參數(shù)后代碼:

from loguru import logger

LOG_DIR = os.path.expanduser("~/Desktop/logs")
LOG_FILE = os.path.join(LOG_DIR, "file_{time}.log")
if not os.path.exists(LOG_DIR):
    os.mkdir(LOG_DIR)

logger.add(LOG_FILE, rotation="200KB",retention=1)
for n in range(10000):
    logger.info(f"test - {n}")

當(dāng)然對retention 傳入整數(shù)時,該參數(shù)表示的是所有文件的索引,而非要保留的文件數(shù),這里是個反直覺的小坑,用的時候注意一下就好了。所以最后我們會看到只有兩個時間最近的日志文件會被保留下來,其他都被直接清理掉了。

4.png

序列化

如果在實際中你不太喜歡以文件的形式保留日志,那么你也可以通過 serialize 參數(shù)將其轉(zhuǎn)化成序列化的json格式,最后將導(dǎo)入類似于MongoDB、ElasticSearch 這類數(shù)NoSQL 數(shù)據(jù)庫中用作后續(xù)的日志分析。

from loguru import logger
import os

logger.add(os.path.expanduser("~/Desktop/testlog.log"), serialize=True)
logger.info("hello, world!")

最后保存的日志都是序列化后的單條記錄:

{
    "text": "2020-10-07 18:23:36.902 | INFO     | __main__:<module>:6 - hello, world\n",
    "record": {
        "elapsed": {
            "repr": "0:00:00.005412",
            "seconds": 0.005412
        },
        "exception": null,
        "extra": {},
        "file": {
            "name": "log_test.py",
            "path": "/Users/Bobot/PycharmProjects/docs-python/src/loguru/log_test.py"
        },
        "function": "<module>",
        "level": {
            "icon": "\u2139\ufe0f",
            "name": "INFO",
            "no": 20
        },
        "line": 6,
        "message": "hello, world",
        "module": "log_test",
        "name": "__main__",
        "process": {
            "id": 12662,
            "name": "MainProcess"
        },
        "thread": {
            "id": 4578131392,
            "name": "MainThread"
        },
        "time": {
            "repr": "2020-10-07 18:23:36.902358+08:00",
            "timestamp": 1602066216.902358
        }
    }
}

異常追溯

當(dāng)異常和錯誤不可避免時,最好的方式就是讓我們知道程序到底是哪里出了錯,或者是因為什么導(dǎo)致錯誤,這樣才能更好地讓開發(fā)人員及時應(yīng)對并解決。

loguru集成了一個名為better_exceptions 的庫,不僅能夠?qū)惓:湾e誤記錄,并且還能對異常進行追溯,這里是來自一個官網(wǎng)的例子

import os
import sys

from loguru import logger

logger.add(os.path.expanduser("~/Desktop/exception_log.log"), backtrace=True, diagnose=True)

def func(a, b):
    return a / b

def nested(c):
    try:
        func(5, c)
    except ZeroDivisionError:
        logger.exception("What?!")

if __name__ == "__main__":
    nested(0)

最后在日志文件中我們可以得到以下內(nèi)容:

File "/Users/Bobot/PycharmProjects/docs-python/src/loguru/log_test.py", line 20, in <module>
    nested(0)
    └ <function nested at 0x7fb9300c1170>

> File "/Users/Bobot/PycharmProjects/docs-python/src/loguru/log_test.py", line 14, in nested
    func(5, c)
    │       └ 0
    └ <function func at 0x7fb93010add0>

  File "/Users/Bobot/PycharmProjects/docs-python/src/loguru/log_test.py", line 10, in func
    return a / b
           │   └ 0
           └ 5

ZeroDivisionError: division by zero

與 Logging 完全兼容(Entirely Compatible)

盡管說loguru 算是重新「造輪子」,但是它也能和logging庫很好地兼容。到現(xiàn)在我們才談?wù)摰?code>add() 方法的第一個參數(shù) sink。

這個參數(shù)的英文單詞動詞有「下沉、浸沒」等意,對于外國人來說在理解上可能沒什么難的,可對我們國人來說,這可之前logging 庫中的handler 概念還不好理解。好在前面我有說過,logurulogging 庫的使用上存在相似之處,因此在后續(xù)的使用中其實我們就可以將其理解為handler,只不過它的范圍更廣一些,可以除了 handler 之外的字符串、可調(diào)用方法、協(xié)程對象等。

loguru 官方文檔對這一參數(shù)的解釋是:

 object in charge of receiving formatted logging messages and propagating them to an appropriate endpoint.

翻譯過來就是「一個用于接收格式化日志信息并將其傳輸合適端點的對象」,進一步形象理解就像是一個「分流器」。

import logging.handlers
import os
import sys

from loguru import logger

LOG_FILE = os.path.expanduser("~/Desktop/testlog.log")
file_handler = logging.handlers.RotatingFileHandler(LOG_FILE, encoding="utf-8")
logger.add(file_handler)
logger.debug("hello, world")

當(dāng)然目前只是想在之前基于logging 寫好的模塊中集成loguru,只要重新編寫一個繼承自 logging.Handler 類并實現(xiàn)了emit() 方法的Handler 即可。

import logging.handlers
import os
import sys

from loguru import logger

class InterceptHandler(logging.Handler):
    def emit(self, record):
        try:
            level = logger.level(record.levelname).name
        except ValueError:
            level = record.levelno

        frame, depth = logging.currentframe(), 2
        while frame.f_code.co_filename == logging.__file__:
            frame = frame.f_back
            depth += 1

        logger.opt(depth=depth, exception=record.exc_info).log(level, record.getMessage())

logging.basicConfig(handlers=[InterceptHandler()], level=0)

def func(a, b):
    return a / b

def nested(c):
    try:
        func(5, c)
    except ZeroDivisionError:
        logging.exception("What?!")

if __name__ == "__main__":
    nested(0)

后結(jié)果同之前的異常追溯一致。而我們只需要在配置后直接調(diào)用logging 的相關(guān)方法即可,減少了遷移和重寫的成本。

最后

本文介紹了關(guān)于loguru的常用方法,從對比例子上來看,相比于復(fù)雜的 logging 配置來說,使用loguru 庫無疑還是很香的,畢竟別人已經(jīng)為我們一些日常的通用性需求提供了封裝好的解決方案,無論是在學(xué)習(xí)還是在使用的成本上,無疑還是比較小的。

由于篇幅有限,loguru的其他配置部分沒有進一步展開,如果看完本文的你對這個庫感興趣并打算投入到實際的開發(fā)和生產(chǎn)中使用,那么建議你還是閱讀一下其官方文檔,有必要的話可以瀏覽一下源碼。

不過loguru的通用配置不一定滿足每個人的需要,對于那些動手能力強或水平較高的朋友還能進一步根據(jù)個人需求或業(yè)務(wù)需求進行二次封裝,或許也能較為貼合實際情況。

以上就是小編今天為大家?guī)淼膬?nèi)容,小編本身就是一名python開發(fā)工程師,我自己花了三天時間整理了一套python學(xué)習(xí)教程,從最基礎(chǔ)的python腳本到web開發(fā),爬蟲,數(shù)據(jù)分析,數(shù)據(jù)可視化,機器學(xué)習(xí),等,這些資料有想要的小伙伴點擊下方連接即可領(lǐng)取
https://docs.qq.com/doc/DTGpFa2lVeE9jUkRv

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容