日志模块logging和loguru的基本用法及封装

4,605 阅读4分钟

我正在参加「掘金·启航计划」

loguru

loguru Github: Python logging made (stupidly) simple (github.com)
loguru API Reference — loguru documentation

安装

pip install loguru

用法

loguru不用像logging那样做很多配置操作,直接从loguru导入logger即可,需要注意的是,此logger是经过实例化后的全局唯一logger对象,意味着即使创建多个logger对象最终也是写入到这个全局的logger中。

基本使用

from loguru import logger
logger.info("hello world")

详细用法

from loguru import logger

logger.add(
    sink=os.path.join(BASE_DIR, 'ssh/logs/service.log'),
    rotation='500 MB',                  # 日志文件最大限制500mb
    retention='30 days',                # 最长保留30天
    format="{time}|{level}|{message}",  # 日志显示格式
    compression="zip",                  # 压缩形式保存
    encoding='utf-8',                   # 编码
    level='DEBUG',                      # 日志级别
    enqueue=True,                       # 默认是线程安全的,enqueue=True使得多进程安全
)

logger.debug("详细调试信息")
logger.info("普通信息")
logger.success("成功信息")
logger.warning("警告信息")
logger.error("错误信息")
logger.trace("异常信息")
logger.critical("严重错误信息")

运行如下:

image.png

如果想捕获程序中的异常可使用logger.exception,此方法可以定位到具体变量值

try:
    a = 0
    b = 5/a
except ZeroDivisionError:
    logger.exception("ZeroDivisionError")
    
    
 # 装饰器形式
@logger.catch()
def test():
    a = 0
    b = 5/a
test()

image.png

add方法参数说明

参数含义
sink文件对象/字符串/Path对象/日志handler负责接收格式化的日志并输出到相应对象
format如:"{time} {level} {message}"自定义日志格式
rotation如:500 MB1 week00:00超过设定的规则上限就会创建另一份日志
retention1 week30 days2 months超过存储时间就会清空
levelINFODEBUGERROR日志级别
compression"zip""tar.gz""gz""tar"压缩格式(日志文件很大时可以节省空间)
serializebool类型为True表示将日志序列化成json格式

关于日志严重程度等级排序(copy的官方文档)

Level nameSeverity valueLogger method
TRACE5logger.trace()
DEBUG10logger.debug()
INFO20logger.info()
SUCCESS25logger.success()
WARNING30logger.warning()
ERROR40logger.error()
CRITICAL50logger.critical()

filter

现在有这样一个需求:我想把错误日志输出到error.log中,消息包含hello的输出到hello.log中,那么我们可以使用filter来过滤

from loguru import logger

logger.add(os.path.join(BASE_DIR, 'ssh/logs/service.log'), encoding="utf-8")
logger.add(os.path.join(BASE_DIR, 'ssh/logs/error.log'), encoding="utf-8", filter=lambda x: 'ERROR' in str(x['level']).upper())
logger.add(os.path.join(BASE_DIR, 'ssh/logs/hello.log'), encoding="utf-8", filter=lambda x: 'hello' in str(x['message']))

logger.debug("详细调试信息")
logger.error("错误信息")
logger.info("普通信息")
logger.error("hello world")

三个日志文件输出如下:

image.png

remove

删除之前添加的的handler,相当于刷新

from loguru import logger

logger.add(os.path.join(BASE_DIR, 'ssh/logs/service.log'), encoding="utf-8")
logger.add(os.path.join(BASE_DIR, 'ssh/logs/error.log'), encoding="utf-8", filter=lambda x: 'ERROR' in str(x['level']).upper())
lg_hello = logger.add(os.path.join(BASE_DIR, 'ssh/logs/hello.log'), encoding="utf-8", filter=lambda x: 'hello' in str(x['message']))

logger.debug("详细调试信息")
logger.error("错误信息")
logger.info("普通信息")
logger.error("hello world")

# 这里移除了hello handler
logger.remove(lg_hello)
logger.error("hello new world")

输出如下,移除hello那个handler之后,hello.log中没有输出hello new world,而error.log则有:

image.png

封装

from loguru import logger as lg

@singleton
class AppLogger:
    def __init__(self):
        self.app_logger = lg

    def set_logger(self, filename, filter_type=None, level='DEBUG'):
        """
        :param filename: 日志文件名
        :param filter_type: 日志过滤,如:将日志级别为ERROR的单独记录到一个文件中
        :param level: 日志级别设置
        :return:
        """

        dic = dict(
            sink=self.get_log_path(filename),
            rotation='500 MB',
            retention='30 days',
            format="{time}|{level}|{message}",
            encoding='utf-8',
            level=level,
            enqueue=True,
        )
        if filter_type:
            dic["filter"] = lambda x: filter_type in str(x['level']).upper()
        self.app_logger.add(**dic)
        return self.app_logger

    @property
    def get_logger(self):
        return self.app_logger
    
    @staticmethod
    def get_log_path(filename):
        log_path = os.path.join(BASE_LOG_DIR, filename)
        return log_path

    def trace(self, msg):
        self.app_logger.trace(msg)

    def debug(self, msg):
        self.app_logger.debug(msg)

    def info(self, msg):
        self.app_logger.info(msg)

    def success(self, msg):
        self.app_logger.success(msg)

    def warning(self, msg):
        self.app_logger.warning(msg)

    def error(self, msg):
        self.app_logger.error(msg)

    def critical(self, msg):
        self.app_logger.critical(msg)




logger = AppLogger()
logger.set_logger('error.log', filter_type='ERROR')
logger.set_logger('service.log', filter_type='INFO', level='INFO')

logging

logging是python的标准库,主要方法有info()debug()warning()error()critical() ,默认多线程安全,若要保证多进程安全,可以使用concurrent_log_handler.ConcurrentRotatingFileHandler

相比较于loguru,其配置项较多,支持更细化的配置,但输出没有颜色标识,所以综合来说使用loguru效率更高,输出样式也优于logging。项目中两个都有用到,一般用loguru记录系统中每个用户的操作日志,每个业务app用logging记录日志。

class Loggings:

    def __init__(self, log_name, level=logging.DEBUG):
        """
        :param log_name: 日志文件名称
        :param level: 日志级别
        """

        log_path = os.path.join(BASE_LOG_DIR, log_name)

        self.logger = logging.getLogger(__name__)  # 指定输出日志的程序名
        self.logger.setLevel(level)    #设定全局的日志级别

        # 添加日志文件handler,用于输出日志到文件中
        # file_handler = logging.FileHandler(filename='log.log', encoding='UTF-8')

        # 按时间分割日志,此处为30天生成一个新的日志文件,最多保留5个
        console_handler = logging.StreamHandler()
        file_handler = logging.handlers.TimedRotatingFileHandler(log_path,
                                                                 when='D',
                                                                 interval=30,
                                                                 backupCount=5,     # 保留日志个数
                                                                 encoding="utf-8")
        file_handler.suffix = "%Y_%m_%d.log"    # 分割的日志后缀名

        # 给logger添加handler
        if not self.logger.handlers:
            self.logger.addHandler(console_handler)
            self.logger.addHandler(file_handler)
            formatter = logging.Formatter(
                '%(asctime)s | %(levelname)s | %(thread)d | %(name)s | %(filename)s | %(funcName)s | %(message)s')
            console_handler.setFormatter(formatter)
            file_handler.setFormatter(formatter)

    def get_logger(self):
        return self.logger