别再手动配置logging了大家都在用loguru

在部署一些定时运行或者长期运行的任务时,为了留存一些导致程序出现异常或错误的信息,通常会才用日志的方式来进行记录这些信息。

Python 中用到日志记录,那就不可避免地会用到内置的 logging标准库 。虽然logging 库采用的是模块化设计,你可以设置不同的 handler 来进行组合,但是在配置上通常较为繁琐;而且如果不是特别处理,在一些多线程或多进程的场景下使用 logging还会导致日志记录会出现错乱或是丢失的情况。

但有这么一个库,它不仅能够减少繁琐的配置过程还能实现和logging类似的功能,同时还能保证日志记录的线程进程安全,又能够和logging 相兼容,并进一步追踪异常也能进行代码回溯。这个库叫loguru——一个专为像我这样懒人而生日志记录库。

loguru 库的使用可以说是十分简单,我们直接可以通过导入它本身封装好的logger 类就可以直接进行调用。

#!pip install loguru
from loguru import logger

logger 本身就是一个已经实例化好的对象,如果没有特殊的配置需求,那么自身就已经带有通用的配置参数;同时它的用法和 logging库输出日志时的用法一致

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

当你在IDE 或终端里运行时会发现,loguru 还为输出的日志信息带上了不同的颜色样式(schema),使得结果更加美观。

1.png

当然,loguru 也像logging一样为我们提供了其他可配置的部分,但相比于 logging 每次要导入特定的handler再设定一些formatter来说是更为「傻瓜化」了。

配置

使用基本的add() 方法就可以对logger 进行简单的配置,这些配置有点类似于使用 logging 时的 handler。这里简单提及一下比较常用的几个。

写入日志

在不指定任何参数时,logger 默认采用 sys.stderr 标准错误输出将日志输出到控制台(console)中;但在linux 服务器上我们有时不仅让其输出,还要以文件的形式进行留存,那么只需要在第一个参数中传入一个你想要留存文件的路径字符串即可。就像这样:

from loguru import logger
import os

logger.add(os.path.expanduser("~/Desktop/testlog.log"))
logger.info("hello, world!")

这样在你的桌面上就会直接出现相应的testlog.log日志文件了。

但是如果你没有自己要是用logging没有预先封装来操作,那估计你得写成这样:

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学习资料的或者有技术问题交流点击下方链接即可
https://docs.qq.com/doc/DTGpFa2lVeE9jUkRv

日志留存、压缩与清理

通常来说如果程序或服务的量级较大,那么就可以通过集成的日志平台或数据库来对日志信息进行存储和留存,后续有需要的话也方便进行日志分析。

但对我们个人或者一些中小型项目来说,通常只需要以文件的形式留存输出的日志即可。

尽管我们需要将日志写入到相应的文件中,如果是少量的日志那还好,但是如果是日志输出或记录时间较长的情况,那么单个日志文件就十分之大,倘若仍然是将日志都写入到一个文件中,那么当日志中的内容增长到一定数量时我们想要读取并查找相应的部分时就十分困难。这时候我们就需要对日志文件进行留存、压缩,甚至在必要时及时进行清理。

基于以上,我们可以通过对rotation 、compressionretention 三个参数进行设定来满足我们的需要:

rotation 参数能够帮助我们将日志记录以大小、时间等方式进行分割或划分:

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}")

最后呈现如下:

2.png

随着分割文件的数量越来越多之后,我们也可以进行压缩对日志进行留存,这里就要使用到 compression参数,该参数只要你传入通用的压缩文件扩展名即可,如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}")

从结果可以看到,只要是满足了rotation分割后的日志文件都被直接压缩成了zip 文件,文件大小由原本的 200kb 直接减少至10kb,对于一些磁盘空间吃紧的Linux服务器来说是则是很有必要的。

3.png

当然了,如果你不想对日志进行留存,或者只想保留一段时间内的日志并对超期的日志进行删除,那么直接使用 retention 参数就好了。

这里我们可以将之前的结果随意复制 N 多份在logs文件夹中,然后再执行一次加上 retension 参数后代码:

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}")

当然对retention 传入整数时,该参数表示的是所有文件的索引,而非要保留的文件数,这里是个反直觉的小坑,用的时候注意一下就好了。所以最后我们会看到只有两个时间最近的日志文件会被保留下来,其他都被直接清理掉了。

4.png

序列化

如果在实际中你不太喜欢以文件的形式保留日志,那么你也可以通过 serialize 参数将其转化成序列化的json格式,最后将导入类似于MongoDB、ElasticSearch 这类数NoSQL 数据库中用作后续的日志分析。

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
        }
    }
}

异常追溯

当异常和错误不可避免时,最好的方式就是让我们知道程序到底是哪里出了错,或者是因为什么导致错误,这样才能更好地让开发人员及时应对并解决。

loguru集成了一个名为better_exceptions 的库,不仅能够将异常和错误记录,并且还能对异常进行追溯,这里是来自一个官网的例子

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)

最后在日志文件中我们可以得到以下内容:

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)

尽管说loguru 算是重新「造轮子」,但是它也能和logging库很好地兼容。到现在我们才谈论到add() 方法的第一个参数 sink

这个参数的英文单词动词有「下沉、浸没」等意,对于外国人来说在理解上可能没什么难的,可对我们国人来说,这可之前logging 库中的handler 概念还不好理解。好在前面我有说过,logurulogging 库的使用上存在相似之处,因此在后续的使用中其实我们就可以将其理解为handler,只不过它的范围更广一些,可以除了 handler 之外的字符串、可调用方法、协程对象等。

loguru 官方文档对这一参数的解释是:

 object in charge of receiving formatted logging messages and propagating them to an appropriate endpoint.

翻译过来就是「一个用于接收格式化日志信息并将其传输合适端点的对象」,进一步形象理解就像是一个「分流器」。

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")

当然目前只是想在之前基于logging 写好的模块中集成loguru,只要重新编写一个继承自 logging.Handler 类并实现了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)

后结果同之前的异常追溯一致。而我们只需要在配置后直接调用logging 的相关方法即可,减少了迁移和重写的成本。

最后

本文介绍了关于loguru的常用方法,从对比例子上来看,相比于复杂的 logging 配置来说,使用loguru 库无疑还是很香的,毕竟别人已经为我们一些日常的通用性需求提供了封装好的解决方案,无论是在学习还是在使用的成本上,无疑还是比较小的。

由于篇幅有限,loguru的其他配置部分没有进一步展开,如果看完本文的你对这个库感兴趣并打算投入到实际的开发和生产中使用,那么建议你还是阅读一下其官方文档,有必要的话可以浏览一下源码。

不过loguru的通用配置不一定满足每个人的需要,对于那些动手能力强或水平较高的朋友还能进一步根据个人需求或业务需求进行二次封装,或许也能较为贴合实际情况。

以上就是小编今天为大家带来的内容,小编本身就是一名python开发工程师,我自己花了三天时间整理了一套python学习教程,从最基础的python脚本到web开发,爬虫,数据分析,数据可视化,机器学习,等,这些资料有想要的小伙伴点击下方连接即可领取
https://docs.qq.com/doc/DTGpFa2lVeE9jUkRv

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,504评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,434评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,089评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,378评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,472评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,506评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,519评论 3 413
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,292评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,738评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,022评论 2 329
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,194评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,873评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,536评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,162评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,413评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,075评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,080评论 2 352