From 3c74ce6cb9835718c714508a14af8b35474df6b2 Mon Sep 17 00:00:00 2001 From: Valentin Deniaud Date: Wed, 12 Feb 2020 15:09:44 +0100 Subject: [PATCH] views: extend log search to responses (#39779) --- passerelle/views.py | 54 ++++++++++++++++++++++++++++--------------- tests/test_manager.py | 14 ++++++++++- 2 files changed, 48 insertions(+), 20 deletions(-) diff --git a/passerelle/views.py b/passerelle/views.py index c4d9f58f..5dd4eb01 100644 --- a/passerelle/views.py +++ b/passerelle/views.py @@ -26,6 +26,8 @@ from django.core.exceptions import PermissionDenied from django.contrib.auth import logout as auth_logout from django.contrib.auth import views as auth_views from django.db import transaction +from django.db.models import TextField +from django.db.models.functions import Cast from django.http import HttpResponse, HttpResponseRedirect, Http404 from django.views.decorators.csrf import csrf_exempt from django.views.generic import ( @@ -47,7 +49,7 @@ from jsonschema import validate, ValidationError from passerelle.base.models import BaseResource, ResourceLog from passerelle.compat import json_loads -from passerelle.utils.jsonresponse import APIError +from passerelle.utils.jsonresponse import APIError, JSONEncoder from passerelle.utils.json import unflatten from .utils import to_json, is_authorized @@ -241,7 +243,9 @@ class GenericViewLogsConnectorView(GenericConnectorMixin, ListView): try: date = date_parser.parse(query, dayfirst=True) except Exception: - qs = qs.filter(message__icontains=query) + qs = qs.annotate( + text_extra=Cast('extra', TextField()) + ).filter(text_extra__icontains=query) else: date = make_aware(date) if date.hour == 0 and date.minute == 0 and date.second == 0: @@ -428,15 +432,40 @@ class GenericEndpointView(GenericConnectorMixin, SingleObjectMixin, View): extra.append(key) raise WrongParameter(missing, extra) - # auto log request's inputs + payload = request.body # for logging, but we cannot access it after firing request + result = None + try: + params = self.get_params(request, *args, **kwargs) + if request.method == 'GET' and self.endpoint.endpoint_info.cache_duration: + cache_key = hashlib.md5( + force_bytes(repr(self.get_object().slug) + repr(self.endpoint) + repr(params)) + ).hexdigest() + result = cache.get(cache_key) + if result is not None: + return result + + result = self.endpoint(request, **params) + if request.method == 'GET' and self.endpoint.endpoint_info.cache_duration: + cache.set(cache_key, result, self.endpoint.endpoint_info.cache_duration) + return result + finally: + # auto log endpoint call + self.log_request(request, payload, result, kwargs) + + def log_request(self, request, payload, result, kwargs): connector_name, endpoint_name = kwargs['connector'], kwargs['endpoint'] connector = self.get_object() + payload = payload[:connector.logging_parameters.requests_max_size] url = request.get_full_path() - payload = request.body[:connector.logging_parameters.requests_max_size] try: payload = payload.decode('utf-8') except UnicodeDecodeError: payload = '' + try: + result = json.dumps(result, cls=JSONEncoder) + result = result[:connector.logging_parameters.responses_max_size] + except TypeError as e: + result = None connector.logger.info('endpoint %s %s (%r) ' % (request.method, url, payload), extra={ @@ -445,23 +474,10 @@ class GenericEndpointView(GenericConnectorMixin, SingleObjectMixin, View): 'connector_endpoint': endpoint_name, 'connector_endpoint_method': request.method, 'connector_endpoint_url': url, - 'connector_payload': payload + 'connector_payload': payload, + 'connector_response': result, }) - params = self.get_params(request, *args, **kwargs) - if request.method == 'GET' and self.endpoint.endpoint_info.cache_duration: - cache_key = hashlib.md5( - force_bytes(repr(self.get_object().slug) + repr(self.endpoint) + repr(params)) - ).hexdigest() - result = cache.get(cache_key) - if result is not None: - return result - - result = self.endpoint(request, **params) - if request.method == 'GET' and self.endpoint.endpoint_info.cache_duration: - cache.set(cache_key, result, self.endpoint.endpoint_info.cache_duration) - return result - def get(self, request, *args, **kwargs): if self.endpoint.endpoint_info.pattern: pattern = url(self.endpoint.endpoint_info.pattern, self.endpoint) diff --git a/tests/test_manager.py b/tests/test_manager.py index 01c81533..f113e145 100644 --- a/tests/test_manager.py +++ b/tests/test_manager.py @@ -138,7 +138,7 @@ def test_menu_json(app, admin_user): assert resp.content.startswith(b'FooBar([{"') def test_logs(app, admin_user): - data = StringIO('1;Foo\n2;Bar\n3;Baz') + data = StringIO('1;Foo\n2;Bar\n3;Baz\n4;Bat') csv = CsvDataSource.objects.create(csv_file=File(data, 't.csv'), columns_keynames='id, text', slug='test', title='a title', description='a description') @@ -193,6 +193,18 @@ def test_logs(app, admin_user): resp = app.get(base_url + log_pk + '/') resp = app.get(base_url + '12345' + '/', status=404) + app.get('/csvdatasource/test/query/fooba/?q=az') + resp = app.get(csv.get_absolute_url()) + assert 'endpoint GET /csvdatasource/test/query/fooba/?q=az' in resp.text + resp = resp.click('full page') + resp.form['q'] = 'Baz' + resp = resp.form.submit() + assert resp.text.count('') == 1 + resp.form['q'] = 'Bat' + resp = resp.form.submit() + assert resp.text.count('') == 0 + + def test_logging_parameters(app, admin_user): data = StringIO('1;Foo\n2;Bar\n3;Baz') csv = CsvDataSource.objects.create(csv_file=File(data, 't.csv'), -- 2.20.1