如何在 django 应用程序中自动识别 n+1 个查询(二

问题:

  • 1、日志输出在控制台
  • 2、从输出的日志中并不能确定具体是哪一个接口出现的问题

改进:

  • 1、将日志保存在文件中,方便我们定期检查哪些接口问题存在问题
  • 2、修改日志格式,将当前的url拼接在日志格式中,便于我们定位问题
1
2
3
4
csharp复制代码格式:
Potential n+1 query detected on `<model>.<field>`
输出:
2021-11-29 11:20:24,515;WARNING;notifiers.py:40;Potential n+1 query detected on `OutboundOrder.created_by`

结果:

f1e81d2f629d68b91c9801ada9a4fde.png

创建nplusone.py

1、重新定义日志格式

将当前接口的url拼接在日志格式上

1
2
3
4
5
6
7
8
ini复制代码class LazyLoadMessage2(Message2):
    label = 'n_plus_one'
    formatter = '{url}:Potential n+1 query detected on `{model}.{field}`'


class EagerLoadMessage2(Message2):
    label = 'unused_eager_load'
    formatter = '{url}:Potential unnecessary eager load detected on `{model}.{field}`'

2、重写Message

将url拼接在日志中

1
2
3
4
5
6
7
8
9
10
11
12
13
python复制代码class Message2(Message):
    def __init__(self, model, field, url):
        self.url = url
        super(Message2, self).__init__(model, field)

    @property
    def message(self):
        return self.formatter.format(
            label=self.label,
            model=self.model.__name__,
            field=self.field,
            url=self.url
        )

3、重写监听函数

setup方法新增request参数,handle_lazy中将当前接口路径传给对应的Message

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
python复制代码class LazyListener2(LazyListener):
    def setup(self, **kwargs):
        self.request = kwargs.get('request', None)
        super(LazyListener2, self).setup()

    def handle_lazy(self, caller, args=None, kwargs=None, context=None, ret=None,
                    parser=None):
        model, instance, field = parser(args, kwargs, context)
        if instance in self.loaded and instance not in self.ignore:
            url = self.request.path if self.request else '' 
            message = LazyLoadMessage2(model, field, url)
            self.parent.notify(message)


class EagerListener2(EagerListener):
    def setup(self, **kwargs):
        self.request = kwargs.get('request', None)
        super(EagerListener2, self).setup()

    def log_eager(self):
        self.tracker.prune([each for each in self.touched if each])
        for model, field in self.tracker.unused:
            url = self.request.path if self.request else ''
            message = EagerLoadMessage2(model, field, url)
            self.parent.notify(message)


listeners2 = {
    'lazy_load': LazyListener2,
    'eager_load': EagerListener2,
}

4、重写中间件NPlusOneMiddleware

这里我们只需要将process_request方法中的listeners.listeners改成我们上面重写的listeners2

1
2
3
4
5
6
7
8
ruby复制代码class NPlusOneMiddleware2(NPlusOneMiddleware):

    def process_request(self, request):
        self.load_config()
        self.listeners[request] = self.listeners.get(request, {})
        for name, listener_type in six.iteritems(listeners2):
            self.listeners[request][name] = listener_type(self)
            self.listeners[request][name].setup(request=request)

5、修改settings

重新导入中间件和日志输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
arduino复制代码MIDDLEWARE = (
    'common.nplusone.NPlusOneMiddleware2',
    ...
)

LOGGING = {
    ...
    'handlers': {
        ...
        'nplusone': {
            'level': 'DEBUG',
            'class': 'logging.handlers.TimedRotatingFileHandler',
            'filename': LOG_DIR / 'nplusone.log',
            'formatter': 'standard'
        }
    },
    'loggers': {
        ...
        'nplusone': {
            'handlers': ['nplusone'],
            'level': 'WARN',
        },
    }
}

扫码_搜索联合传播样式-标准色版.png

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

0%