前言
在python中用到日志记录,早期接触的时候一直用的是内置的标准库logging。虽然logging库采用的是模块化设计,你可以设置不同的handler来进行组合,但是在配置上通常较为繁琐;而且如果不是特别处理,在一些多线程或多进程的场景下还需考虑各种异步处理情况,每一次应用得写个封装类来调。
但有这么一个第三方库,它不仅能够减少繁琐的配置过程还能实现和logging类似的功能,同时还能保证日志记录的线程进程安全,又能够和logging相兼容,并进一步追踪异常也能进行详细的代码回溯。这个库叫loggur——旨在为 Python 带来愉快的日志记录。
git地址:https://github.com/Delgan/loguru
安装
pip install loguru
loguru
1. 日志打印
from loguru import logger
logger.debug('this is a debug message')
logger.info('this is a info message')
logger.warning('this is a warning message')
logger.error('this is a error message')
logger.critical('this is a critical message')
可以看到其默认的输出格式是上面的内容,有时间、级别、模块名、行号以及日志信息,不需要手动创建 logger,直接使用即可,另外其输出还是彩色的,看起来会更加友好。
2. 文件配置
以上的日志信息是直接输出到控制台的,并没有输出到其他的地方,如果想要输出到其他的位置,比如存为文件,我们只需要使用一行代码声明即可。 调用如下:
from loguru import logger
logger.add('./logs/file_{time}.log', encoding='utf-8')
logger.debug('this is a debug message')
我们再也不需要声明一个文件句柄了,就一行 add 语句搞定,运行之后会发现自动创建logs以及目录下 file_%Y-%m-%d_%H_%M_%S_%f.log 文件,文件记录了 DEBUG 信息。下面解下它的其他一些功能,包括留存、清理、压缩方法。
from loguru import logger
# 使用 format、filter、level 来规定输出的格式
logger.add(sys.stderr, format="{time} {level} {message}", filter="my_module", level="INFO")
# 每超过500M创建一个新文件输出保存
logger.add("file_{time}.log", rotation="500 MB")
# 每天12:00创建一个新文件输出保存
logger.add("file_{time}.log", rotation="12:00")
# 每隔一周创建一个新文件输出保存
logger.add("file_{time}.log", rotation="1 week")
# 设置日志文件最长保留 10 天
logger.add("file_{time}.log", retention="10 days")
# 使用 zip 文件格式保存
logger.add("file_{time}.log", compression="zip")
3. 字符串格式化
loguru 在输出 log 的时候还提供了非常友好的字符串格式化功能,像这样:
logger.info('If you are using Python {}, prefer {feature} of course!', 3.6, feature='f-strings')
4. 异常捕获记录
在很多情况下,如果遇到运行错误,而我们在打印输出 log 的时候万一不小心没有配置好 Traceback 的输出,很有可能我们就没法追踪错误所在了。 loguru可以直接用它提供的装饰器进行 Traceback 的记录,类似这样的配置即可:
@logger.catch # 装饰器捕获
def my_function(x, y, z):
# An error? It's caught anyway!
return 1 / (x + y + z)
def my_function2(x, y, z):
try:
return 1 / (x + y + z)
except:
logger.exception('what?') # exception方法捕获
my_function(0, 0, 0) # 引发ZeroDivisionError异常通过装饰器捕获
my_function2(0, 0, 0) # 引发ZeroDivisionError异常通过exception方法捕获
5. 序列化
希望日志被序列化以便于解析或传递它们,可以使用该serialize参数,每条日志消息将在发送到配置的接收器之前转换为 JSON 字符串。
from loguru import logger
logger.add('./logs/file_{time}.log', encoding='utf-8', serialize=True)
logger.debug('this is a debug message')
logger.info('this is a info message')
{"text": "2022-01-04 15:20:47.614 | DEBUG | __main__:<module>:10 - this is a debug message\n", "record": {"elapsed": {"repr": "0:00:00.003996", "seconds": 0.003996}, "exception": null, "extra": {}, "file": {"name": "test.py", "path": "E:/code/small_project/test.py"}, "function": "<module>", "level": {"icon": "\ud83d\udc1e", "name": "DEBUG", "no": 10}, "line": 10, "message": "this is a debug message", "module": "test", "name": "__main__", "process": {"id": 13912, "name": "MainProcess"}, "thread": {"id": 11852, "name": "MainThread"}, "time": {"repr": "2022-01-04 15:20:47.614379+08:00", "timestamp": 1641280847.614379}}}
{"text": "2022-01-04 15:20:47.614 | INFO | __main__:<module>:11 - this is a info message\n", "record": {"elapsed": {"repr": "0:00:00.003996", "seconds": 0.003996}, "exception": null, "extra": {}, "file": {"name": "test.py", "path": "E:/code/small_project/test.py"}, "function": "<module>", "level": {"icon": "\u2139\ufe0f", "name": "INFO", "no": 20}, "line": 11, "message": "this is a info message", "module": "test", "name": "__main__", "process": {"id": 13912, "name": "MainProcess"}, "thread": {"id": 11852, "name": "MainThread"}, "time": {"repr": "2022-01-04 15:20:47.614379+08:00", "timestamp": 1641280847.614379}}}
6. 多模块多线程应用
在 loguru 中有且仅有一个对象:logger。所有添加至logger的sink默认都是线程安全的。
所以loguru是可以在多模块多线程下使用的,只需要在对应模块下导入logger引用即可。
from loguru import logger
更多玩法
飞书异常通知
# _*_ coding:utf-8 _*_
# @Time : 2022/1/4 14:26
# @Author : mancheng
# @File : test.py
from loguru import logger
import requests
import json
def except_only(record):
return record["level"].name == "ERROR" and record["exception"] is not None
def except_sink(message):
# print("The full exception message: ", message)
# print("The record: ", message.record)
# print("The message: ", message.record["message"])
# print("The exception: ", message.record["exception"].value)
webhook = 'https://open.feishu.cn/open-apis/bot/v2/hook/67097366-a3c8-4f1b-b17c-971be81e59dd' # 飞书机器人webhook
body = {"msg_type": "post",
"content": {
"post": {
"zh_cn": {
"title": "Exception: {}".format(message.record["exception"].value),
"content": [
[{"tag": "text", "text": "报错内容信息如下:"}],
[{"tag": "text", "text": message}],
],
}
}
}}
with requests.post(webhook, data=json.dumps(body)) as response:
logger.info(response.content)
@logger.catch
def my_function(x, y, z):
# An error? It's caught anyway!
return 1 / (x + y + z)
def run():
my_function(0, 0, 0)
logger.add('./logs/{time}.log', encoding='utf-8', backtrace=True, diagnose=True)
logger.add(except_sink, filter=except_only, enqueue=True) # 添加异常钩子
run()
再也不需要登录服务器去tail查看日志异常啦。
欢迎更多玩法添加补充哦。