Python日志记录进阶:从logging到loguru的优雅实践

叶初
叶初
发布时间:2026-10-15 | 4人浏览过
原创

本文深入解析Python中两种主流的日志记录方案。首先剖析标准logging模块的配置繁琐痛点,展示如何封装以支持多端输出;随后重点介绍loguru库,通过简洁API实现文件轮转、保留策略、压缩及异常追踪等高级功能,帮助开发者构建更优雅、高效的日志系统。

在Python开发中,开发者通常直接使用内置的logging模块来记录日志,这包括作者早期的做法。使用该模块时,往往需要配置Handler和Formatter以处理具体逻辑,例如将日志输出至不同位置、设定特定的输出格式,或进行日志分块与备份。然而,个人体验表明,logging模块的实际使用体验并非十分理想,主要问题在于配置过程过于繁琐。

常见使用

首先回顾logging的常见解决方案。作者通常会配置将日志输出至文件、控制台以及Elasticsearch。输出到控制台便于直接查看;输出到文件则用于持久化存储,保留所有历史记录备份;而输出到Elasticsearch则是将其作为存储和分析中心,结合Kibana可以便捷地分析和查看运行状态。

因此,作者对logging进行了如下封装处理:

import logging
import sys
from os import makedirs
from os.path import dirname, exists

from cmreslogging.handlers import CMRESHandler

loggers = {}

LOG_ENABLED = True  # 是否开启日志
LOG_TO_CONSOLE = True  # 是否输出到控制台
LOG_TO_FILE = True  # 是否输出到文件
LOG_TO_ES = True  # 是否输出到 Elasticsearch

LOG_PATH = './runtime.log'  # 日志文件路径
LOG_LEVEL = 'DEBUG'  # 日志级别
LOG_FORMAT = '%(levelname)s - %(asctime)s - process: %(process)d - %(filename)s - %(name)s - %(lineno)d - %(module)s - %(message)s'  # 每条日志输出格式
ELASTIC_SEARCH_HOST = 'eshost'  # Elasticsearch Host
ELASTIC_SEARCH_PORT = 9200  # Elasticsearch Port
ELASTIC_SEARCH_INDEX = 'runtime'  # Elasticsearch Index Name
APP_ENVIRONMENT = 'dev'  # 运行环境,如测试环境还是生产环境

def get_logger(name=None):
    """
    get logger by name
    :param name: name of logger
    :return: logger
    """
    global loggers

    if not name: name = __name__

    if loggers.get(name):
        return loggers.get(name)

    logger = logging.getLogger(name)
    logger.setLevel(LOG_LEVEL)

    # 输出到控制台
    if LOG_ENABLED and LOG_TO_CONSOLE:
        stream_handler = logging.StreamHandler(sys.stdout)
        stream_handler.setLevel(level=LOG_LEVEL)
        formatter = logging.Formatter(LOG_FORMAT)
        stream_handler.setFormatter(formatter)
        logger.addHandler(stream_handler)

    # 输出到文件
    if LOG_ENABLED and LOG_TO_FILE:
        # 如果路径不存在,创建日志文件文件夹
        log_dir = dirname(log_path)
        if not exists(log_dir): makedirs(log_dir)
        # 添加 FileHandler
        file_handler = logging.FileHandler(log_path, encoding='utf-8')
        file_handler.setLevel(level=LOG_LEVEL)
        formatter = logging.Formatter(LOG_FORMAT)
        file_handler.setFormatter(formatter)
        logger.addHandler(file_handler)

    # 输出到 Elasticsearch
    if LOG_ENABLED and LOG_TO_ES:
        # 添加 CMRESHandler
        es_handler = CMRESHandler(hosts=[{'host': ELASTIC_SEARCH_HOST, 'port': ELASTIC_SEARCH_PORT}],
                                  # 可以配置对应的认证权限
                                  auth_type=CMRESHandler.AuthType.NO_AUTH,  
                                  es_index_name=ELASTIC_SEARCH_INDEX,
                                  # 一个月分一个 Index
                                  index_name_frequency=CMRESHandler.IndexNameFrequency.MONTHLY,
                                  # 额外增加环境标识
                                  es_additional_fields={'environment': APP_ENVIRONMENT}  
                                  )
        es_handler.setLevel(level=LOG_LEVEL)
        formatter = logging.Formatter(LOG_FORMAT)
        es_handler.setFormatter(formatter)
        logger.addHandler(es_handler)

    # 保存到全局 loggers
    loggers[name] = logger
    return logger

定义完成后,使用方式十分简单:只需调用定义的方法获取一个logger实例,随后记录对应的内容即可:

logger = get_logger()
logger.debug('this is a message')

运行结果如下所示:

DEBUG - 2019-10-11 22:27:35,923 - process: 99490 - logger.py - __main__ - 81 - logger - this is a message

