锐单电子商城 , 一站式电子元器件采购平台!
  • 电话:400-990-0325

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

时间:2023-08-31 11:07:01 568236传感器盒srap

场景简介

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

锐单商城拥有海量元器件数据手册IC替代型号,打造电子元器件IC百科大全!

相关文章