網(wǎng)站首頁 編程語言 正文
一、前言
Python logging 模塊定義了為應(yīng)用程序和庫實現(xiàn)靈活的事件日志記錄的函數(shù)和類。
程序開發(fā)過程中,很多程序都有記錄日志的需求,并且日志包含的信息有正常的程序訪問日志還可能有錯誤、警告等信息輸出,Python 的 logging 模塊提供了標準的日志接口,可以通過它存儲各種格式的日志,日志記錄提供了一組便利功能,用于簡單的日志記錄用法。
使用 Python Logging 模塊的主要好處是所有 Python 模塊都可以參與日志記錄Logging 模塊提供了大量具有靈活性的功能。
為什么要使用loguru?
簡單且方便的幫助我們輸出需要的日志信息:
使用 Python 來寫程序或者腳本的話,常常遇到的問題就是需要對日志進行刪除。一方面可以幫助我們在程序出問題的時候排除問題,二來可以幫助我們記錄需要關(guān)注的信息。
但是,使用自帶自帶的 logging 模塊的話,則需要我們進行不同的初始化等相關(guān)工作。對應(yīng)不熟悉該模塊的同學來說,還是有些費勁的,比如需要配置 Handler/Formatter 等。 隨著業(yè)務(wù)的復雜度提升, 對日志收集有著更高的要求, 例如: 日志分類, 文件存儲, 異步寫入, 自定義類型等等
loguru 是一個 Python 簡易且強大的第三方日志記錄庫,該庫旨在通過添加一系列有用的功能來解決標準記錄器的注意事項,從而減少 Python 日志記錄的痛苦。
二、優(yōu)雅的使用loguru
1. 安裝loguru
pip install loguru
2.功能特性介紹
有很多優(yōu)點,以下列舉了其中比較重要的幾點:
- 開箱即用,無需準備
- 無需初始化,導入函數(shù)即可使用
- 更容易的文件日志記錄與轉(zhuǎn)存/保留/壓縮方式
- 更優(yōu)雅的字符串格式化輸出
- 可以在線程或主線程中捕獲異常
- 可以設(shè)置不同級別的日志記錄樣式
- 支持異步,且線程和多進程安全
- 支持惰性計算
- 適用于腳本和庫
- 完全兼容標準日志記錄
- 更好的日期時間處理
3. 開箱即用,無需準備
from loguru import logger logger.debug("That's it, beautiful and simple logging!")
無需初始化,導入函數(shù)即可使用, 那么你肯定要問, 如何解決一下問題?
- 如何添加處理程序(handler)呢?
- 如何設(shè)置日志格式(logs formatting)呢?
- 如何過濾消息(filter messages)呢?
- 如何如何設(shè)置級別(log level)呢?
# add logger.add(sys.stderr, \ format="{time} {level} {message}",\ filter="my_module",\ level="INFO")
是不是很easy~
4. 更容易的文件日志記錄與轉(zhuǎn)存/保留/壓縮方式
# 日志文件記錄 logger.add("file_{time}.log") # 日志文件轉(zhuǎn)存 logger.add("file_{time}.log", rotation="500 MB") logger.add("file_{time}.log", rotation="12:00") logger.add("file_{time}.log", rotation="1 week") # 多次時間之后清理 logger.add("file_X.log", retention="10 days") # 使用zip文件格式保存 logger.add("file_Y.log", compression="zip")
5. 更優(yōu)雅的字符串格式化輸出
logger.info( "If you're using Python {}, prefer {feature} of course!", 3.10, feature="f-strings")
6. 在子線程或主線程中捕獲異常
@logger.catch def my_function(x, y, z): # An error? It's caught anyway! return 1 / (x + y + z) my_function(0, 0, 0)
7. 可以設(shè)置不同級別的日志記錄樣式
Loguru 會自動為不同的日志級別,添加不同的顏色進行區(qū)分, 也支持自定義顏色哦~
logger.add(sys.stdout, colorize=True, format="<green>{time}</green> <level>{message}</level>") logger.add('logs/z_{time}.log', level='DEBUG', format='{time:YYYY-MM-DD :mm:ss} - {level} - {file} - {line} - {message}', rotation="10 MB")
8.支持異步且線程和多進程安全
- 默認情況下,添加到 logger 中的日志信息都是線程安全的。但這并不是多進程安全的,我們可以通過添加 enqueue 參數(shù)來確保日志完整性。
- 如果我們想要在異步任務(wù)中使用日志記錄的話,也是可以使用同樣的參數(shù)來保證的。并且通過 complete() 來等待執(zhí)行完成。
# 異步寫入 logger.add("some_file.log", enqueue=True)
你沒有看錯, 只需要enqueue=True
即可異步執(zhí)行
9. 異常的完整性描述
用于記錄代碼中發(fā)生的異常的 bug 跟蹤,Loguru 通過允許顯示整個堆棧跟蹤(包括變量值)來幫助您識別問題
logger.add("out.log", backtrace=True, diagnose=True) def func(a, b): return a / b def nested(c): try: func(5, c) except ZeroDivisionError: logger.exception("What?!") nested(0)
10. 結(jié)構(gòu)化日志記錄
- 對日志進行序列化以便更容易地解析或傳遞數(shù)據(jù)結(jié)構(gòu),使用序列化參數(shù),在將每個日志消息發(fā)送到配置的接收器之前,將其轉(zhuǎn)換為 JSON 字符串。
- 同時,使用 bind() 方法,可以通過修改額外的 record 屬性來將日志記錄器消息置于上下文中。還可以通過組合 bind() 和 filter 對日志進行更細粒度的控制。
- 最后 patch() 方法允許將動態(tài)值附加到每個新消息的記錄 dict 上。
# 序列化為json格式 logger.add(custom_sink_function, serialize=True) # bind方法的用處 logger.add("file.log", format="{extra[ip]} {extra[user]} {message}") context_logger = logger.bind(ip="192.168.2.174", user="someone") context_logger.info("Contextualize your logger easily") context_logger.bind(user="someone_else").info("Inline binding of extra attribute") context_logger.info("Use kwargs to add context during formatting: {user}", user="anybody") # 粒度控制 logger.add("special.log", filter=lambda record: "special" in record["extra"]) logger.debug("This message is not logged to the file") logger.bind(special=True).info("This message, though, is logged to the file!") # patch()方法的用處 logger.add(sys.stderr, format="{extra[utc]} {message}") loggerlogger = logger.patch(lambda record: record["extra"].update(utc=datetime.utcnow()))
11. 惰性計算
有時希望在生產(chǎn)環(huán)境中記錄詳細信息而不會影響性能,可以使用 opt() 方法來實現(xiàn)這一點。
logger.opt(lazy=True).debug("If sink level <= DEBUG: {x}", x=lambda: expensive_function(2**64)) # By the way, "opt()" serves many usages logger.opt(exception=True).info("Error stacktrace added to the log message (tuple accepted too)") logger.opt(colors=True).info("Per message <blue>colors</blue>") logger.opt(record=True).info("Display values from the record (eg. {record[thread]})") logger.opt(raw=True).info("Bypass sink formatting\n") logger.opt(depth=1).info("Use parent stack context (useful within wrapped functions)") logger.opt(capture=False).info("Keyword arguments not added to {dest} dict", dest="extra")
12. 可定制的級別
new_level = logger.level("SNAKY", no=38, color="<yellow>", icon="??") logger.log("SNAKY", "Here we go!")
13. 適用于腳本和庫
# For scripts config = { "handlers": [ {"sink": sys.stdout, "format": "{time} - {message}"}, {"sink": "file.log", "serialize": True}, ], "extra": {"user": "someone"} } logger.configure(**config) # For libraries logger.disable("my_library") logger.info("No matter added sinks, this message is not displayed") logger.enable("my_library") logger.info("This message however is propagated to the sinks")
14. 完全兼容標準日志記錄
- 希望使用 Loguru 作為內(nèi)置的日志處理程序?
- 需要將 Loguru 消息到標準日志?
- 想要攔截標準的日志消息到 Loguru 中匯總?
handler = logging.handlers.SysLogHandler(address=('localhost', 514)) logger.add(handler) class PropagateHandler(logging.Handler): def emit(self, record): logging.getLogger(record.name).handle(record) logger.add(PropagateHandler(), format="{message}") class InterceptHandler(logging.Handler): def emit(self, record): # Get corresponding Loguru level if it exists try: level = logger.level(record.levelname).name except ValueError: level = record.levelno # Find caller from where originated the logged message frame, depth = logging.currentframe(), 2 while frame.f_code.co_filename == logging.__file__: frameframe = frame.f_back depth += 1 logger.opt(depthdepth=depth, exception=record.exc_info).log(level, record.getMessage()) logging.basicConfig(handlers=[InterceptHandler()], level=0)
15. 非常方便的解析器
從生成的日志中提取特定的信息通常很有用,這就是為什么 Loguru 提供了一個 parse() 方法來幫助處理日志和正則表達式。
pattern = r"(?P<time>.*) - (?P<level>[0-9]+) - (?P<message>.*)" # Regex with named groups caster_dict = dict(time=dateutil.parser.parse, level=int) # Transform matching groups for groups in logger.parse("file.log", pattern, cast=caster_dict): print("Parsed:", groups) # {"level": 30, "message": "Log example", "time": datetime(2018, 12, 09, 11, 23, 55)}
16. 通知機制 (郵件告警)
import notifiers params = { "username": "you@gmail.com", "password": "abc123", "to": "dest@gmail.com" } # Send a single notification notifier = notifiers.get_notifier("gmail") notifier.notify(message="The application is running!", **params) # Be alerted on each error message from notifiers.logging import NotificationHandler handler = NotificationHandler("gmail", defaults=params) logger.add(handler, level="ERROR")
17. Flask 框架集成
- 現(xiàn)在最關(guān)鍵的一個問題是如何兼容別的 logger,比如說 tornado 或者 django 有一些默認的 logger。
- 經(jīng)過研究,最好的解決方案是參考官方文檔的,完全整合 logging 的工作方式。比如下面將所有的 logging都用 loguru 的 logger 再發(fā)送一遍消息。
import logging import sys from pathlib import Path from flask import Flask from loguru import logger app = Flask(__name__) class InterceptHandler(logging.Handler): def emit(self, record): loggerlogger_opt = logger.opt(depth=6, exception=record.exc_info) logger_opt.log(record.levelname, record.getMessage()) def configure_logging(flask_app: Flask): """配置日志""" path = Path(flask_app.config['LOG_PATH']) if not path.exists(): path.mkdir(parents=True) log_name = Path(path, 'sips.log') logging.basicConfig(handlers=[InterceptHandler(level='INFO')], level='INFO') # 配置日志到標準輸出流 logger.configure(handlers=[{"sink": sys.stderr, "level": 'INFO'}]) # 配置日志到輸出到文件 logger.add(log_name, rotation="500 MB", encoding='utf-8', colorize=False, level='INFO')
18. 要點解析
介紹,主要函數(shù)的使用方法和細節(jié) - add()的創(chuàng)建和刪除
- add() 非常重要的參數(shù) sink 參數(shù)
- 具體的實現(xiàn)規(guī)范可以參見官方文檔
- 可以實現(xiàn)自定義 Handler 的配置,比如 FileHandler、StreamHandler 等等
- 可以自行定義輸出實現(xiàn)
- 代表文件路徑,會自動創(chuàng)建對應(yīng)路徑的日志文件并將日志輸出進去
- 例如 sys.stderr 或者 open(‘file.log’, ‘w’) 都可以
- 可以傳入一個 file 對象
- 可以直接傳入一個 str 字符串或者 pathlib.Path 對象
- 可以是一個方法
- 可以是一個 logging 模塊的 Handler
- 可以是一個自定義的類
def add(self, sink, *, level=_defaults.LOGURU_LEVEL, format=_defaults.LOGURU_FORMAT, filter=_defaults.LOGURU_FILTER, colorize=_defaults.LOGURU_COLORIZE, serialize=_defaults.LOGURU_SERIALIZE, backtrace=_defaults.LOGURU_BACKTRACE, diagnose=_defaults.LOGURU_DIAGNOSE, enqueue=_defaults.LOGURU_ENQUEUE, catch=_defaults.LOGURU_CATCH, **kwargs ):
另外添加 sink 之后我們也可以對其進行刪除,相當于重新刷新并寫入新的內(nèi)容。刪除的時候根據(jù)剛剛 add 方法返回的 id 進行刪除即可。可以發(fā)現(xiàn),在調(diào)用 remove 方法之后,確實將歷史 log 刪除了。但實際上這并不是刪除,只不過是將 sink 對象移除之后,在這之前的內(nèi)容不會再輸出到日志中,這樣我們就可以實現(xiàn)日志的刷新重新寫入操作
from loguru import logger trace = logger.add('runtime.log') logger.debug('this is a debug message') logger.remove(trace) logger.debug('this is another debug message')
三、總結(jié)
我們在開發(fā)流程中, 通過日志快速定位問題, 高效率解決問題, 我認為 loguru 能幫你解決不少麻煩, 趕快試試吧~
當然, 使用各種也有不少麻煩, 例如:
1. 常見錯誤1:
--- Logging error in Loguru Handler #3 ---
Record was: None
Traceback (most recent call last):
? File "/usr/local/lib/python3.9/site-packages/loguru/_handler.py", line 272, in _queued_writer
? ? message = queue.get()
? File "/usr/local/lib/python3.9/multiprocessing/queues.py", line 366, in get
? ? res = self._reader.recv_bytes()
? File "/usr/local/lib/python3.9/multiprocessing/connection.py", line 221, in recv_bytes
? ? buf = self._recv_bytes(maxlength)
? File "/usr/local/lib/python3.9/multiprocessing/connection.py", line 419, in _recv_bytes
? ? buf = self._recv(4)
? File "/usr/local/lib/python3.9/multiprocessing/connection.py", line 384, in _recv
? ? chunk = read(handle, remaining)
OSError: [Errno 9] Bad file descriptor
--- End of logging error ---
解決辦法:
嘗試將logs文件夾忽略git提交, 避免和服務(wù)器文件沖突即可;
當然也不止這個原因引起這個問題, 也可能是三方庫(ciscoconfparse)沖突所致.解決辦法: https://github.com/Delgan/loguru/issues/534
2.常見錯誤2:
File "/home/ronaldinho/xxx/xxx/venv/lib/python3.9/site-packages/loguru/_logger.py", line 939, in add
? ? handler = Handler(
? File "/home/ronaldinho/xxx/xxx/venv/lib/python3.9/site-packages/loguru/_handler.py", line 86, in __init__
? ? self._queue = multiprocessing.SimpleQueue()
? File "/home/ronaldinho/.pyenv/versions/3.9.4/lib/python3.9/multiprocessing/context.py", line 113, in SimpleQueue
? ? return SimpleQueue(ctx=self.get_context())
? File "/home/ronaldinho/.pyenv/versions/3.9.4/lib/python3.9/multiprocessing/queues.py", line 342, in __init__
? ? self._rlock = ctx.Lock()
? File "/home/ronaldinho/.pyenv/versions/3.9.4/lib/python3.9/multiprocessing/context.py", line 68, in Lock
? ? return Lock(ctx=self.get_context())
? File "/home/ronaldinho/.pyenv/versions/3.9.4/lib/python3.9/multiprocessing/synchronize.py", line 162, in __init__
? File "/home/ronaldinho/.pyenv/versions/3.9.4/lib/python3.9/multiprocessing/synchronize.py", line 57, in __init__
OSError: [Errno 24] Too many open files
你可以 remove()添加的處理程序,它應(yīng)該釋放文件句柄。?
總之, 諸如此類的問題都能找到解決方法, 總體來說這個庫是非常值得應(yīng)用的, 白看不如一試, 快去coding吧~
原文鏈接:https://blog.csdn.net/qq_31810357/article/details/124779625
相關(guān)推薦
- 2022-04-01 用C語言實現(xiàn)推箱子游戲?qū)嵗齙C 語言
- 2023-01-31 Android數(shù)據(jù)存儲幾種方式講解_Android
- 2022-05-05 深入講解下Rust模塊使用方式_相關(guān)技巧
- 2022-09-21 flutter實現(xiàn)底部不規(guī)則導航欄_Android
- 2022-06-25 Python中最強大的錯誤重試庫(tenacity庫)_python
- 2023-07-18 讀取Springboot的jar包中的文件資源方式
- 2022-07-10 如何替換重構(gòu)依賴里面的Service
- 2022-06-14 C語言數(shù)據(jù)類型與sizeof關(guān)鍵字_C 語言
- 最近更新
-
- window11 系統(tǒng)安裝 yarn
- 超詳細win安裝深度學習環(huán)境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎(chǔ)操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區(qū)別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權(quán)
- redisson分布式鎖中waittime的設(shè)
- maven:解決release錯誤:Artif
- restTemplate使用總結(jié)
- Spring Security之安全異常處理
- MybatisPlus優(yōu)雅實現(xiàn)加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務(wù)發(fā)現(xiàn)-Nac
- Spring Security之基于HttpR
- Redis 底層數(shù)據(jù)結(jié)構(gòu)-簡單動態(tài)字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應(yīng)用詳解
- 聊聊消息隊列,發(fā)送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支