接下来分析该定义的基本实现。首先,部分常量用于定义logging模块的基础属性,例如LOG_ENABLED代表是否启用日志功能,LOG_TO_ES代表是否将日志输出至Elasticsearch。此外还包含其他基础配置,如LOG_FORMAT指定了每条日志输出的基本格式,以及连接所需的其他必要信息。这些变量可与运行时的命令行参数或环境变量对接,从而方便地实现功能开关和配置的动态切换。

随后定义了一个get_logger方法,接收一个name参数。该方法首先获取name,然后在全局的loggers变量(一个全局字典)中查找。如果已存在声明过的logger,则直接获取并返回,无需二次初始化。若在loggers中未找到对应的logger,则进行创建。创建logger后,可为其添加各种Handler,例如输出到控制台使用StreamHandler,输出到文件使用FileHandler或RotatingFileHandler,输出至Elasticsearch则使用CMRESHandler,分别配置好相应信息即可。

最后,将新建的logger保存至全局loggers中并返回。这样,当再次获取同名logger时,可直接从loggers中查找并返回。

此处依赖了一个额外的用于输出至Elasticsearch的包,名为CMRESHandler,它支持将日志输出到Elasticsearch。如需使用,可安装以下组件:

pip install CMRESHandler其GitHub地址为:https://github.com/cmanaha/python-elasticsearch-logger,具体使用方式可参考其官方说明,例如配置认证信息及Index分隔信息等。

上述即为作者此前常用的logging配置方案。通过此类配置,可实现将logging输出至三个位置,并达到预期效果。例如,输出至Elasticsearch后,即可便捷地使用Kibana查看当前运行状况、ERROR Log的比例等:

也可在此基础上进行更深入的统计分析。

loguru

上述实现方案已具备一定可行性。然而,作者仍觉得配置Handler较为麻烦,特别是在新建项目时,往往不愿编写配置代码。即便不使用上述复杂配置,仅使用最基本的几行logging配置,如以下通用配置:

