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