在部署一些定時(shí)運(yùn)行或者長(zhǎng)期運(yùn)行的任務(wù)時(shí)走芋,為了留存一些導(dǎo)致程序出現(xiàn)異常或錯(cuò)誤的信息潘鲫,通常會(huì)才用日志的方式來(lái)進(jìn)行記錄這些信息翁逞。
在 Python
中用到日志記錄,那就不可避免地會(huì)用到內(nèi)置的 logging
標(biāo)準(zhǔn)庫(kù) 溉仑。雖然logging
庫(kù)采用的是模塊化設(shè)計(jì)挖函,你可以設(shè)置不同的 handler
來(lái)進(jìn)行組合,但是在配置上通常較為繁瑣浊竟;而且如果不是特別處理怨喘,在一些多線程或多進(jìn)程的場(chǎng)景下使用 logging
還會(huì)導(dǎo)致日志記錄會(huì)出現(xiàn)錯(cuò)亂或是丟失的情況。
但有這么一個(gè)庫(kù)振定,它不僅能夠減少繁瑣的配置過(guò)程還能實(shí)現(xiàn)和logging
類(lèi)似的功能必怜,同時(shí)還能保證日志記錄的線程進(jìn)程安全,又能夠和logging
相兼容后频,并進(jìn)一步追蹤異常也能進(jìn)行代碼回溯梳庆。這個(gè)庫(kù)叫loguru
——一個(gè)專為像我這樣懶人而生日志記錄庫(kù)。
loguru
庫(kù)的使用可以說(shuō)是十分簡(jiǎn)單卑惜,我們直接可以通過(guò)導(dǎo)入它本身封裝好的logger
類(lèi)就可以直接進(jìn)行調(diào)用膏执。
#!pip install loguru
from loguru import logger
logger
本身就是一個(gè)已經(jīng)實(shí)例化好的對(duì)象,如果沒(méi)有特殊的配置需求露久,那么自身就已經(jīng)帶有通用的配置參數(shù)胧后;同時(shí)它的用法和 logging
庫(kù)輸出日志時(shí)的用法一致
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
或終端里運(yùn)行時(shí)會(huì)發(fā)現(xiàn),loguru
還為輸出的日志信息帶上了不同的顏色樣式(schema
)抱环,使得結(jié)果更加美觀壳快。
當(dāng)然纸巷,loguru
也像logging
一樣為我們提供了其他可配置的部分,但相比于 logging
每次要導(dǎo)入特定的handler
再設(shè)定一些formatter
來(lái)說(shuō)是更為「傻瓜化」了眶痰。
配置
使用基本的add()
方法就可以對(duì)logger
進(jìn)行簡(jiǎn)單的配置瘤旨,這些配置有點(diǎn)類(lèi)似于使用 logging
時(shí)的 handler
。這里簡(jiǎn)單提及一下比較常用的幾個(gè)竖伯。
寫(xiě)入日志
在不指定任何參數(shù)時(shí)存哲,logger
默認(rèn)采用 sys.stderr
標(biāo)準(zhǔn)錯(cuò)誤輸出將日志輸出到控制臺(tái)(console
)中;但在linux
服務(wù)器上我們有時(shí)不僅讓其輸出七婴,還要以文件的形式進(jìn)行留存祟偷,那么只需要在第一個(gè)參數(shù)中傳入一個(gè)你想要留存文件的路徑字符串即可。就像這樣:
from loguru import logger
import os
logger.add(os.path.expanduser("~/Desktop/testlog.log"))
logger.info("hello, world!")
這樣在你的桌面上就會(huì)直接出現(xiàn)相應(yīng)的testlog.log
日志文件了打厘。
但是如果你沒(méi)有自己要是用logging
沒(méi)有預(yù)先封裝來(lái)操作修肠,那估計(jì)你得寫(xiě)成這樣:
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ù)問(wèn)題交流點(diǎn)擊下方鏈接即可
https://docs.qq.com/doc/DTGpFa2lVeE9jUkRv
日志留存、壓縮與清理
通常來(lái)說(shuō)如果程序或服務(wù)的量級(jí)較大户盯,那么就可以通過(guò)集成的日志平臺(tái)或數(shù)據(jù)庫(kù)來(lái)對(duì)日志信息進(jìn)行存儲(chǔ)和留存嵌施,后續(xù)有需要的話也方便進(jìn)行日志分析。
但對(duì)我們個(gè)人或者一些中小型項(xiàng)目來(lái)說(shuō)莽鸭,通常只需要以文件的形式留存輸出的日志即可吗伤。
盡管我們需要將日志寫(xiě)入到相應(yīng)的文件中,如果是少量的日志那還好硫眨,但是如果是日志輸出或記錄時(shí)間較長(zhǎng)的情況足淆,那么單個(gè)日志文件就十分之大,倘若仍然是將日志都寫(xiě)入到一個(gè)文件中礁阁,那么當(dāng)日志中的內(nèi)容增長(zhǎng)到一定數(shù)量時(shí)我們想要讀取并查找相應(yīng)的部分時(shí)就十分困難缸浦。這時(shí)候我們就需要對(duì)日志文件進(jìn)行留存、壓縮氮兵,甚至在必要時(shí)及時(shí)進(jìn)行清理裂逐。
基于以上,我們可以通過(guò)對(duì)rotation 泣栈、compression
和retention
三個(gè)參數(shù)進(jìn)行設(shè)定來(lái)滿足我們的需要:
rotation
參數(shù)能夠幫助我們將日志記錄以大小卜高、時(shí)間等方式進(jìn)行分割或劃分:
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)如下:
隨著分割文件的數(shù)量越來(lái)越多之后,我們也可以進(jìn)行壓縮對(duì)日志進(jìn)行留存南片,這里就要使用到 compression
參數(shù)掺涛,該參數(shù)只要你傳入通用的壓縮文件擴(kuò)展名即可,如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
疼电,對(duì)于一些磁盤(pán)空間吃緊的Linux
服務(wù)器來(lái)說(shuō)是則是很有必要的。
當(dāng)然了减拭,如果你不想對(duì)日志進(jìn)行留存蔽豺,或者只想保留一段時(shí)間內(nèi)的日志并對(duì)超期的日志進(jìn)行刪除,那么直接使用 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)然對(duì)retention
傳入整數(shù)時(shí),該參數(shù)表示的是所有文件的索引可霎,而非要保留的文件數(shù)魄鸦,這里是個(gè)反直覺(jué)的小坑,用的時(shí)候注意一下就好了癣朗。所以最后我們會(huì)看到只有兩個(gè)時(shí)間最近的日志文件會(huì)被保留下來(lái)拾因,其他都被直接清理掉了。
序列化
如果在實(shí)際中你不太喜歡以文件的形式保留日志斯棒,那么你也可以通過(guò) serialize
參數(shù)將其轉(zhuǎn)化成序列化的json
格式盾致,最后將導(dǎo)入類(lèi)似于MongoDB主经、ElasticSearch
這類(lèi)數(shù)NoSQL
數(shù)據(jù)庫(kù)中用作后續(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)異常和錯(cuò)誤不可避免時(shí),最好的方式就是讓我們知道程序到底是哪里出了錯(cuò)罩驻,或者是因?yàn)槭裁磳?dǎo)致錯(cuò)誤穗酥,這樣才能更好地讓開(kāi)發(fā)人員及時(shí)應(yīng)對(duì)并解決。
loguru
集成了一個(gè)名為better_exceptions
的庫(kù)惠遏,不僅能夠?qū)惓:湾e(cuò)誤記錄砾跃,并且還能對(duì)異常進(jìn)行追溯,這里是來(lái)自一個(gè)官網(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)
盡管說(shuō)loguru
算是重新「造輪子」节吮,但是它也能和logging
庫(kù)很好地兼容抽高。到現(xiàn)在我們才談?wù)摰?code>add() 方法的第一個(gè)參數(shù) sink
。
這個(gè)參數(shù)的英文單詞動(dòng)詞有「下沉透绩、浸沒(méi)」等意翘骂,對(duì)于外國(guó)人來(lái)說(shuō)在理解上可能沒(méi)什么難的,可對(duì)我們國(guó)人來(lái)說(shuō)帚豪,這可之前logging
庫(kù)中的handler
概念還不好理解碳竟。好在前面我有說(shuō)過(guò),loguru
和logging
庫(kù)的使用上存在相似之處狸臣,因此在后續(xù)的使用中其實(shí)我們就可以將其理解為handler
莹桅,只不過(guò)它的范圍更廣一些,可以除了 handler
之外的字符串烛亦、可調(diào)用方法诈泼、協(xié)程對(duì)象等懂拾。
loguru 官方文檔對(duì)這一參數(shù)的解釋是:
object in charge of receiving formatted logging messages and propagating them to an appropriate endpoint.
翻譯過(guò)來(lái)就是「一個(gè)用于接收格式化日志信息并將其傳輸合適端點(diǎn)的對(duì)象」,進(jìn)一步形象理解就像是一個(gè)「分流器」厂汗。
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
寫(xiě)好的模塊中集成loguru
委粉,只要重新編寫(xiě)一個(gè)繼承自 logging.Handler
類(lèi)并實(shí)現(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)方法即可贾节,減少了遷移和重寫(xiě)的成本。
最后
本文介紹了關(guān)于loguru
的常用方法衷畦,從對(duì)比例子上來(lái)看栗涂,相比于復(fù)雜的 logging
配置來(lái)說(shuō),使用loguru
庫(kù)無(wú)疑還是很香的祈争,畢竟別人已經(jīng)為我們一些日常的通用性需求提供了封裝好的解決方案斤程,無(wú)論是在學(xué)習(xí)還是在使用的成本上,無(wú)疑還是比較小的菩混。
由于篇幅有限忿墅,loguru
的其他配置部分沒(méi)有進(jìn)一步展開(kāi),如果看完本文的你對(duì)這個(gè)庫(kù)感興趣并打算投入到實(shí)際的開(kāi)發(fā)和生產(chǎn)中使用沮峡,那么建議你還是閱讀一下其官方文檔疚脐,有必要的話可以瀏覽一下源碼。
不過(guò)loguru
的通用配置不一定滿足每個(gè)人的需要邢疙,對(duì)于那些動(dòng)手能力強(qiáng)或水平較高的朋友還能進(jìn)一步根據(jù)個(gè)人需求或業(yè)務(wù)需求進(jìn)行二次封裝棍弄,或許也能較為貼合實(shí)際情況。
以上就是小編今天為大家?guī)?lái)的內(nèi)容疟游,小編本身就是一名python開(kāi)發(fā)工程師呼畸,我自己花了三天時(shí)間整理了一套python學(xué)習(xí)教程,從最基礎(chǔ)的python腳本到web開(kāi)發(fā)颁虐,爬蟲(chóng)蛮原,數(shù)據(jù)分析,數(shù)據(jù)可視化另绩,機(jī)器學(xué)習(xí)儒陨,等,這些資料有想要的小伙伴點(diǎn)擊下方連接即可領(lǐng)取
https://docs.qq.com/doc/DTGpFa2lVeE9jUkRv