Log对于程序运行中异常产生后的分析以及程序运行状态的追踪都起到了十分重要的作用。
虽然Python内置了标准库 - logging用于日志的记录 ,但是在配置上较为繁琐。同时在多线程或多进程的场景下,若不进行特殊处理还会导致日志记录出现异常。
所以在这里我们介绍一款开箱即用的日志记录第三方库Loguru。
安装方法如下:
pip install loguru
安装后,我们可以直接通过导入loguru 封装好的logger 类的实例化对象,对不同级别的日志进行记录。
首先是loguru默认支持的所有级别:
from loguru import logger
logger.trace('级别名称:trace,严重值:5')
logger.debug('级别名称:debug,严重值:10')
logger.info('级别名称:info,严重值:20')
logger.success('级别名称:success,严重值:25')
logger.warning('级别名称:warning,严重值:30')
logger.error('级别名称:error,严重值:40')
logger.critical('级别名称:critical,严重值:50')
我们可以从效果图看到,在最终打印的日志中,每一条记录包含了时间戳,行数等信息,不同级别log的字体和背景颜色也做了特殊处理,可谓非常的贴心。
当我们需要将日志记录到文件中时,也非常方便,只需要在日志记录前添加下面这句代码即可,log信息就会保存在指定路径的'test_当前日期时间.log'中:
logger.add(r"C:\temp\test_{time}.log", enqueue=True)
logger.info('此信息会保存在以上log文件中。')
以下是add函数支持的参数选项,也是日志常用参数配置解析:
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 传入的是一个 file 对象,比如'xxxx.log',可以应用下列参数,对保存的文件做特殊处理。
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() 。
光看上面的参数及解释,显然有些生硬也不好理解,我们还是通过一系列的例子,来解释其应用场景。
范例1:有时候我们只需要log记录到文件,不需要打印输出log的功能,则可以做如下设定。
#logger 默认包含终端打印功能
logger.remove(handler_id=0) # 清除终端打印功能
logger.info("不打印,不保存") #什么都不会显示或记录
#添加文件保存
file_save = logger.add("test.log")
logger.info("文件保存")
这里简单解释下,logger实例化后默认的sink只有一个,为sys.stderr(屏幕打印),所以logger.remove(handler_id=0)的目的,就是将这个屏幕打印的sink删除,然后单独添加 file 对象。
范例2:有时候我们希望在记录串口log的时候每隔1小时,产生一个新的log文件,或者每达到100mb后产生一个新的log文件,因为这样做可以不会让记录的文件过大难以被打开。
# 清除之前所有的设置,包括终端打印的设置
logger.remove(handler_id=None)
cmd_disp = logger.add(sys.stderr) #添加终端打印
#当前logl达到100mb后,关闭当前日志文件并启动一个新文件
file_save = logger.add("test_{time}.log" , rotation="100 MB")
#or
#每天 0 点新创建一个 log 文件输出
file_save =logger.add('test_{time}.log', rotation='00:00')
#or
#每以个小时创建一个 log 文件输出
file_save =logger.add('test_{time}.log', rotation='1h')
范例3: 有时候我们希望将warning以及warning以上级别的log,既做屏幕打印也记录到文件中,warning以下的级别只打印不记录文件。
# 清除之前所有的设置,包括终端打印的设置
logger.remove(handler_id=None)
cmd_disp = logger.add(sys.stderr) #添加终端打印
file_save = logger.add("test.log" , level="WARNING") #添加文件保存,最低记录级别为WARNING
logger.info("级别太低,log未被写入文件")
logger.error("log写入文件")
范例4: 有时候我们希望运行程序时,在同一个py文件或多个py文件中同时产生不同模块的log,并根据分类将log信息保存在各自的log文件中。
比如,这里有一个程序有功能安全和诊断两个模块。我们可以通过logurn的filter功能来实现log文件相互独立记录的功能。
# 清除之前所有的设置,包括终端打印的设置
logger.remove(handler_id=None)
cmd_disp = logger.add(sys.stderr) #添加终端打印
#添加功能安全相关的log文件,并在filter中设置仅针对record name == "safety_func"的实例化对象。
safety_func_save= logger.add("safety_func.log",
level='INFO',
filter=lambda record: record["extra"].get("name") == "safety_func",
enqueue=True)
#添加诊断相关的log文件,并在filter中设置仅针对record name == "diag_func"的实例化对象。
diag_func_save=logger.add("diag_func.log",
level='INFO',
filter=lambda record: record["extra"].get("name") == "diag_func",
enqueue=True)
safety_func_log = logger.bind(name="safety_func")
diag_func_log = logger.bind(name="diag_func")
safety_func_log.info("打印+保存 safety_func.log")
diag_func_log .info("打印+保存 diag_func.log")
范例5: 针对exception, loguru也有专门的分类,且可以很方便的定位到引发错误的行。
# 清除之前所有的设置,包括终端打印的设置
logger.remove(handler_id=None)
cmd_disp = logger.add(sys.stderr) #添加终端打印
try:
if 想吃芝士条 == True:
print("就行动吧!")
except:
logger.exception("异常") #exception分类
执行后,显示的异常结果如下,方便定位问题点:
总结:loguru在易用性和显示效果等方面都超越了Python自带的logging模块,真正做到了一两行配置,就可以实现常用的log配置。
但其也有不足,那就是速度,目前loguru打印和保存log的速度相比python自带的print和logging都慢了很多,所以也建议如果有对性能要求较高的场景,尽量避免使用loguru的终端打印功能,以免其影响整个程序的执行速度。
另外,官网也意识到其速度上的略势,计划将在0.7.0版本中,使用C Python API重构部分核心功能,以提高loguru的执行速度。