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

参考