Python日志记录进阶:从logging到loguru的优雅实践
本文深入解析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日志记录的资料请关注本站其它相关文章!
本站声明
本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。













