优雅高效的日志管理loguru

前言

在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查看日志异常啦。

欢迎更多玩法添加补充哦。

posted @ 2022-05-09 12:11  MC-Blog  阅读(586)  评论(0编辑  收藏  举报