Django 我有一个中间件,我想记录每个请求/响应。如何访问POST数据?

Django 我有一个中间件,我想记录每个请求/响应。如何访问POST数据?,django,rest,Django,Rest,我有这个中间件 import logging request_logger = logging.getLogger('api.request.logger') class LoggingMiddleware(object): def process_response(self, request, response): request_logger.log(logging.DEBUG, "GET: {}. POST: {} respon

我有这个中间件

import logging

request_logger = logging.getLogger('api.request.logger')


class LoggingMiddleware(object):

    def process_response(self, request, response):
        request_logger.log(logging.DEBUG,
               "GET: {}. POST: {} response code: {}. response "
               "content: {}".format(request.GET, request.DATA,
                                       response.status_code,
                                       response.content))
        return response
问题是进程中的请求\响应方法没有.POST或.DATA或.body。我使用的是django rest框架,我的请求的内容类型为:application/json


请注意,如果我将日志记录放在process_请求方法中,它有.body和我需要的所有内容。但是,我需要在单个日志条目中同时包含请求和响应。

这就像访问表单数据来创建新表单一样

为此,您必须使用
request.POST
(也许
request.FILES
也是您需要记录的内容)


请参阅以获取请求属性。

这是我制定的完整解决方案

"""
Api middleware module
"""
import logging

request_logger = logging.getLogger('api.request.logger')


class LoggingMiddleware(object):
    """
    Provides full logging of requests and responses
    """
    _initial_http_body = None

    def process_request(self, request):
        self._initial_http_body = request.body # this requires because for some reasons there is no way to access request.body in the 'process_response' method.


    def process_response(self, request, response):
        """
        Adding request and response logging
        """
        if request.path.startswith('/api/') and \
                (request.method == "POST" and
                         request.META.get('CONTENT_TYPE') == 'application/json'
                 or request.method == "GET"):
            request_logger.log(logging.DEBUG,
                               "GET: {}. body: {} response code: {}. "
                               "response "
                               "content: {}"
                               .format(request.GET, self._initial_http_body,
                                       response.status_code,
                                       response.content), extra={
                    'tags': {
                        'url': request.build_absolute_uri()
                    }
                })
        return response
注意,这个

'tags': {
    'url': request.build_absolute_uri()
}

将允许您在sentry中按url进行筛选

令人沮丧和惊讶的是,Django中没有易于使用的请求日志记录包

所以我自己创造了一个。请查看:

使用日志系统,因此易于配置。这是调试级别的结果:

GET/POST request url
POST BODY if any
GET/POST request url - response code
Response body

Andrey的解决方案将在并发请求时中断。您需要将主体存储在请求范围中的某个位置,并在
process\u response()中获取它。


以上所有答案都有一个潜在的问题——传递给服务器的大request.body。在Django中,request.body是一个属性。(来自框架)

Django框架仅在一种情况下直接访问主体。(来自框架)

如您所见,属性体将整个请求读取到内存中。因此,您的服务器可能会崩溃。此外,它还容易受到拒绝服务攻击。 在本例中,我建议使用HttpRequest类的另一个方法。(来自框架)

所以,你不再需要这样做了

def process_request(self, request):
    request._body_to_log = request.body
您可以简单地执行以下操作:

def process_response(self, request, response):

    msg = "method=%s path=%s status=%s request.body=%s response.body=%s"
    args = (request.method,
            request.path,
            response.status_code,
            request.readlines(),
            response.content)

    request_logger.info(msg, *args)

    return response

编辑:使用request.readlines()的这种方法存在问题。有时它不会记录任何内容。

还要注意,
response.content
返回的是testring而不是unicode字符串,因此如果需要打印unicode,则需要调用
response.content.decode(“utf-8”)
您无法访问
request.POST
(或等效的
request.body
)在
过程\u响应中
中间件的一部分。下面是一个提出问题的例子。尽管您可以在
过程请求部分使用它。前面的答案给出了一个基于类的中间件。Django 2.0+和3.0+允许基于函数的中间件

from.models导入RequestData#存储所有请求数据的模型
def请求中间件(获取_响应):
#一次性配置和初始化。
def中间件(请求):
#之前为每个请求执行的代码
#该视图(以及更高版本的中间件)被称为。
try:metadata=request.META;
除外:元数据='无数据'
try:data=request.body;
除外:数据='无数据'
try:u=str(request.user)
除外:u='nouser'
响应=获取响应(请求)
w=RequestData.objects.create(userdata=u,metadata=metadata,data=data)
w、 保存()
返回响应
返回中间件
模型
RequestData
如下所示-


class RequestData(models.Model):
    time = models.DateTimeField(auto_now_add=True)
    userdata = models.CharField(max_length=10000, default=' ')
    data = models.CharField(max_length=20000, default=' ')  
    metadata = models.CharField(max_length=20000, default=' ')  

使用request.POSTinstead@LuisMasuelli不,它是空的。您可以分配
request.OLD\u POST=request.POST
process\u request
中,然后读取
request.OLD\u POST
process\u response
中,用您实施的解决方案写一个答案:p。有一天我或其他人会遇到这个问题。在这个解决方案中,任何请求都会覆盖\u initial\u http\u body,因此并发请求将引用同一个self。\u initial\u http\u body对象,属于稍后传入的请求的一个。当请求包含文件时会中断。我认为只有
self.\u initial\u http\u body
def process\u请求(self,request):
函数内就足够了。因为当您将
\u initial\u http\u body=None
放入类中时,实际上是将此变量设置为
类变量,而不是
实例变量
,我认为这将破坏任何试图读取request.body的视图,出现以下错误:RawPostDataException:在读取请求的数据流后无法访问正文我对此表示怀疑。中间件没有在process\u请求中读取主体,因此如果它中断,则应该在view.post之后在process\u响应中。那不是真的吗?
elif self.META.get('CONTENT_TYPE', '').startswith('application/x-www-form-urlencoded'):
def readlines(self):
    return list(iter(self))
def process_request(self, request):
    request._body_to_log = request.body
def process_response(self, request, response):

    msg = "method=%s path=%s status=%s request.body=%s response.body=%s"
    args = (request.method,
            request.path,
            response.status_code,
            request.readlines(),
            response.content)

    request_logger.info(msg, *args)

    return response

class RequestData(models.Model):
    time = models.DateTimeField(auto_now_add=True)
    userdata = models.CharField(max_length=10000, default=' ')
    data = models.CharField(max_length=20000, default=' ')  
    metadata = models.CharField(max_length=20000, default=' ')