python的日志管理模块可以用自带的logging模块,也可以用第三方的Loguru模块,关于logging和loguru模块的简单使用可以参考以下文章,写的还是不错的:logging 和 loguru的使用
关于logging模块的详细使用,可以参考这篇文章:logging模块详细使用。
本片文章只记录loguru模块的使用,包括简单的用法,以及在多模块和多线程下的使用。
pip\pip3 install loguru
Use out of box without boilerplate。
loguru的用法非常简单,在 loguru 中有且仅有一个对象:logger。为了使用方便,logger在使用时,是提前配置好的,并且开始是默认输出至stderr(但是这些完全是可以再进行配置的),而且打印出的log信息默认是配置了颜色的。如下所示,loguru的使用真的是非常简单:
from loguru import logger
logger.debug("This's a log message")
上面的日志记录语句,默认向stderr(控制台)打印了一条输出语句,输出结果如下所示:
可以看出,loguru默认配置了一套日志输出格式,有时间、级别、模块名、行号以及日志信息,不需要手动创建 logger,直接使用即可,另外其输出还是彩色的,看起来会更加友好。所以我们不需要提前配置什么,直接用就可以。
No Handler、no Formatter、no Filter:one function to rule them all。
在使用logging模块是,我们需要手动配置Handler、Formatter以及Filter,需要调用不同的函数进行配置,但是在loguru中,只需要一个add()函数即可。通过add()函数,我们可以设置Handler,设置Formatter,Filter Message以及设置Level。使用示例:
import sys
from loguru import logger
logger.add(sys.stderr, format="{time} {level} {message}", filter="my_module", level="INFO")
logger.debug("This's a new log message")
在上述代码中,通过add()函数指明了Handler为控制台输出,指定了format的格式,以及filter和level。然后就可以输出日志信息了:
add()函数原型定义如下:
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
里面有好多参数可以用来配置不同的属性。 这里面有一个非常重要的参数sink。通过 sink 我们可以传入多种不同的数据结构,汇总如下:
sys.stderr
或者 open('file.log', 'w')
都可以。str
字符串或者 pathlib.Path
对象,其实就是代表文件路径的,如果识别到是这种类型,它会自动创建对应路径的日志文件并将日志输出进去。
Easier file logging with rotation / retention / compression。
我们可以通过传入一个文件名字符串或者文件路径,loguru就会自动创建一个日志文件,如下所示:
from loguru import logger
logger.add("runtime.log") # 创建了一个文件名为runtime的log文件
logger.debug("This's a log message in file")
上面的程序就会在程序文件所在目录下创建一个名为runtime.log的文件,而且会在文件中记录日志:
同时也会在控制台输出日志信息:
如果不想在控制台也输出日志信息,因为logger是默认输出至stderr的,所以只需要在之前把它给remove掉就好了:
from loguru import logger
logger.remove(handler_id=None)
logger.add("runtime.log") # 创建了一个文件名为runtime的log文件
logger.debug("This's a log message in file")
这样就不会在控制台输出日志信息了。
在add()创建日志文件的时候,可以通过添加占位符的方式添加文件的日期,如下所示:
from loguru import logger
logger.add("runtime_{time}.log") # 创建了一个文件名为runtime的log文件
logger.debug("This's a log message in file")
这样就会创建一个带有日期的log文件。
通过配置rotation参数,指定日志文件滚动记录的条件,如下所示:
1)、
logger.add("file_1.log", rotation="500 MB") # Automatically rotate too big file
通过这样的配置我们就可以实现每 500MB 存储一个文件,每个 log 文件过大就会新创建一个新的 log 文件。我们可以在创建文件的时候加一个(time)占位符,这样在生成时可以自动将时间替换进去,生成一个文件名包含时间的 log 文件。
2)、
logger.add("file_2.log", rotation="12:00") # New file is created each day at noon
通过上面的配置,可以实现没填中午12:00创建一个log文件输出了。
3)、
logger.add("file_3.log", rotation="1 week") # Once the file is too old, it's rotated
通过上面的配置可以实现每隔1周创建一个新的log文件输出了。
通过配置retention参数,可以指定日志的保留时长:
logger.add("file_X.log", retention="10 days") # Cleanup after some time
通过上面的配置,就可以指定日志最多保留10天,每隔10天之后就会清理旧的日志,这样就不会造成内存浪费。
通过配置compression参数可以指定日志文件的压缩格式:
logger.add("file_Y.log", compression="zip") # Save some loved space
通过上面的配置,可以指定日志文件的压缩格式为zip格式,可以节省存储空间。
Exceptions catching within threads or main。
最让我感到loguru模块功能强大的地方就是它的异常捕获功能。如果程序在运行过程出现崩溃或错误,记录日志是我们回溯程序执行过程的一个重要方式,但是很多时候,根据日志并不知道程序为什么出错或者看不出来程序具体出错在哪些地方,此时如果我们能在日志中记录当异常发生时的情况或者信息,那么多我们解决程序问题来说,简直事半功倍。
在loguru模块中,异常的捕获有两种方式:
通过catch装饰器的方式实现异常捕获:
from loguru import logger
logger.add("runtime.log")
@logger.catch
def my_function(x, y, z):
return 1 / (x + y + z) # An error? It's caught anyway!
my_function(0, 0, 0)
上面的代码中,通过catch装饰器对函数my_function进行装饰,这样,当该函数出现异常时,就会打印出异常日志信息,如下所示:
在日志信息中,不仅指明了异常出现的地方,而且把参数的值也记录下来了。
通过exception方法也可以实现异常的捕获与记录:
from loguru import logger
logger.add("runtime.log")
def my_function1(x, y, z):
try:
return 1 / (x + y + z)
except ZeroDivisionError:
logger.exception("What?!")
my_function1(0, 0, 0)
记录的日志信息如下所示:
Asynchronous, Thread-safe, Multiprocess-safe。
由于在 loguru 中有且仅有一个对象:logger。所以loguru是可以在多块module文件中使用的,而且不会出现冲突:
exceptions_catching2_03.py:
from loguru import logger
def func(a, b):
logger.info("Process func")
return a / b
def nested(c):
try:
func(5, c)
except ZeroDivisionError:
logger.exception("What?!")
test.py:
#coding:utf-8
from loguru import logger
import exceptions_catching2_03 as ec3
if __name__=='__main__':
logger.add("run.log")
logger.info("Start!")
ec3.nested(0)
logger.info("End!")
运行结果如下所示:
Asynchronous, Thread-safe, Multiprocess-safe
所有添加至logger的sink默认都是线程安全的,所以loguru也可以很安全的在多线程的情形下使用:
#coding:utf-8
from atexit import register
from random import randrange
from threading import Thread, Lock, current_thread
from time import sleep, ctime
from loguru import logger
class CleanOutputSet(set):
def __str__(self):
return ','.join(x for x in self)
lock = Lock()
loops = (randrange(2, 5) for x in range(randrange(3, 7)))
remaining = CleanOutputSet()
def loop(nsec):
myname = current_thread().name
logger.info("Startted {}", myname)
'''
锁的申请和释放交给with上下文管理器
'''
with lock:
remaining.add(myname)
sleep(nsec)
logger.info("Completed {} ({} secs)", myname, nsec)
with lock:
remaining.remove(myname)
logger.info("Remaining:{}", (remaining or 'NONE'))
'''
_main()函数是一个特殊的函数,只有这个模块从命令行直接运行时才会执行该函数(不能被其他模块导入)
'''
def _main():
for pause in loops:
Thread(target=loop, args=(pause,)).start()
'''
这个函数(装饰器的方式)会在python解释器中注册一个退出函数,也就是说,他会在脚本退出之前请求调用这个特殊函数
'''
@register
def _atexit():
logger.info("All Thread DONE!")
logger.info("\n===========================================================================\n")
if __name__=='__main__':
logger.add("run.log")
_main()
日志记录文件如下所示:
上面代码创建了3个线程,每个线程都正确打印出了日志信息。