logging:我的日志管理模板
loguru简介
回顾logging模块及我的日志管理
在前面的文章中介绍了日志管理logging,如果是复杂日志的管理或需求,需要配置一些 Handler、Formatter 来进行一些处理,比如把日志输出到不同的位置,或者设置一个不同的输出格式,或者设置日志分块和备份,配置起来可能相对比较繁琐
我的日志管理
在使用logging模块时,如果只是简单的日志记录,直接使用如下通用记录方式即可:
import logging
import sys
FORMAT = "%(asctime)s --> [%(name)s] %(levelname)s : %(message)s"
DATE_FORMAT = "%Y-%m-%d %H:%M:%S"
logging.basicConfig(level=logging.INFO, format=FORMAT, datefmt=DATE_FORMAT, filename="test.log")
logger = logging.getLogger(__name__)
fmt = logging.Formatter(fmt=FORMAT, datefmt=DATE_FORMAT)
console_handler = logging.StreamHandler(sys.stdout)
console_handler.setFormatter(fmt)
logger.addHandler(console_handler)
logger.info('test info message')
logger.error('test error message')
每个模块都加上上面这一堆,显然很冗余。进行封装是一个很好的习惯:
import logging
import sys
from os import makedirs
from os.path import dirname, exists
loggers = {}
LOG_ENABLED = True # 是否开启日志
LOG_TO_CONSOLE = True # 是否输出到控制台
LOG_TO_FILE = True # 是否输出到文件
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' # 日志默认输出格式
def get_logger(name=__name__, enable=LOG_ENABLED, level=LOG_LEVEL, fmt=LOG_FORMAT, log_to_file=LOG_TO_FILE,log_to_console=LOG_TO_CONSOLE, filename=LOG_PATH):
"""
get logger by name
:param name: logger名称
:param enable: 是否开启日志
:param level: 日志级别
:param fmt: 日志格式
:param log_to_file: 是否输出到文件
:param log_to_console: 是否输出到控制台
:param filename: 日志文件路径
:return: logger
"""
global loggers
if loggers.get(name):
return loggers.get(name)
logger = logging.getLogger(name)
logger.setLevel(level)
# 输出到控制台
if enable and log_to_console:
stream_handler = logging.StreamHandler(sys.stdout)
stream_handler.setLevel(level=level)
formatter = logging.Formatter(fmt)
stream_handler.setFormatter(formatter)
logger.addHandler(stream_handler)
# 输出到文件
if enable and log_to_file:
# 如果路径不存在,创建日志文件文件夹
log_dir = dirname(filename)
if not exists(log_dir):
makedirs(log_dir)
# 添加 FileHandler
file_handler = logging.FileHandler(filename, encoding='utf-8')
file_handler.setLevel(level=level)
formatter = logging.Formatter(fmt)
file_handler.setFormatter(formatter)
logger.addHandler(file_handler)
# 保存到全局 loggers
loggers[name] = logger
return logger
loggers 变量是一个全局字典,如果有已经声明过的 logger,直接将其获取返回即可,不用再将其二次初始化。如果 loggers 里面没有找到 name 对应的 logger,那就进行创建即可
loguru
接下来介绍一种更优雅更简单方便的日志管理库loguru
loguru官方文档
loguru安装
pip install loguru
loguru的使用
loguru使用初探
from loguru import logger
logger.debug('this is a debug message')
logger.info('this is a info message')
--------------输出结果------------
2021-01-23 16:35:03.020 | DEBUG | __main__:<module>:3 - this is a debug message
2021-01-23 16:35:03.020 | INFO | __main__:<module>:4 - this is a info message
通过上面的例子可以看出,loguru使用很简单,直接引入logger,然后直接调用即可。这个logger对象已经提前配置好一些基础信息:比较友好的格式及文本信息(包含颜色)
- 输出到文件
上面的例子只能输出到控制台,如果要输出到文件,也只要加如简单的一句声明即可
from loguru import logger
logger.add('test.log')
logger.debug('this is a debug message')
logger.info('this is a info message')
可见,不再需要像logging模块声明一个 FileHandler 了,添加一行 add 语句就行,运行之后发现控制台和当前目录下的test.log里出现了log信息,且格式一样
loguru的详细使用
add方法的参数
loguru 对输出到文件的配置有非常强大的支持,比如支持输出到多个文件,分级别分别输出,过大创建新文件,过久自动删除等等。 下面我们分别看看这些怎样来实现,这里基本上就是 add 方法的使用介绍。因为这个 add 方法就相当于给 logger 添加了一个 Handler,它给我们暴露了许多参数来实现 Handler 的配置
在ide中输入from loguru._logger import Logger
来查看源码,这里我们来重点看下add的相关参数
class Logger:
......
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
):
......
add 方法的sink参数
- sink 可以传入一个 file 对象,例如 sys.stderr 或者 open('file.log', 'w') 都可以
- sink 可以直接传入一个 str 字符串或者 pathlib.Path 对象,其实就是代表文件路径的,如果识别到是这种类型,它会自动创建对应路径的日志文件并将日志输出进去
- sink 可以是一个方法,可以自行定义输出实现
- sink 可以是一个 logging 模块的 Handler,比如 FileHandler、StreamHandler 等等,或者 CMRESHandler 也是可以的,这样就可以实现自定义 Handler 的配置
- sink 还可以是一个自定义的类,具体的实现规范可以参见官方文档
如上,我们就使用了一个字符串test.log,就能够给我们创建了一个日志文件。这里就不在举例
add 方法的 format、filter、level参数
其实它的概念和格式和 logging 模块都是基本一致,例如这里使用 format、filter、level 来规定输出的格式
from loguru import logger
logger.add('test.log', format="{time:YYYY-MM-DD HH:mm:ss} {level} {message}", filter="__main__", level="INFO")
logger.debug('this is a debug message')
logger.info('this is a info message')
删除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 方法设置rotation
用了 loguru 我们还可以非常方便地使用 rotation 配置,比如我们想一天输出一个日志文件,或者文件太大了自动分隔日志文件,我们可以直接使用 add 方法的 rotation 参数进行配置
- 按照文件大小分割日志
from loguru import logger
logger.add('runtime_{time}.log', rotation="500 MB")
logger.debug('this is a debug message')
每 500MB 存储一个文件,每个 log 文件过大就会新创建一个 log 文件。我们在配置 log 名字时加上了一个 time 占位符,这样在生成时可以自动将时间替换进去,生成一个文件名包含时间的 log 文件
- 按照时间分割日志
from loguru import logger
logger.add('runtime_{time}.log', rotation='00:00') # 每天 0 点新创建一个 log 文件
# logger.add('runtime_{time}.log', rotation='1 week') # 每隔一周创建一个 log 文件
logger.debug('this is a debug message')
add 方法设置retention
非常久远的 log 对我们来说并没有什么用处了,它白白占据了一些存储空间,不清除掉就会非常浪费。retention 这个参数可以配置日志的最长保留时间
logger.add('runtime.log', retention='10 days') # 设置日志文件最长保留 10 天
add 方法设置compression
还可以配置文件的压缩格式,比如使用 zip 文件格式保存
logger.add('runtime.log', compression='zip')
字符串格式化
import logging
from loguru import logger
logging.warning('this is {} message to {test}'.format('info', test='test'))
logger.info('this is {} message to {test}', 'info', test='test')
Traceback 记录
遇到运行错误, loguru提供的装饰器就可以直接进行 Traceback 的记录
from loguru import logger
logger.add('error.log')
@logger.catch
def func(x, y):
return 1 / x + y
func(0, 0)
运行后可以发现 log 里面就出现了 Traceback 信息,而且给我们输出了当时的变量值
如下:
2021-01-23 20:30:46.431 | ERROR | __main__:<module>:9 - An error has been caught in function '<module>', process 'MainProcess' (38838), thread 'MainThread' (4447514112):
Traceback (most recent call last):
> File "/Users/monkey/PycharmProjects/test/t1.py", line 9, in <module>
func(0, 0)
└ <function func at 0x102396158>
File "/Users/monkey/PycharmProjects/test/t1.py", line 7, in func
return 1 / x + y
│ └ 0
└ 0
ZeroDivisionError: division by zero