網(wǎng)站后端技術(shù)有哪些運(yùn)營(yíng)商大數(shù)據(jù)精準(zhǔn)營(yíng)銷獲客
Python 日志記錄:6大日志記錄庫(kù)的比較
文章目錄
- Python 日志記錄:6大日志記錄庫(kù)的比較
- 前言
- 一些日志框架建議
- 1. logging - 內(nèi)置的標(biāo)準(zhǔn)日志模塊
- 默認(rèn)日志記錄器
- 自定義日志記錄器
- 生成結(jié)構(gòu)化日志
- 2. Loguru - 最流行的Python第三方日志框架
- 默認(rèn)日志記錄器
- 自定義日志記錄器
- 3. Structlog
- 4. Eliot
- 5. Logbook
- 6. Picologging
- 最后的想法
前言
日志記錄框架是一種工具,可幫助您標(biāo)準(zhǔn)化應(yīng)用程序中的日志記錄過(guò)程。雖然某些編程語(yǔ)言提供內(nèi)置日志記錄模塊作為其標(biāo)準(zhǔn)庫(kù)的一部分,但大多數(shù)日志記錄框架都是第三方庫(kù),例如logging (Python)、Log4j (Java)、 Zerolog (Go) 或 Winston (Node.js)。有時(shí),組織會(huì)選擇開(kāi)發(fā)自定義日志記錄解決方案,但這通常僅限于具有高度專業(yè)化需求的大型公司。
雖然 Python 在其標(biāo)準(zhǔn)庫(kù)中提供了強(qiáng)大且功能豐富的日志記錄解決方案,但第三方日志記錄生態(tài)系統(tǒng)也提供了一系列引人注目的替代方案。根據(jù)您的需求,這些外部庫(kù)可能更適合您的日志記錄需求。
因此,本文將介紹 Python 用于跟蹤應(yīng)用程序和庫(kù)行為的六大日志解決方案。我們將首先討論標(biāo)準(zhǔn)logging
模塊,然后研究 Python 社區(qū)創(chuàng)建的其他五個(gè)logging frameworks。
一些日志框架建議
-
Pino (Node.js)
-
Zerolog, Zap, or Slog (Go)
-
Monolog (PHP)
-
SLF4J with Log4J2 or Logback (Java)
-
Loguru (Python)
-
Semantic Logger (Ruby)
1. logging - 內(nèi)置的標(biāo)準(zhǔn)日志模塊
默認(rèn)日志記錄器
與大多數(shù)編程語(yǔ)言不同,Python 在其標(biāo)準(zhǔn)庫(kù)中包含了一個(gè)功能齊全的日志框架。該日志記錄解決方案有效地滿足了庫(kù)和應(yīng)用程序開(kāi)發(fā)人員的需求,并包含了以下嚴(yán)重性級(jí)別:DEBUG
、INFO
、WARNING
、ERROR
和 CRITICAL
。有了默認(rèn)日志記錄器,無(wú)需任何前期設(shè)置,您就可以立即開(kāi)始記錄日志。
import logginglogging.debug("A debug message")
logging.info("An info message")
logging.warning("A warning message")
logging.error("An error message")
logging.critical("A critical message")
此default(或root)記錄器在該WARNING
級(jí)別運(yùn)行,這意味著只有嚴(yán)重性等于或超過(guò)的記錄調(diào)用WARNING
才會(huì)產(chǎn)生輸出:
WARNING:root:A warning message
ERROR:root:An error message
CRITICAL:root:A critical message
自定義日志記錄器
這種配置可確保只顯示潛在的重要信息,減少日志輸出中的噪音。不過(guò),您也可以根據(jù)需要自定義日志級(jí)別并微調(diào)日志記錄行為。使用logging
模塊的推薦方法是通過(guò) getLogger()
方法創(chuàng)建自定義日志記錄器:
import logginglogger = logging.getLogger(__name__)
一旦有了自定義記錄器,您就可以通過(guò) logging
模塊 提供的Handler(處理程序)、 Formatter(格式化器)和Filter(過(guò)濾器) 類來(lái)自定義其輸出。
-
Handlers
決定輸出目的地,并可根據(jù)日志級(jí)別進(jìn)行定制。一個(gè)日志記錄器還可添加多個(gè)Handlers
,以便同時(shí)向不同目的地發(fā)送日志信息。 -
Formatters
決定了日志記錄器產(chǎn)生的記錄的格式。然而,目前還沒(méi)有JSON、Logfmt等預(yù)定義格式。您必須結(jié)合可用的日志記錄屬性來(lái)構(gòu)建自己的格式。
root日志記錄器的默認(rèn)格式為%(levelname)s:%(name)s:%(message)s
。
然而,自定義日志記錄器默認(rèn)為只有%(message)s
。 -
Filters
由handler
和logger objects
使用,用于過(guò)濾日志記錄。與日志級(jí)別相比,Filters
能更好地控制哪些日志記錄應(yīng)被處理或忽略。在日志被發(fā)送到最終目的地之前,它們還能以某種方式增強(qiáng)或修改記錄。例如,您可以創(chuàng)建一個(gè)自定義過(guò)濾器,以刪除日志中的敏感數(shù)據(jù)。
下面是一個(gè)使用自定義日志記錄器將日志記錄到控制臺(tái)和文件的示例:
import sys
import logginglogger = logging.getLogger("example")
logger.setLevel(logging.DEBUG)# Create handlers for logging to the standard output and a file
stdoutHandler = logging.StreamHandler(stream=sys.stdout)
errHandler = logging.FileHandler("error.log")# Set the log levels on the handlers
stdoutHandler.setLevel(logging.DEBUG)
errHandler.setLevel(logging.ERROR)# Create a log format using Log Record attributes
fmt = logging.Formatter("%(name)s: %(asctime)s | %(levelname)s | %(filename)s:%(lineno)s | %(process)d >>> %(message)s"
)# Set the log format on each handler
stdoutHandler.setFormatter(fmt)
errHandler.setFormatter(fmt)# Add each handler to the Logger object
logger.addHandler(stdoutHandler)
logger.addHandler(errHandler)logger.info("Server started listening on port 8080")
logger.warning("Disk space on drive '/var/log' is running low. Consider freeing up space"
)try:raise Exception("Failed to connect to database: 'my_db'")
except Exception as e:# exc_info=True ensures that a Traceback is includedlogger.error(e, exc_info=True)
執(zhí)行上述程序時(shí),控制臺(tái)會(huì)如期打印出以下日志信息:
example: 2023-07-23 14:42:18,599 | INFO | main.py:30 | 187901 >>> Server started listening on port 8080example: 2023-07-23 14:14:47,578 | WARNING | main.py:28 | 143936 >>> Disk space on drive '/var/log' is running low. Consider freeing up spaceexample: 2023-07-23 14:14:47,578 | ERROR | main.py:34 | 143936 >>> Failed to connect to database: 'my_db'Traceback (most recent call last):File "/home/ayo/dev/betterstack/demo/python-logging/main.py", line 32, in <module>raise Exception("Failed to connect to database: 'my_db'")
Exception: Failed to connect to database: 'my_db'
同時(shí)還創(chuàng)建了 error.log 文件,該文件應(yīng)僅包含 ERROR 日志,因?yàn)?errHandler 的最小級(jí)別已設(shè)置為 ERROR:
example: 2023-07-23 14:14:47,578 | ERROR | main.py:34 | 143936 >>> Failed to connect to database: 'my_db'
Traceback (most recent call last):File "/home/ayo/dev/betterstack/demo/python-logging/main.py", line 32, in <module>raise Exception("Failed to connect to database: 'my_db'")
Exception: Failed to connect to database: 'my_db'
生成結(jié)構(gòu)化日志
在撰寫本文時(shí),除非執(zhí)行一些附加代碼,否則logging
模塊無(wú)法生成結(jié)構(gòu)化日志。值得慶幸的是,有一種更簡(jiǎn)單、更好的方法可以獲得結(jié)構(gòu)化輸出:python-json-logger 庫(kù)。
$ pip install python-json-logger
安裝后,您可以按以下方式使用它:
import sys
import logging
from pythonjsonlogger import jsonlogger. . .# The desired Log Record attributes must be included here, and they can be
# renamed if necessary
fmt = jsonlogger.JsonFormatter("%(name)s %(asctime)s %(levelname)s %(filename)s %(lineno)s %(process)d %(message)s",rename_fields={"levelname": "severity", "asctime": "timestamp"},
)# Set the log format on each handler
stdoutHandler.setFormatter(fmt)
errHandler.setFormatter(fmt). . .
如果用上面突出顯示的幾行修改前面的示例,執(zhí)行時(shí)將觀察到以下輸出:
{"name": "example", "filename": "main.py", "lineno": 31, "process": 179775, "message": "Server started listening on port 8080", "severity": "INFO", "timestamp": "2023-07-23 14:39:03,265"}
{"name": "example", "filename": "main.py", "lineno": 32, "process": 179775, "message": "Disk space on drive '/var/log' is running low. Consider freeing up space", "severity": "WARNING", "timestamp": "2023-07-23 14:39:03,265"}
{"name": "example", "filename": "main.py", "lineno": 38, "process": 179775, "message": "Failed to connect to database: 'my_db'", "exc_info": "Traceback (most recent call last):\n File \"/home/ayo/dev/betterstack/demo/python-logging/main.py\", line 36, in <module>\n raise Exception(\"Failed to connect to database: 'my_db'\")\nException: Failed to connect to database: 'my_db'", "severity": "ERROR", "timestamp": "2023-07-23 14:39:03,265"}
還可以通過(guò) level 方法上的extra
屬性在log point添加上下文數(shù)據(jù),如下所示:
logger.info("Server started listening on port 8080",extra={"python_version": 3.10, "os": "linux", "host": "fedora 38"},
)
{"name": "example", "filename": "main.py", "lineno": 31, "process": 195301, "message": "Server started listening on port 8080", "python_version": 3.1, "os": "linux", "host": "fedora 38", "severity": "INFO", "timestamp": "2023-07-23 14:45:42,472"}
正如您所看到的,built-in的 logging
module 能夠滿足各種日志記錄需求,并且具有可擴(kuò)展性。不過(guò),它的初始配置和自定義可能比較麻煩,因?yàn)樵陂_(kāi)始有效記錄之前,你必須創(chuàng)建和配置loggers
、handlers
和formatters
。
請(qǐng)參閱我們的 Python 日志指南和官方文檔,進(jìn)一步了解日志模塊的功能和最佳實(shí)踐。
2. Loguru - 最流行的Python第三方日志框架
Loguru 是最流行的 Python 第三方日志框架,在撰寫本文時(shí)已在 GitHub 上獲得超過(guò) 15k顆星。它旨在通過(guò)預(yù)配置日志記錄器來(lái)簡(jiǎn)化日志記錄過(guò)程,并通過(guò)其 add()
方法使自定義日志記錄器變得非常容易。
默認(rèn)日志記錄器
使用 Loguru 啟動(dòng)日志記錄非常簡(jiǎn)單,只需安裝軟件包并導(dǎo)入,然后調(diào)用其級(jí)別方法即可,如下所示:
$ pip install loguru
from loguru import loggerlogger.trace("Executing program")
logger.debug("Processing data...")
logger.info("Server started successfully.")
logger.success("Data processing completed successfully.")
logger.warning("Invalid configuration detected.")
logger.error("Failed to connect to the database.")
logger.critical("Unexpected system error occurred. Shutting down.")
默認(rèn)配置將半結(jié)構(gòu)化和彩色化的輸出記錄到標(biāo)準(zhǔn)錯(cuò)誤。它還默認(rèn)將 DEBUG
作為最低級(jí)別,這也解釋了為什么 TRACE
輸出不會(huì)被記錄。
自定義日志記錄器
通過(guò) add()
函數(shù),可以輕松定制 Loguru 的內(nèi)部工作機(jī)制,該函數(shù)可處理從日志格式化到日志目的地設(shè)置等一切操作。例如,您可以將日志記錄到標(biāo)準(zhǔn)輸出,將默認(rèn)級(jí)別更改為 INFO
,并使用下面的配置將日志格式化為 JSON:
from loguru import logger
import syslogger.remove(0) # remove the default handler configuration
logger.add(sys.stdout, level="INFO", serialize=True). . .
{"text": "2023-07-17 15:26:21.597 | INFO | __main__:<module>:9 - Server started successfully.\n", "record": {"elapsed": {"repr": "0:00:00.006401", "seconds": 0.006401}, "exception": null, "extra": {}, "file": {"name": "main.py", "path": "/home/ayo/dev/betterstack/demo/python-logging/main.py"}, "function": "<module>", "level": {"icon": "??", "name": "INFO", "no": 20}, "line": 9, "message": "Server started successfully.", "module": "main", "name": "__main__", "process": {"id": 3852028, "name": "MainProcess"}, "thread": {"id": 140653618894656, "name": "MainThread"}, "time": {"repr": "2023-07-17 15:26:21.597156+02:00", "timestamp": 1689600381.597156}}}
Loguru 生成的默認(rèn) JSON 輸出可能相當(dāng)冗長(zhǎng),但使用類似這樣的自定義函數(shù)可以輕松地將日志信息序列化:
from loguru import logger
import sys
import jsondef serialize(record):subset = {"timestamp": record["time"].timestamp(),"message": record["message"],"level": record["level"].name,"file": record["file"].name,"context": record["extra"],}return json.dumps(subset)def patching(record):record["extra"]["serialized"] = serialize(record)logger.remove(0)logger = logger.patch(patching)
logger.add(sys.stderr, format="{extra[serialized]}")logger.bind(user_id="USR-1243", doc_id="DOC-2348").debug("Processing document")
{"timestamp": 1689601339.628792, "message": "Processing document", "level": "DEBUG", "file": "main.py", "context": {"user_id": "USR-1243", "doc_id": "DOC-2348"}}
Loguru 也完全支持上下文日志記錄。您已經(jīng)看到上面的 bind()
方法,它允許在 log point 添加上下文數(shù)據(jù)。
您還可以使用 bind()
方法來(lái)創(chuàng)建子記錄器來(lái)記錄共享相同上下文的記錄:
child = logger.bind(user_id="USR-1243", doc_id="DOC-2348")
child.debug("Processing document")
child.warning("Invalid configuration detected. Falling back to defaults")
child.success("Document processed successfully")
請(qǐng)注意,user_id 和 doc_id 字段出現(xiàn)在所有三條記錄中:
{"timestamp": 1689601518.884659, "message": "Processing document", "level": "DEBUG", "file": "main.py", "context": {"user_id": "USR-1243", "doc_id": "DOC-2348"}}{"timestamp": 1689601518.884706, "message": "Invalid configuration detected. Falling back to defaults", "level": "WARNING", "file": "main.py", "context": {"user_id": "USR-1243", "doc_id": "DOC-2348"}}{"timestamp": 1689601518.884729, "message": "Document processed successfully", "level": "SUCCESS", "file": "main.py", "context": {"user_id": "USR-1243", "doc_id": "DOC-2348"}}
另一方面,它的 contextualize()
方法可以輕松地為特定范圍或上下文中的所有日志記錄添加上下文字段。例如,下面的代碼段演示了將唯一的請(qǐng)求 ID 屬性添加到因該請(qǐng)求而創(chuàng)建的所有日志中:
from loguru import logger
import uuiddef logging_middleware(get_response):def middleware(request):request_id = str(uuid.uuid4())with logger.contextualize(request_id=request_id):response = get_response(request)response["X-Request-ID"] = request_idreturn responsereturn middleware
Loguru 還支持優(yōu)秀日志框架所應(yīng)具備的所有功能,如通過(guò)自動(dòng)旋轉(zhuǎn)和壓縮將日志記錄到文件、自定義日志級(jí)別、異常處理、同時(shí)記錄到多個(gè)目的地等。它還為來(lái)自標(biāo)準(zhǔn)logging
模塊的用戶提供了遷移指南。
請(qǐng)參閱Loguru 官方文檔和我們專用 Loguru 指南,了解有關(guān)使用 Loguru 為 Python 應(yīng)用程序創(chuàng)建production-ready日志設(shè)置的更多信息。
3. Structlog
Structlog 是一個(gè)日志庫(kù),專門用于生成 JSON 或 Logfmt 格式的結(jié)構(gòu)化輸出。它支持為開(kāi)發(fā)環(huán)境提供彩色、美觀的控制臺(tái)輸出,也允許完全自定義日志格式,以滿足不同需求。你可以使用下面的命令安裝 Structlog 軟件包:
$ pip install structlog
Structlog 最簡(jiǎn)單的用法是調(diào)用 get_logger()
方法,然后在生成的logger上使用任何level方法:
import structloglogger = structlog.get_logger()logger.debug("Database query executed in 0.025 seconds")
logger.info("Processing file 'data.csv' completed. 1000 records were imported",file="data.csv",elapsed_ms=300,num_records=1000,
)
logger.warning("Unable to load configuration file 'config.ini'. Using default settings instead",file="config.ini",
)try:1 / 0
except ZeroDivisionError as e:logger.exception("Division by zero error occurred during calculation. Check the input values",exc_info=e,)
logger.critical("Application crashed due to an unhandled exception")
Structlog 日志記錄器的默認(rèn)配置對(duì)開(kāi)發(fā)環(huán)境非常友好。輸出是彩色的,任何包含的上下文數(shù)據(jù)都以 key=value
對(duì)的形式出現(xiàn)。此外,tracebacks的格式和organized都很整齊,因此更容易發(fā)現(xiàn)問(wèn)題的原因。
Structlog 的獨(dú)特之處在于,它不會(huì)按levels去過(guò)濾記錄。這就是為什么上面所有的levels都被寫入控制臺(tái)的原因。不過(guò),通過(guò) configure()
方法配置默認(rèn)級(jí)別也很簡(jiǎn)單,如下所示:
import structlog
import loggingstructlog.configure(wrapper_class=structlog.make_filtering_bound_logger(logging.INFO))
Structlog 與標(biāo)準(zhǔn)logging
模塊中的日志級(jí)別兼容,因此可以使用上述 logging.INFO
常量。你也可以直接使用與級(jí)別相關(guān)的數(shù)字:
structlog.configure(wrapper_class=structlog.make_filtering_bound_logger(20))
get_logger()
函數(shù)返回的日志記錄器稱為綁定日志記錄器,因?yàn)槟梢詫⑸舷挛闹蹬c之綁定。一旦綁定了key/value pairs,它們將包含在日志記錄器生成的每個(gè)后續(xù)日志條目中。
import structlog
import platformlogger = structlog.get_logger()logger = logger.bind(python_version=platform.python_version(), os="linux"). . .
2023-07-23 17:20:10 [debug] Database query executed in 0.025 seconds os=linux python_version=3.11.42023-07-23 17:20:10 [info] Processing file 'data.csv' completed. 1000 records were imported elapsed_ms=300 file=data.csv num_records=1000 os=linux python_version=3.11.4
綁定日志記錄器還包括一系列處理器函數(shù),可在日志記錄通過(guò)日志記錄管道時(shí)對(duì)日志記錄進(jìn)行轉(zhuǎn)換和豐富。例如,您可以使用以下配置以 JSON 格式記錄日志:
import structlog
import platformstructlog.configure(processors=[structlog.processors.TimeStamper(fmt="iso"),structlog.processors.add_log_level,structlog.processors.JSONRenderer(),]
). . .
每個(gè)處理器都按照聲明順序執(zhí)行,因此首先調(diào)用 TimeStamper()
為每個(gè)條目添加 ISO-8601 格式的時(shí)間戳,然后通過(guò) add_log_level
添加嚴(yán)重級(jí)別,最后調(diào)用 JSONRenderer()
將整個(gè)記錄序列化為 JSON 格式。對(duì)程序進(jìn)行高亮顯示的修改后,您將看到以下輸出:
{"python_version": "3.11.4", "os": "linux", "event": "Database query executed in 0.025 seconds", "timestamp": "2023-07-23T15:32:21.590688Z", "level": "debug"}{"python_version": "3.11.4", "os": "linux", "file": "data.csv", "elapsed_ms": 300, "num_records": 1000, "event": "Processing file 'data.csv' completed. 1000 records were imported", "timestamp": "2023-07-23T15:32:21.590720Z", "level": "info"}
Structlog 能做的另一件很酷的事情是自動(dòng)格式化tracebacks,使其也以 JSON 格式序列化。你只需要像這樣使用 dict_tracebacks
處理器:
structlog.configure(processors=[structlog.processors.TimeStamper(fmt="iso"),structlog.processors.add_log_level,structlog.processors.dict_tracebacks,structlog.processors.JSONRenderer(),]
)
每當(dāng)記錄異常情況時(shí),你會(huì)發(fā)現(xiàn)記錄中的異常情況信息格式非常豐富,便于在日志管理服務(wù)中進(jìn)行分析。
{"python_version": "3.11.4", "os": "linux", "event": "Division by zero error occurred during calculation. Check the input values", "timestamp": "2023-07-23T16:07:50.127241Z", "level": "error", "exception": [{"exc_type": "ZeroDivisionError", "exc_value": "division by zero", "syntax_error": null, "is_cause": false, "frames": [{"filename": "/home/ayo/dev/betterstack/demo/python-logging/main.py", "lineno": 32, "name": "<module>", "line": "", "locals": {"__name__": "__main__", "__doc__": "None", "__package__": "None", "__loader__": "<_frozen_importlib_external.SourceFileLoader object at 0x7fdb22df2ed0>", "__spec__": "None", "__annotations__": "{}", "__builtins__": "<module 'builtins' (built-in)>", "__file__": "/home/ayo/dev/betterstack/demo/python-logging/main.py", "__cached__": "None", "structlog": "\"<module 'structlog' from '/home/ayo/.local/lib/python3.11/site-packages/structlo\"+15", "platform": "<module 'platform' from '/usr/lib64/python3.11/platform.py'>", "logging": "<module 'logging' from '/usr/lib64/python3.11/logging/__init__.py'>", "logger": "\"<BoundLoggerFilteringAtDebug(context={'python_version': '3.11.4', 'os': 'linux'}\"+249", "e": "ZeroDivisionError('division by zero')"}}]}]}
這里只介紹了 Structlog 所提供的常用功能,因此請(qǐng)務(wù)必查看其文檔以了解更多信息。
4. Eliot
Eliot 是一種獨(dú)特的 Python 日志解決方案,它不僅能記錄程序中發(fā)生的事件,還能輸出導(dǎo)致事件發(fā)生的行為因果鏈。使用 pip 安裝 Eliot 的方法如下:
$ pip install eliot
Eliot 的一個(gè)關(guān)鍵概念是 “動(dòng)作”(action),它代表任何可以開(kāi)始并成功完成或因異常而失敗的任務(wù)。當(dāng)你啟動(dòng)一個(gè)動(dòng)作時(shí),會(huì)產(chǎn)生兩條日志記錄:一條用來(lái)表示action的開(kāi)始,另一條用來(lái)表示action的成功或失敗。演示此模型的最佳方式是舉例說(shuō)明:
import sys
from eliot import start_action, to_fileto_file(sys.stdout)def calculate(x, y):with start_action(action_type="multiply"):return x * ycalculate(10, 5)
這里使用start_action
函數(shù)來(lái)表示一個(gè)新action的開(kāi)始。一旦執(zhí)行calculate()
函數(shù),兩個(gè)日志就會(huì)被發(fā)送到to_file()
配置的目的地:
{"action_status": "started", "timestamp": 1690213156.7701144, "task_uuid": "a9a47808-15a9-439b-8335-b88d50013f75", "action_type": "multiply", "task_level": [1]}{"action_status": "succeeded", "timestamp": 1690213156.7701554, "task_uuid": "a9a47808-15a9-439b-8335-b88d50013f75", "action_type": "multiply", "task_level": [2]}
Eliot 默認(rèn)生成結(jié)構(gòu)化的 JSON 輸出,其中包括以下記錄:
task_uuid
: 生成消息的唯一任務(wù)標(biāo)識(shí)符。action_status
: 表示action的狀態(tài)。timestamp
: 信息的 UNIX 時(shí)間戳。task_level
: 信息在actions樹(shù)中的位置。action_type
: 提供的action_type
參數(shù)。
您可以向action的開(kāi)始消息和成功消息添加其他字段,如下所示:
def calculate(x, y):# additional fields here are added to the start message of the action alonewith start_action(action_type="multiply", x=x, y=y) as action:result = x * y# fields added here show up only in the success message of the actionaction.add_success_fields(result=result)return result
{"x": 10, "y": 5, "action_status": "started", "timestamp": 1690213820.4083755, "task_uuid": "09df3632-96d2-4dd8-b782-1926cd87ccc9", "action_type": "multiply", "task_level": [1]}{"result": 50, "action_status": "succeeded", "timestamp": 1690213820.4084144, "task_uuid": "09df3632-96d2-4dd8-b782-1926cd87ccc9", "action_type": "multiply", "task_level": [2]}
另一種記錄函數(shù)的輸入和結(jié)果的方法是通過(guò)log_call
裝飾器:
from eliot import log_call, to_fileto_file(sys.stdout)@log_call
def calculate(x, y):return x * ycalculate(10, 5)
在這種情況下,action_type
將是模塊和函數(shù)名稱的連接,但其余字段將與之前相同:
{"x": 10, "y": 5, "action_status": "started", "timestamp": 1690214038.799868, "task_uuid": "2c78b304-12a1-474a-8b95-e80deadb8dde", "action_type": "__main__.calculate", "task_level": [1]}{"result": 50, "action_status": "succeeded", "timestamp": 1690214038.7999015, "task_uuid": "2c78b304-12a1-474a-8b95-e80deadb8dde", "action_type": "__main__.calculate", "task_level": [2]}
您可以通過(guò)更改 action_type
字段并排除某些參數(shù)或結(jié)果來(lái)自定義 log_call
裝飾器的行為:
@log_call(action_type="CALC", include_args=["x"], include_result=False)
如果在某個(gè)action的上下文中檢測(cè)到uncaught exception,該操作將被標(biāo)記為失敗,并將記錄一條異常消息,而不是成功消息:
import sys
from eliot import log_call, to_fileto_file(sys.stdout)@log_call
def calculate(x, y):return x / ytry:calculate(1, 0)
except ZeroDivisionError as e:print("division by zero detected")
您現(xiàn)在看到的不是成功信息,而是一條exception
信息,并附有reason
:
{"x": 1, "y": 0, "action_status": "started", "timestamp": 1690215830.1103916, "task_uuid": "f267b0f5-8c07-4828-a973-0a8a273f272d", "action_type": "__main__.calculate", "task_level": [1]}{"exception": "builtins.ZeroDivisionError", "reason": "division by zero", "action_status": "failed", "timestamp": 1690215830.1104264, "task_uuid": "f267b0f5-8c07-4828-a973-0a8a273f272d", "action_type": "__main__.calculate", "task_level": [2]}
當(dāng)您需要在action的上下文中記錄獨(dú)立的消息時(shí),可以使用log
方法,如下所示:
def calculate(x, y):with start_action(action_type="multiply") as ctx:ctx.log(message_type="mymsg", msg="a standalone message")return x * y
{"msg": "a standalone message", "timestamp": 1690217318.2063951, "task_uuid": "500b06e6-c0ba-42b4-9d6c-466ea3f1634d", "task_level": [2], "message_type": "mymsg"}
Eliot沒(méi)有 log levels 的概念,因此只能在需要時(shí)手動(dòng)添加level字段:
def calculate(x, y):with start_action(action_type="multiply", level="INFO") as ctx:ctx.log(message_type="mymsg", msg="a standalone message", level="INFO")return x * y
Eliot 的另一項(xiàng)出色功能是通過(guò) eliot-tree
命令行工具實(shí)現(xiàn)日志可視化。
$ pip install eliot-tree
安裝 eliot -tree
后,您可以將 Eliot 生成的 JSON 日志通過(guò)管道傳輸?shù)矫?#xff0c;如下所示:
$ python main.py | eliot-tree
如果你將日志記錄到文件,可以將這個(gè)文件作為參數(shù)傳遞給工具:
$ eliot-tree <file>
Eliot的內(nèi)容遠(yuǎn)不止于此,因此請(qǐng)務(wù)必查看其文檔以了解更多信息。
5. Logbook
Logbook 自稱是 Python 標(biāo)準(zhǔn)庫(kù)logging
模塊的酷炫替代品,其目的是讓日志記錄變得有趣。你可以使用以下命令將其安裝到你的項(xiàng)目中:
$ pip install logbook
開(kāi)始使用 Logbook 也非常簡(jiǎn)單:
import sys
import logbooklogger = logbook.Logger(__name__)handler = logbook.StreamHandler(sys.stdout, level="INFO")
handler.push_application()logger.info("Successfully connected to the database 'my_db' on host 'ubuntu'")logger.warning("Detected suspicious activity from IP address: 111.222.333.444")
[2023-07-24 21:41:50.932575] INFO: __main__: Successfully connected to the database 'my_db' on host 'ubuntu'[2023-07-24 21:41:50.932623] WARNING: __main__: Detected suspicious activity from IP address: 111.222.333.444
如上圖所示,logbook.Logger
方法用于創(chuàng)建一個(gè)新的 logger
channel。該 logger provides 提供了對(duì) info()
和 warning()
等級(jí)別方法的訪問(wèn),用于寫入日志信息。支持logging
模塊中的所有日志levels,并增加了介于 INFO
和 WARNING
之間的 NOTICE
級(jí)別。
Logbook 還使用Handler
概念來(lái)確定日志的目的地和格式。StreamHandler
類可將日志發(fā)送到任何輸出流(本例中為標(biāo)準(zhǔn)output),其他處理程序可將日志發(fā)送到文件、Syslog、Redis、Slack 等。
不過(guò),與標(biāo)準(zhǔn)logging
模塊不同的是,我們不鼓勵(lì)你直接在日志記錄器上注冊(cè)handlers。相反,你應(yīng)該分別通過(guò) push_application()
、push_thread()
和 push_greenlet()
方法將處理程序綁定到process、thread或 greenlet stack中。相應(yīng)的 pop_application()
、pop_thread()
和 pop_greenlet()
方法用于取消處理程序的注冊(cè):
handler = MyHandler()
handler.push_application()
# everything logged here here goes to that handler
handler.pop_application()
您還可以在 with-block 的持續(xù)時(shí)間內(nèi)綁定一個(gè)handler。這樣可以確保在塊內(nèi)創(chuàng)建的日志只發(fā)送給指定的handler:
with handler.applicationbound():logger.info(...)with handler.threadbound():logger.info(...)with handler.greenletbound():logger.info(...)
日志格式化也是通過(guò)handlers完成的。為此,每個(gè)處理程序都有一個(gè) format_string
屬性,它接受 LogRecord 類的屬性:
import sys
import logbooklogger = logbook.Logger(__name__)handler = logbook.StreamHandler(sys.stdout, level="INFO")
handler.format_string = "{record.channel} | {record.level_name} | {record.message}"
handler.push_application()logger.info("Successfully connected to the database 'my_db' on host 'ubuntu'")logger.warning("Detected suspicious activity from IP address: 111.222.333.444")
__main__ | INFO | Successfully connected to the database 'my_db' on host 'ubuntu'
__main__ | WARNING | Detected suspicious activity from IP address: 111.222.333.444
遺憾的是,Logbook 的任何內(nèi)置處理程序都不支持結(jié)構(gòu)化日志記錄。您必須通過(guò)自定義處理程序自行實(shí)現(xiàn)。有關(guān)詳細(xì)信息,請(qǐng)參閱 Logbook 文檔。
6. Picologging
Mirosoft 的 Picologging 庫(kù)是 Python 日志生態(tài)系統(tǒng)的一個(gè)相對(duì)較新的補(bǔ)充。如其 GitHub Readme 所述,它被定位為標(biāo)準(zhǔn)日志模塊的高性能直接替代品,速度可顯著提高 4-10 倍。要將其集成到你的項(xiàng)目中,你可以用以下命令安裝它:
$ pip install picologging
Picologging 與 Python 中的logging
模塊共享相同的熟悉的 API,并且使用相同的日志記錄屬性進(jìn)行格式化:
import sys
import picologging as logginglogger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)stdout_handler = logging.StreamHandler(sys.stdout)
fmt = logging.Formatter("%(name)s: %(asctime)s | %(levelname)s | %(process)d >>> %(message)s"
)stdout_handler.setFormatter(fmt)
logger.addHandler(stdout_handler)logger.info("Successfully connected to the database '%s' on host '%s'", "my_db", "ubuntu20.04"
)logger.warning("Detected suspicious activity from IP address: %s", "111.222.333.444")
__main__: 2023-07-24 05:46:38,-2046715687 | INFO | 795975 >>> Successfully connected to the database 'my_db' on host 'ubuntu20.04'
__main__: 2023-07-24 05:46:38,-2046715687 | WARNING | 795975 >>> Detected suspicious activity from IP address: 111.222.333.444
Picologging 的文檔強(qiáng)調(diào),它目前還處于早期開(kāi)發(fā)階段,因此應(yīng)暫緩在生產(chǎn)中使用。不過(guò),根據(jù)這些benchmarks,Picologging 在提高標(biāo)準(zhǔn)日志模塊的性能方面已經(jīng)顯示出了一定的前景。有關(guān)其功能和限制的更多信息,請(qǐng)參閱文檔。
最后的想法
在 Python 中使用日志記錄時(shí),我們的主要推薦是使用 Loguru,因?yàn)樗哂辛钊擞∠笊羁痰墓δ芎陀脩粲押玫?API。不過(guò),熟悉built-in logging
模塊也很重要,因?yàn)樗匀皇且粋€(gè)功能強(qiáng)大、使用廣泛的解決方案。
Structlog 是另一個(gè)值得考慮的強(qiáng)大選項(xiàng),Eliot 也可能是一個(gè)不錯(cuò)的選擇,只要它缺乏日志級(jí)別并不是您的用例的主要問(wèn)題。另一方面,Picologging 目前還處于早期開(kāi)發(fā)階段,而 Logbook 缺乏對(duì)結(jié)構(gòu)化日志記錄的原生支持,因此不太適合在生產(chǎn)環(huán)境中進(jìn)行日志記錄。