web-dev-qa-db-fra.com

J'ai un middleware où je veux enregistrer chaque demande/réponse. Comment puis-je accéder à POST Les données?

J'ai ce middleware

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

Le problème est que la requête dans la méthode process_response n'a ni .POST, ni .DATA, ni .body. J'utilise Django-rest-framework et mes requêtes ont Content-Type: application/json

Notez que si je mets la journalisation à la méthode process_request - elle a .body et tout ce dont j'ai besoin. Cependant, j'ai besoin à la fois d'une requête et d'une réponse dans une seule entrée de journal.

19
Andrey Zarubin

Voici la solution complète que j'ai faite

"""
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

Notez, cela

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

vous permettra de filtrer par url dans sentinelle.

10
Andrey Zarubin

La solution d'Andrey va casser sur les demandes concurrentes. Vous devez stocker le corps quelque part dans la portée de la demande et le récupérer dans la process_response().

class RequestLoggerMiddleware(object):

    def process_request(self, request):
        request._body_to_log = request.body

    def process_response(self, request, response):
        if not hasattr(request, '_body_to_log'):
            return response

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

        request_logger.info(msg, *args)

        return response
5
siavach

Toutes les réponses ci-dessus ont un problème potentiel - gros request.body transmis au serveur. Dans Django request.body est une propriété. (du cadre)

@property
def body(self):
    if not hasattr(self, '_body'):
        if self._read_started:
            raise RawPostDataException("You cannot access body after reading from request's data stream")
        try:
            self._body = self.read()
        except IOError as e:
            six.reraise(UnreadablePostError, UnreadablePostError(*e.args), sys.exc_info()[2])
        self._stream = BytesIO(self._body)
    return self._body

Django framework d'accès au corps directement que dans un cas. (du cadre)

Elif self.META.get('CONTENT_TYPE', '').startswith('application/x-www-form-urlencoded'):

Comme vous pouvez le constater, le corps de la propriété lit l'intégralité de la demande en mémoire. En conséquence, votre serveur peut simplement tomber en panne. De plus, il devient vulnérable aux attaques par déni de service. Dans ce cas, je suggérerais d'utiliser une autre méthode de la classe HttpRequest. (du cadre)

def readlines(self):
    return list(iter(self))

Donc, vous n'avez plus besoin de le faire

def process_request(self, request):
    request._body_to_log = request.body

vous pouvez simplement faire:

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

EDIT: cette approche avec request.readlines () a des problèmes. Parfois, il n'enregistre rien.

3
vwvolodya

Il est frustrant et surprenant de constater qu’il n’existe pas de paquet de journalisation des demandes facile à utiliser dans Django.

Alors j'en ai créé un moi-même. Découvrez-le: https://github.com/rhumbixsf/Django-request-logging.git

Utilise le système de journalisation, il est donc facile à configurer. Voici ce que vous obtenez avec le niveau DEBUG:

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

C'est comme accéder aux données du formulaire pour créer un nouveau formulaire.

Vous devez utiliser request.POST pour cela (peut-être que request.FILES est quelque chose que vous voudriez également enregistrer).

class LoggingMiddleware(object):

    def process_response(self, request, response):
        request_logger.log(logging.DEBUG,
               "GET: {}. POST: {} response code: {}. response "
               "content: {}".format(request.GET, request.POST,
                                       response.status_code,
                                       response.content))
        return response

Voir Ici pour les propriétés de la demande.

1
Luis Masuelli

Notez également que response.content renvoie la chaîne bytestring et non unicode. Par conséquent, si vous devez imprimer unicode, vous devez appeler response.content.decode("utf-8").

0
K.H.