栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Python

Django如何使用loguru优雅地替换logging,并做到低侵入性

Python 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Django如何使用loguru优雅地替换logging,并做到低侵入性

场景简介

python的logging功能十分的强大,但火热的loguru同样令人神往。一个是原配,一个是小三,家花还是野花香?作为成熟的程序猿,我当然是全都要,怎么做到不惊动logging的情况下,使用loguru神不知鬼不觉地接管Django的后宫日志大权,就是穿着品如的衣服,还有着爱莉的骚气【手动滑稽】。

ps:此操作需要一定的编程基础,大佬请忽略。

  • 【低侵入性】不改动任何代码,还是使用logging的接口logging.info(),没有loguru包一样起飞
  • 【可读性】添加不同类别的日志文件分类(debug、error、sql_debug),妈妈再也不用担心我不会看日志了。

loguru简介

简介个屁

代码原理

就是构建一个handler代理,所有的logging日志,这个handler(你可以理解为品如的衣服)下,其实是loguru在emit日志(有种NTR的感觉),这样通过一些简单的配置就能实现上层接口不变的情况下,使用不同的日志管理器,除了loguru、colorlog、nb_log你甚至可以自己做一个,替换进去就可以。

  • settings.py
# 指定日志的目录所在,如果不存在则创建
LOG_ROOT = os.path.join(BASE_DIR, 'log')
if not os.path.exists(LOG_ROOT):
    os.mkdir(LOG_ROOT)

# 日志配置(基本跟原生的TimedRotatingFileHandler一样)
LOGGING = {
    'version': 1,
    'disable_existing_loggers': True,
    'formatters': {
        'standard': {
            'format': '[%(asctime)s] [%(filename)s:%(lineno)d] [%(module)s:%(funcName)s] '
                      '[%(levelname)s]- %(message)s'},
        'simple': {  # 简单格式
            'format': '%(levelname)s %(message)s'
        },
    },
    'handlers': {
        'servers': {
            'class': 'common.utils.log.InterceptTimedRotatingFileHandler',  # 这个路径看你本地放在哪里(下面的log文件)
            'filename': os.path.join(LOG_ROOT, 'srap.log'),
            'when': "D",
            'interval': 1,
            'backupCount': 1,
            'formatter': 'standard',
            'encoding': 'utf-8',
        },
        'db': {
            'class': 'common.utils.log.InterceptTimedRotatingFileHandler', # 这个路径看你本地放在哪里
            'filename': os.path.join(LOG_ROOT, 'srap_db.log'),
            'when': "D",
            'interval': 1,
            'backupCount': 1,
            'formatter': 'standard',
            'encoding': 'utf-8',
            'logging_levels': ['debug']  # 注意这里,这是自定义类多了一个参数,因为我只想让db日志有debug文件,所以我只看sql,这个可以自己设置
        }
    },
    'loggers': {
        # Django全局绑定
        'django': {
            'handlers': ['servers'],
            'propagate': True,
            'level': "INFO"
        },
        'celery': {
            'handlers': ['servers'],
            'propagate': False,
            'level': "INFO"
        },
        'django.db.backends': {
            'handlers': ['db'],
            'propagate': False,
            'level': "DEBUG"
        },
        'django.request': {
            'handlers': ['servers'],
            'propagate': False,
            'level': "DEBUG"
        },
    }
}
  • log.py
import logging
import os.path

from loguru import logger


# 1.️先声明一个类继承logging.Handler(制作一件品如的衣服)
class InterceptTimedRotatingFileHandler(logging.Handler):
    """
    自定义反射时间回滚日志记录器
    缺少命名空间
    """

    def __init__(self, filename, when='d', interval=1, backupCount=15, encoding="utf-8", delay=False, utc=False,
                 atTime=None, logging_levels="all"):
        super(InterceptTimedRotatingFileHandler, self).__init__()
        filename = os.path.abspath(filename)
        when = when.lower()
        # 2.️需要本地用不同的文件名做为不同日志的筛选器
        self.logger_ = logger.bind(sime=filename)
        self.filename = filename
        key_map = {
            'h': 'hour',
            'w': 'week',
            's': 'second',
            'm': 'minute',
            'd': 'day',
        }
        # 根据输入文件格式及时间回滚设立文件名称
        rotation = "%d %s" % (interval, key_map[when])
        retention = "%d %ss" % (backupCount, key_map[when])
        time_format = "{time:%Y-%m-%d_%H-%M-%S}"
        if when == "s":
            time_format = "{time:%Y-%m-%d_%H-%M-%S}"
        elif when == "m":
            time_format = "{time:%Y-%m-%d_%H-%M}"
        elif when == "h":
            time_format = "{time:%Y-%m-%d_%H}"
        elif when == "d":
            time_format = "{time:%Y-%m-%d}"
        elif when == "w":
            time_format = "{time:%Y-%m-%d}"
        level_keys = ["info"]
        # 3.️构建一个筛选器
        levels = {
            "debug": lambda x: "DEBUG" == x['level'].name.upper() and x['extra'].get('sime') == filename,
            "error": lambda x: "ERROR" == x['level'].name.upper() and x['extra'].get('sime') == filename,
            "info": lambda x: "INFO" == x['level'].name.upper() and x['extra'].get('sime') == filename,
            "warning": lambda x: "WARNING" == x['level'].name.upper() and x['extra'].get('sime') == filename}
        # 4. ️根据输出构建筛选器
        if isinstance(logging_levels, str):
            if logging_levels.lower() == "all":
                level_keys = levels.keys()
            elif logging_levels.lower() in levels:
                level_keys = [logging_levels]
        elif isinstance(logging_levels, (list, tuple)):
            level_keys = logging_levels
        for k, f in {_: levels[_] for _ in level_keys}.items():

            # 5.️为防止重复添加sink,而重复写入日志,需要判断是否已经装载了对应sink,防止其使用秘技:反复横跳。
            filename_fmt = filename.replace(".log", "_%s_%s.log" % (time_format, k))
            # noinspection PyUnresolvedReferences,PyProtectedMember
            file_key = {_._name: han_id for han_id, _ in self.logger_._core.handlers.items()}
            filename_fmt_key = "'{}'".format(filename_fmt)
            if filename_fmt_key in file_key:
                continue
                # self.logger_.remove(file_key[filename_fmt_key])
            self.logger_.add(
                filename_fmt,
                retention=retention,
                encoding=encoding,
                level=self.level,
                rotation=rotation,
                compression="tar.gz",  # 日志归档自行压缩文件
                delay=delay,
                enqueue=True,
                filter=f
            )

    def emit(self, record):
        try:
            level = self.logger_.level(record.levelname).name
        except ValueError:
            level = record.levelno

        frame, depth = logging.currentframe(), 2
        # 6.️把当前帧的栈深度回到发生异常的堆栈深度,不然就是当前帧发生异常而无法回溯
        while frame.f_code.co_filename == logging.__file__:
            frame = frame.f_back
            depth += 1
        self.logger_.opt(depth=depth, exception=record.exc_info).log(level, record.getMessage())

附图一张。

  • db_debug.log

  • error.log

  • info.log

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/1009500.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号