Loguru:Python日志管理模块的更优雅、简洁拓展

【拓展】Loguru:更为优雅、简洁的Python 日志管理模块

【一】引入

  • 在 Python 开发中涉及到日志记录,我们或许通常会想到内置标准库 —— logging 。
  • 虽然logging 库采用的是模块化设计,可以设置不同的 handler 来进行组合,但是在配置上较为繁琐。
  • 同时在多线程或多进程的场景下,若不进行特殊处理还会导致日志记录会出现异常。
  • 本文将介绍一个十分优雅、简洁的日志记录第三方库—— loguru ,我们可以通过导入其封装的logger 类的实例,即可直接进行调用。
  • 【二】安装

  • 使用 pip 安装即可,Python 3 版本的安装如下:
  • pip3 install loguru

    【三】基本使用

    【1】使用方法

  • 我们直接通过导入loguru 封装好的logger 类的实例化对象,不需要手动创建 logger,直接进行调用不同级别的日志输出方法。
  • 【2】示例

    from loguru import logger
    
    logger.debug('This is debug information')
    logger.info('This is info information')
    logger.warning('This is warn information')
    logger.error('This is error information')

    【3】样式

  • 在 IDE 或终端运行时会发现
  • loguru 在输出的不同级别信息时,带上了不同的颜色,使得结果更加直观
  • 其中也包含了时间、级别、模块名、行号以及日志信息。
  • 【4】不同日志级别与日志记录方法对应关系

  • loguru 中不同日志级别与日志记录方法对应关系 如下:
  • 级别名称 严重度值 记录器法
    TRACE 5 logger.trace()
    DEBUG 10 logger.debug()
    INFO 20 logger.info()
    SUCCESS 25 logger.success()
    WARNING 30 logger.warning()
    ERROR 40 logger.error()
    CRITICAL 50 logger.critical()
  • 简单语法
  • logger.trace('这是一条记录日志)
    logger.debug('这是一条测试日志')
    logger.info('这是一条信息日志')
    logger.success('这是一条成功日志')
    logger.warning('这是一条警告日志')
    logger.error('这是一条错误日志')
    logger.critical('这是一条严重错误日志')

    【5】日志规则

  • 设置日志格式,过滤器,日志级别
  • from loguru import logger
    
    logger.add("file.log", format="{time} {level} {message}", level="INFO")
    
    logger.debug("这是一条测试日志")
    logger.info("这是一条信息日志")
  • 终端展示
  • 2023-08-07 23:16:25.307 | DEBUG    | __main__:<module>:18 - 这是一条测试日志
    2023-08-07 23:16:25.307 | INFO     | __main__:<module>:19 - 这是一条信息日志

    【6】保存到文件

    from loguru import logger
    
    logger.add("file.log")
    
    logger.debug("这是一条debug日志")
    logger.info("这是一条info日志")
  • 结果为目录多出一个日志文件:file.log
  • 【7】日志文件拆分(add方法)

  • 由于日志文件会非常大,所以实际情况中会对日志文件进行拆分。
  • from loguru import logger
    
    logger.add("log_{time}.log", encoding='utf-8')  # 以时间命名

    【四】loguru 配置日志文件

  • logger 默认采用 sys.stderr 标准错误输出将日志输出到控制台中,假如想要将日志同时输出到其他的位置,比如日志文件,此时我们只需要使用一行代码即可实现。
  • 例如,将日志信息输出到 2021-3-28.log 文件中,可以这么写:
  • from loguru import logger
    
    logger.add("E:/Old Boy/day_projects/日志模块/log_2023-8-7.log",rotation="500MB", encoding="utf-8", enqueue=True, retention="10 days")
    
    logger.info('This is info information')
  • 终端显示结果
  • 2023-08-07 22:47:11.611 | INFO     | __main__:<module>:19 - This is info information
  • 如上,loguru直接通过 add() 方法,完成了日志文件的配置。
  • 【五】日志内容的字符串格式化

  • loguru 在输出 日志的时候,还提供了非常灵活的字符串格式化输出日志的功能,如下:
  • import platform
    from loguru import logger
    
    rounded_value = round(0.345, 2)
    
    trace= logger.add('2023-8-7.log')
    
    logger.info('If you are using Python {version}, prefer {feature} of course!', version=platform.python_version(), feature='f-strings')
  • 执行上述代码,输出结果为
  • 2023-08-07 22:46:03.812 | INFO     | __main__:<module>:21 - If you are using Python 3.9.13, prefer f-strings of course!

    【六】loguru日志常用参数配置解析

  • sink:可以传入一个 file 对象(file-like object),或一个 str 字符串或者 pathlib.Path 对象,或一个方法(coroutine function),或 logging 模块的 Handler(logging.Handler)。
  • level (int or str, optional) :应将已记录消息发送到接收器的最低严重级别。
  • format (str or callable, optional) :格式化模块,在发送到接收器之前,使用模板对记录的消息进行格式化。
  • filter (callable, str or dict, optional) :用于决定每个记录的消息是否应该发送到接收器。
  • colorize (bool, optional) – 是否应将格式化消息中包含的颜色标记转换为用于终端着色的Ansi代码,或以其他方式剥离。如果None,根据水槽是否为TTY自动作出选择。
  • serialize (bool, optional) :在发送到接收器之前,记录的消息及其记录是否应该首先转换为JSON字符串。
  • backtrace (bool, optional) :格式化的异常跟踪是否应该向上扩展,超出捕获点,以显示生成错误的完整堆栈跟踪。
  • diagnose (bool, optional) :异常跟踪是否应该显示变量值以简化调试。在生产中,这应该设置为“False”,以避免泄漏敏感数据。
  • enqueue (bool, optional) :要记录的消息在到达接收器之前是否应该首先通过多进程安全队列。当通过多个进程将日志记录到文件中时,这是非常有用的。这还具有使日志调用非阻塞的优点。
  • catch (bool, optional) :是否应该自动捕获接收器处理日志消息时发生的错误。如果True上显示异常消息 sys.stderr。但是,异常不会传播到调用者,从而防止应用程序崩溃。
  • 如果当接收器(sink)是文件路径( pathlib.Path )时,可以应用下列参数,同时add() 会返回与所添加的接收器相关联的标识符:

  • rotation:分隔日志文件,何时关闭当前日志文件并启动一个新文件的条件,;例如,"500 MB"、"0.5 GB"、"1 month 2 weeks"、"10h"、"monthly"、"18:00"、"sunday"、"monday at 18:00"、"06:15"
  • retention (str, int, datetime.timedelta or callable, optional) ,可配置旧日志的最长保留时间,例如,"1 week, 3 days"、"2 months"
  • compression (str or callable, optional) :日志文件在关闭时应转换为的压缩或归档格式,例如,"gz"、"bz2"、"xz"、"lzma"、"tar"、"tar.gz"、"tar.bz2"、"tar.xz"、"zip"
  • delay (bool, optional):是否应该在配置了接收器之后立即创建文件,或者延迟到第一个记录的消息。默认为' False '。
  • mode (str, optional) :与内置open()函数一样的打开模式。默认为' "a"(以附加模式打开文件)。
  • buffering (int, optional) :内置open()函数的缓冲策略,它默认为1(行缓冲文件)。
  • encoding (str, optional) :文件编码与内置的' open() '函数相同。如果' None ',它默认为'locale.getpreferredencoding() 。
  • 【七】loguru 日志常用方式

    【1】停止日志记录到文件中

  • add 方法 添加 sink 之后我们也可以对其进行删除, 删除的时候根据刚刚 add 方法返回的 id 进行删除即可,还原到标准输出。如下:
  • from loguru import logger
    
    trace = logger.add('2021-8-7.log')
    logger.error('This is error information')
    
    logger.remove(trace)
    logger.warning('This is warn information')
  • 终端显示结果
  • 2023-08-07 22:50:37.834 | ERROR    | __main__:<module>:17 - This is error information
    2023-08-07 22:50:37.834 | WARNING  | __main__:<module>:20 - This is warn information
  • 日志2023-8-7.log内容如下
  • 2023-08-07 22:50:37.834 | ERROR    | __main__:<module>:17 - This is error information
  • 将 sink 对象移除之后,在这之后的内容不会再输出到日志文件中。
  • 只输出到文本,不在console输出
  • 通过 logger.remove(handler_id=None) 删除以前添加的处理程序,并停止向其接收器发送日志。然后通过add 添加输出日志文件,即可 实现 只输出到文本,不在console输出,如下:
  • from loguru import logger
    
    # 清除之前的设置
    logger.remove(handler_id=None)
    
    trace = logger.add('2023-8-7.log')
    
    logger.error('This is error information')
    logger.warning('This is warn information')

    【2】filter 配置日志过滤规则

  • 如下,我们通过实现自定义方法error_only,判断日志级别
  • 当日志级别为ERROR,返回TRUE,我们在add方法设置filter参数时,设置为error_only,即可过滤掉ERROR以外的所有日志 。
  • from loguru import logger
    
    
    def error_only(record):
        """
        error 日志 判断 
        Args:
            record: 
    
        Returns: 若日志级别为ERROR, 输出TRUE
    
        """
        return record["level"].name == "ERROR"
    
    
    # ERROR以外级别日志被过滤掉
    logger.add("2023-8-7.log", filter=error_only)
    
    logger.error('This is error information')
    logger.warning('This is warn information')
  • 终端显示
  • 2023-08-07 22:53:14.679 | ERROR    | __main__:<module>:32 - This is error information
    2023-08-07 22:53:14.680 | WARNING  | __main__:<module>:33 - This is warn information
  • 2023-8-7.log 日志中,我们可以看到仅记录了ERROR级别日志。
  • 2023-08-07 22:53:14.679 | ERROR    | __main__:<module>:32 - This is error information

    【3】format 配置日志记录格式化模板

    from loguru import logger
    
    
    def format_log():
        """
    
        Returns:
    
        """
        trace = logger.add('2023-8-7.log',
                           format="{time:YYYY-MM-DD HH:mm:ss} {level} From {module}.{function} : {message}")
    
        logger.warning('This is warn information')
    
    
    if __name__ == '__main__':
        format_log()
  • 终端显示
  • 2023-08-07 22:54:11.901 | WARNING  | __main__:format_log:26 - This is warn information
  • 如下,我们可以看到在 2023-8-7.log 日志文件中,如 "{time:YYYY-MM-DD HH:mm:ss} {level} From {module}.{function} : {message}" 格式模板进行记录:
  • 2023-08-07 22:54:11 WARNING From 01 日志模块测试.format_log : This is warn information

    【4】enqueue 异步写入

    logger.add("2023-8-7.log", enqueue=True)
  • 使用enqueue,可保证线程安全,多线程安全。
  • 【5】其它的格式化模板属性 如下:

    Key Description
    elapsed 从程序开始经过的时间差
    exception 格式化异常(如果有),否则为' None '
    extra 用户绑定的属性字典(参见bind())
    file 进行日志记录调用的文件
    function 进行日志记录调用的函数
    level 用于记录消息的严重程度
    line 源代码中的行号
    message 记录的消息(尚未格式化)
    module 进行日志记录调用的模块
    name 进行日志记录调用的__name__
    process 进行日志记录调用的进程名
    thread 进行日志记录调用的线程名
    time 发出日志调用时的可感知的本地时间

    (1)通过 extra bind() 添加额外属性来为结构化日志提供更多属性信息

  • 如下:
  • from loguru import logger
    
    
    def format_log():
        """
    
        Returns:
    
        """
        trace = logger.add('2023-8-7.log',
                           format="{time:YYYY-MM-DD HH:mm:ss} {extra[ip]}  {extra[username]} {level} From {module}.{function} : {message}")
    
        extra_logger = logger.bind(ip="192.168.0.1", username="张三")
        extra_logger.info('This is info information')
        extra_logger.bind(username="李四").error("This is error information")
    
        extra_logger.warning('This is warn information')
    
    
    if __name__ == '__main__':
        format_log()
  • 终端显示
  • 2023-08-07 22:56:16.435 | INFO     | __main__:format_log:27 - This is info information
    2023-08-07 22:56:16.435 | ERROR    | __main__:format_log:28 - This is error information
    2023-08-07 22:56:16.436 | WARNING  | __main__:format_log:30 - This is warn information
  • 如下,我们可以看到在 2023-8-7.log 日志文件中,看到日志按上述模板记录,如下:
  • 2023-08-07 22:56:16 192.168.0.1  张三 INFO From 01 日志模块测试.format_log : This is info information
    2023-08-07 22:56:16 192.168.0.1  李四 ERROR From 01 日志模块测试.format_log : This is error information
    2023-08-07 22:56:16 192.168.0.1  张三 WARNING From 01 日志模块测试.format_log : This is warn information

    (2)level 配置日志最低日志级别

    from loguru import logger
    
    trace = logger.add('2023-8-7.log', level='ERROR')

    (3)rotation 配置日志滚动记录的机制

  • 我们想周期性的创建日志文件,或者按照文件大小自动分隔日志文件,我们可以直接使用 add 方法的 rotation 参数进行配置。
  • 例如,每 200MB 创建一个日志文件,避免每个 log 文件过大,如下:
  • from loguru import logger
    
    trace = logger.add('2023-8-7.log', rotation="200 MB")
  • 例如,每天6点 创建一个日志文件,如下:
  • from loguru import logger
    
    trace = logger.add('2023-8-7.log', rotation='06:00')
  • 例如,每隔2周创建一个 日志文件,如下:
  • from loguru import logger
    
    trace = logger.add('2023-8-7.log', rotation='2 week')
  • 例如,定时清理 日志文件,如下:
  • from loguru import logger
    logger.add("2023-8-7.log", retention="10 days")

    (4)retention 配置日志保留机制

  • 通常,一些久远的日志文件,需要周期性的去清除,避免日志堆积,浪费存储空间。
  • 我们可以通过add方法的 retention 参数可以配置日志的最长保留时间。
  • 例如,设置日志文件最长保留 7 天,如下:
  • from loguru import logger
    
    trace = logger.add('2023-8-7.log', retention='7 days')

    (5)compression 配置日志压缩格式

  • loguru 还可以配置文件的压缩格式,比如使用 zip 文件格式保存,示例如下:
  • from loguru import logger
    
    trace = logger.add('2023-8-7.log', compression='zip')
  • 通过设置compression参数,来选择文件压缩格式,如 zip、tar、gz 等。
  • (6)serialize 日志序列化

  • 如果我们希望输出类似于Json-line格式的结构化日志,我们可以通过 serialize 参数,将日志信息序列化的json格式写入log 文件,最后可以将日志文件导入类似于MongoDB、ElasticSearch 中用作后续的日志分析,代码示例如下:
  • from loguru import logger
    import platform
    
    rounded_value = round(0.345, 2)
    
    trace= logger.add('2023-8-7.log', serialize=True)
    
    logger.info('If you are using Python {version}, prefer {feature} of course!', version=platform.python_version(), feature = 'f-strings')
  • 终端显示
  • 2023-08-07 23:01:14.356 | INFO     | __main__:<module>:21 - If you are using Python 3.9.13, prefer f-strings of course!
  • 在2023-8-7.log日志文件,我们可以看到每条日志信息都被序列化后存在日志文件中,如下:
  • {
        "text": "2023-08-07 23:01:14.356 | INFO     | __main__:<module>:21 - If you are using Python 3.9.13, prefer f-strings of course!\n",
        "record": {
            "elapsed": {
                "repr": "0:00:00.018001", 
                "seconds": 0.018001
            },
            "exception": null,
            "extra": {
                "version": "3.9.13", 
                "feature": "f-strings"
            },
            "file": {
                "name": "01 日志模块测试.py",
                "path": "E:\\day_projects\\日志模块\\01 日志模块测试.py"
            },
            "function": "<module>",
            "level": {
                "icon": "\u2139\ufe0f",
                "name": "INFO",
                "no": 20
            },
            "line": 9,
            "message": "If you are using Python 3.9.13, prefer f-strings of course!",
            "module": "01 日志模块测试",
            "name": "__main__",
            "process": {
                "id": 21636,
                "name": "MainProcess"
            },
            "thread": {
                "id": 28612, 
                "name": "MainThread"
            },
            "time": {
                "repr": "2023-08-07 23:01:14.356111+08:00",
                "timestamp": 1691420474.356111
            }
        }
    }

    (7)Traceback 记录(异常追溯)

  • loguru集成了一个名为 better_exceptions 的库,不仅能够将异常和错误记录,并且还能对异常进行追溯
  • 如下,我们通过在遍历列表的过程中删除列表元素,以触发IndexError 异常,
  • 通过catch装饰器的方式实现异常捕获,代码示例如下:
  • from loguru import logger
    
    trace = logger.add('2023-8-7.log')
    
    
    @logger.catch
    def index_error(custom_list: list):
        for index in range(len(custom_list)):
            index_value = custom_list[index]
            if custom_list[index] < 2:
                custom_list.remove(index_value)
    
            print(index_value)
    
    
    if __name__ == '__main__':
        index_error([1, 2, 3])
  • 运行上述代码,我们可以发现loguru输出的 Traceback 日志信息, Traceback 日志信息中同时输出了当时的变量值,如下:
  • 在 2023-8-7.log 日志文件中也同样输出了上述格式的异常追溯信息,如下。
  • 2023-08-07 23:08:12.010 | ERROR    | __main__:<module>:30 - An error has been caught in function '<module>', process 'MainProcess' (17440), thread 'MainThread' (26612):
    Traceback (most recent call last):
    
    > File "E:\Old Boy\day_projects\日志模块\01 日志模块测试.py", line 30, in <module>
        index_error([1, 2, 3])
        └ <function index_error at 0x000001DFBB471550>
    
      File "E:\Old Boy\day_projects\日志模块\01 日志模块测试.py", line 22, in index_error
        index_value = custom_list[index]
                      │           └ 2
                      └ [2, 3]
    
    IndexError: list index out of range
  • 同时,附上对类中的类方法和静态方法的代码实例,以供参考
  • from loguru import logger
    
    trace = logger.add('2023-8-7.log')
    
    class Demo:
        @logger.catch
        def index_error(self, custom_list: list):
            for index in range(len(custom_list)):
                index_value = custom_list[index]
                if custom_list[index] < 2:
                    custom_list.remove(index_value)
    
        @staticmethod
        @logger.catch
        def index_error_static(custom_list: list):
            for index in range(len(custom_list)):
                index_value = custom_list[index]
                if custom_list[index] < 2:
                    custom_list.remove(index_value)
    
    
    if __name__ == '__main__':
        # Demo().index_error([1, 2, 3])
        Demo.index_error_static([1, 2, 3])

    (8)通过 logger.exception 方法也可以实现异常的捕获与记录:

    from loguru import logger
    
    trace = logger.add('2023-8-7.log')
    
    
    def index_error(custom_list: list):
        for index in range(len(custom_list)):
            try:
                index_value = custom_list[index]
            except IndexError as err:
                logger.exception(err)
                break
    
            if custom_list[index] < 2:
                custom_list.remove(index_value)
    
    
    if __name__ == '__main__':
        index_error([1, 2, 3])
  • 终端打印
  • 2023-08-07 23:11:16.587 | ERROR    | __main__:index_error:24 - list index out of range
    Traceback (most recent call last):
    
      File "E:\Old Boy\day_projects\日志模块\01 日志模块测试.py", line 32, in <module>
        index_error([1, 2, 3])
        └ <function index_error at 0x000002B1327E01F0>
    
    > File "E:\Old Boy\day_projects\日志模块\01 日志模块测试.py", line 22, in index_error
        index_value = custom_list[index]
                      │           └ 2
                      └ [2, 3]
    
    IndexError: list index out of range
  • 运行上述代码,我们可以发现loguru输出的 Traceback 日志信息, Traceback 日志信息中同时输出了当时的变量值,如下:
  • 物联沃分享整理
    物联沃-IOTWORD物联网 » Loguru:Python日志管理模块的更优雅、简洁拓展

    发表评论