// 页数

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())

使用

import logging

logging.info("继续使用logging作为上层接口,不影响现有项目已有的日志记录方式")

附图一张。

  • db_debug.log
    在这里插入图片描述

  • error.log

在这里插入图片描述

  • info.log

在这里插入图片描述

点击此处转跳示例

posted @   黄大胆  阅读(128)  评论(0编辑  收藏  举报  
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
点击右上角即可分享
微信分享提示