import logging
logging.basicConfig(level = logging.INFO,format = '%(asctime)s - %(name)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)

作者也懒得编写,认为这并非一种优雅的实现方式。

有需求便产生动力,因此有人实现了这样一个库,名为loguru,旨在使日志的配置和使用更加简单便捷。

接下来探讨其具体用法。

安装

首先,该库的安装方式十分简单,使用基本的pip安装即可。Python 3版本的安装命令如下:

pip3 install loguru

安装完成后,即可在项目中使用loguru库。

基本使用

那么该库如何使用呢?我们先通过一个实例来感受:

from loguru import logger

logger.debug('this is a debug message')

由此可见,无需配置任何内容,直接引入logger并调用其debug方法即可。

在loguru中,有且仅有一个主要对象,即logger。loguru内部仅包含一个logger,且已预先配置了一些基础信息,例如更友好的格式化输出和文本颜色等。

上述代码的运行结果如下:

2019-10-13 22:46:12.367 | DEBUG | __main__:<module>:4 - this is a debug message

可以看到其默认输出格式如上,包含时间、级别、模块名、行号以及日志信息。无需手动创建logger,直接使用即可,且其输出为彩色,视觉效果更加友好。

上述日志信息直接输出至控制台,未输出至其他位置。若希望输出至其他位置,例如保存为文件,只需使用一行代码声明即可。

例如,将结果同时输出至runtime.log文件,可如此编写:

from loguru import logger

logger.add('runtime.log')
logger.debug('this is a debug')

非常简单,无需再声明FileHandler,仅一行add语句即可搞定。运行后发现,目录下的runtime.log文件中同样出现了控制台输出的DEBUG信息。

上述仅为基本使用,但这还远远不够,接下来详细介绍其部分功能模块。

详细使用

既然是日志,最常见的场景便是输出至文件。loguru对输出至文件的配置提供了强大支持,例如支持输出至多个文件、按级别分别输出、文件过大时自动创建新文件、过期自动删除等。

接下来分别介绍这些功能的实现方式,主要涉及add方法的使用。因为add方法相当于为logger添加了一个Handler,它暴露了许多参数以实现Handler的配置,下面将详细介绍。

首先查看其方法定义:

def add(
        self,
        sink,
        *,
        level=_defaults.LOGURU_LEVEL,
        format=_defaults.LOGURU_FORMAT,
        filter=_defaults.LOGURU_FILTER,
        colorize=_defaults.LOGURU_COLORIZE,
        serialize=_defaults.LOGURU_SERIALIZE,
        backtrace=_defaults.LOGURU_BACKTRACE,
        diagnose=_defaults.LOGURU_DIAGNOSE,
        enqueue=_defaults.LOGURU_ENQUEUE,
        catch=_defaults.LOGURU_CATCH,
        **kwargs
    ):
    pass

查看其源代码,它支持众多参数,如level、format、filter、color等。

sink

此外,我们还注意到一个非常重要的参数sink。查阅官方文档可知,通过sink可传入多种不同的数据结构,汇总如下:

• sink可传入一个file对象,例如sys.stderr或open('file.log', 'w')均可。

• sink可直接传入一个str字符串或pathlib.Path对象,即代表文件路径。若识别到此类类型,它会自动创建对应路径的日志文件并将日志输出其中。

• sink可以是一个方法,允许自行定义输出实现。

• sink可以是一个logging模块的Handler,如FileHandler、StreamHandler等,或者上文提到的CMRESHandler亦可,从而实现自定义Handler的配置。

• sink还可以是一个自定义类,具体实现规范可参见官方文档。

因此,前文演示的输出至文件,仅向其传入了一个str字符串路径,它便为我们创建了一个日志文件,原理即在此。

format、filter、level

接下来了解其其他参数,例如format、filter、level等。

其实它们的概念和格式与logging模块基本一致,例如此处使用format、filter、level来规定输出格式:

logger.add('runtime.log', format="{time} {level} {message}", filter="my_module", level="INFO")

删除 sink

此外,添加sink之后也可对其进行删除,相当于重新刷新并写入新内容。

删除时,根据add方法返回的id进行删除即可,参考以下示例:

from loguru import logger

trace = logger.add('runtime.log')
logger.debug('this is a debug message')
logger.remove(trace)
logger.debug('this is another debug message')

此处,我们首先add了一个sink,然后获取其返回值,赋值为trace。随后输出一条日志,接着将trace变量传给remove方法,再次输出一条日志,观察结果如何。

控制台输出如下:

2019-10-13 23:18:26.469 | DEBUG | __main__:<module>:4 - this is a debug message 2019-10-13 23:18:26.469 | DEBUG | __main__:<module>:6 - this is another debug message

日志文件runtime.log内容如下:

2019-10-13 23:18:26.469 | DEBUG | __main__:<module>:4 - this is a debug message

可以发现,在调用remove方法之后,确实将历史log删除了。

这样我们便可实现日志的刷新与重新写入操作。

rotation 配置

使用loguru还可非常方便地使用rotation配置。例如,若希望每天输出一个日志文件,或文件过大时自动分隔日志文件,可直接使用add方法的rotation参数进行配置。

参考以下示例:

logger.add('runtime_{time}.log', rotation="500 MB")

通过此类配置,即可实现每500MB存储一个文件,每个log文件过大便会新创建一个log文件。在配置log名字时加上了time占位符,这样在生成时可自动将时间替换进去,生成一个文件名包含时间的log文件。

此外,也可使用rotation参数实现定时创建log文件,例如:

logger.add('runtime_{time}.log', rotation='00:00')

如此便可实现每天0点新创建一个log文件输出。

此外,也可配置log文件的循环时间,例如每隔一周创建一个log文件,写法如下:

logger.add('runtime_{time}.log', rotation='1 week')

如此便可实现一周创建一个log文件。

retention 配置

许多情况下,非常久远的log对我们已无用处,它们白白占据存储空间,不清除便会造成浪费。retention参数可配置日志的最长保留时间。

例如,若希望设置日志文件最长保留10天,可如此配置:

logger.add('runtime.log', retention='10 days')

如此log文件内便会保留最新10天的log,无需再担心log沉积问题。

compression 配置

loguru还可配置文件的压缩格式,例如使用zip文件格式保存,示例如下:

logger.add('runtime.log', compression='zip')

如此可进一步节省存储空间。

字符串格式化

loguru在输出log时还提供了非常友好的字符串格式化功能,如下所示:

logger.info('If you are using Python {}, prefer {feature} of course!', 3.6, feature='f-strings')

如此在添加参数时便十分方便。

Traceback 记录

在许多情况下,若遇到运行错误,而打印输出log时未配置好Traceback的输出,很可能无法追踪错误所在。

但使用loguru后,利用其提供的装饰器即可直接进行Traceback的记录,类似如下配置:

@logger.catch
def my_function(x, y, z):
    # An error? It's caught anyway!
    return 1 / (x + y + z)

我们进行测试,在调用时三个参数均传入0,直接引发除以0的错误,观察结果:

my_function(0, 0, 0)运行完毕后,可以发现log中出现了Traceback信息,且输出了当时的变量值,效果极佳!结果如下:

> File "run.py", line 15, in <module> my_function(0, 0, 0) └ <function my_function at 0x1171dd510> File "/private/var/py/logurutest/demo5.py", line 13, in my_function return 1 / (x + y + z) │ │ └ 0 │ └ 0 └ 0 ZeroDivisionError: division by zero

因此,使用loguru可非常方便地实现日志追踪,debug效率或许能提高十倍?

以上为Python中更优雅的日志记录方案详解的详细内容,更多关于Python日志记录的资料请关注本站其它相关文章!

相关标签:

本站声明

本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。

Python 相关产品和应用

全部下载

专题列表