Projet

Général

Profil

0002-logging-add-max-sizes-to-connector-log-parameters-36.patch

Nicolas Roche, 25 octobre 2019 14:28

Télécharger (10,9 ko)

Voir les différences:

Subject: [PATCH 2/2] logging: add max sizes to connector log parameters
 (#36596)

 .../migrations/0016_auto_20191002_1443.py     | 25 +++++++++++++++++++
 passerelle/base/models.py                     | 10 ++++++++
 passerelle/base/views.py                      |  6 ++++-
 passerelle/settings.py                        |  2 +-
 passerelle/utils/__init__.py                  | 12 +++++++--
 passerelle/utils/jsonresponse.py              |  6 ++++-
 passerelle/views.py                           |  2 +-
 tests/settings.py                             |  2 ++
 tests/test_api_access.py                      | 12 ++++++---
 tests/test_requests.py                        |  7 ++++--
 10 files changed, 72 insertions(+), 12 deletions(-)
 create mode 100644 passerelle/base/migrations/0016_auto_20191002_1443.py
passerelle/base/migrations/0016_auto_20191002_1443.py
1
# -*- coding: utf-8 -*-
2
# Generated by Django 1.11.18 on 2019-10-02 12:43
3
from __future__ import unicode_literals
4

  
5
from django.db import migrations, models
6

  
7

  
8
class Migration(migrations.Migration):
9

  
10
    dependencies = [
11
        ('base', '0015_auto_20190921_0347'),
12
    ]
13

  
14
    operations = [
15
        migrations.AddField(
16
            model_name='loggingparameters',
17
            name='requests_max_size',
18
            field=models.PositiveIntegerField(default=5000, help_text='Maximum HTTP request size to log', verbose_name='Requests maximum size'),
19
        ),
20
        migrations.AddField(
21
            model_name='loggingparameters',
22
            name='responses_max_size',
23
            field=models.PositiveIntegerField(default=5000, help_text='Maximum HTTP reponse size to log', verbose_name='Responses maximum size'),
24
        ),
25
    ]
passerelle/base/models.py
605 605
        help_text=_('One address per line (empty for site administrators)'),
606 606
        blank=True
607 607
    )
608
    requests_max_size = models.PositiveIntegerField(
609
        verbose_name=_('Requests maximum size'),
610
        help_text=_('Maximum HTTP request size to log'),
611
        default=settings.LOGGED_REQUESTS_MAX_SIZE
612
    )
613
    responses_max_size = models.PositiveIntegerField(
614
        verbose_name=_('Responses maximum size'),
615
        help_text=_('Maximum HTTP reponse size to log'),
616
        default=settings.LOGGED_RESPONSES_MAX_SIZE
617
    )
608 618

  
609 619
    class Meta:
610 620
        unique_together = (('resource_type', 'resource_pk'))
passerelle/base/views.py
136 136
    def get_form_class(self):
137 137
        form_class = model_forms.modelform_factory(
138 138
            LoggingParameters,
139
            fields=['log_level', 'trace_emails'])
139
            fields=['log_level', 'trace_emails', 'requests_max_size', 'responses_max_size'])
140 140
        form_class.base_fields['trace_emails'].widget.attrs['rows'] = '3'
141 141
        return form_class
142 142

  
......
147 147
        parameters = self.get_resource().logging_parameters
148 148
        d['log_level'] = parameters.log_level
149 149
        d['trace_emails'] = parameters.trace_emails
150
        d['requests_max_size'] = parameters.requests_max_size
151
        d['responses_max_size'] = parameters.responses_max_size
150 152
        return d
151 153

  
152 154
    def get_resource(self):
......
160 162
        parameters = self.get_resource().logging_parameters
161 163
        parameters.log_level = form.cleaned_data['log_level']
162 164
        parameters.trace_emails = form.cleaned_data['trace_emails']
165
        parameters.requests_max_size = form.cleaned_data['requests_max_size']
166
        parameters.responses_max_size = form.cleaned_data['responses_max_size']
163 167
        parameters.save()
164 168
        return super(LoggingParametersUpdateView, self).form_valid(form)
165 169

  
passerelle/settings.py
209 209
)
210 210

  
211 211
# Max size of the response to log
212
LOGGED_RESPONSES_MAX_SIZE = 4096
212
LOGGED_RESPONSES_MAX_SIZE = 5000
213 213

  
214 214
# Max size of the request to log
215 215
LOGGED_REQUESTS_MAX_SIZE = 5000
passerelle/utils/__init__.py
161 161
    if logger.level == 10:  # DEBUG
162 162
        extra['request_headers'] = dict(request.headers.items())
163 163
        if request.body:
164
            extra['request_payload'] = repr(request.body[:settings.LOGGED_REQUESTS_MAX_SIZE])
164
            if hasattr(logger, 'connector'):
165
                max_size = logger.connector.logging_parameters.requests_max_size
166
            else:
167
                max_size = settings.LOGGED_REQUESTS_MAX_SIZE
168
            extra['request_payload'] = repr(request.body[:max_size])
165 169
    if response is not None:
166 170
        message = message + ' (=> %s)' % response.status_code
167 171
        extra['response_status'] = response.status_code
......
169 173
            extra['response_headers'] = dict(response.headers.items())
170 174
            # log body only if content type is allowed
171 175
            if content_type_match(response.headers.get('Content-Type')):
172
                content = response.content[:settings.LOGGED_RESPONSES_MAX_SIZE]
176
                if hasattr(logger, 'connector'):
177
                    max_size = logger.connector.logging_parameters.responses_max_size
178
                else:
179
                    max_size = settings.LOGGED_RESPONSES_MAX_SIZE
180
                content = response.content[:max_size]
173 181
                extra['response_content'] = repr(content)
174 182
        if response.status_code // 100 == 3:
175 183
            log_function = logger.warning
passerelle/utils/jsonresponse.py
132 132
        except Exception as e:
133 133
            extras = {'method': req.method, 'exception': exception_to_text(e), 'request': req}
134 134
            if req.method == 'POST':
135
                extras.update({'body': repr(req.body[:settings.LOGGED_REQUESTS_MAX_SIZE])})
135
                if hasattr(logger, 'connector'):
136
                    max_size = logger.connector.logging_parameters.requests_max_size
137
                else:
138
                    max_size = settings.LOGGED_REQUESTS_MAX_SIZE
139
                extras.update({'body': repr(req.body[:max_size])})
136 140
            if (not isinstance(e, (Http404, PermissionDenied, ObjectDoesNotExist, RequestException))
137 141
                    and getattr(e, 'log_error', True)):
138 142
                logger.exception("Error occurred while processing request", extra=extras)
passerelle/views.py
408 408
        connector_name, endpoint_name = kwargs['connector'], kwargs['endpoint']
409 409
        connector = self.get_object()
410 410
        url = request.get_full_path()
411
        payload = request.body[:settings.LOGGED_REQUESTS_MAX_SIZE]
411
        payload = request.body[:connector.logging_parameters.requests_max_size]
412 412
        try:
413 413
            payload.decode('utf-8')
414 414
        except UnicodeDecodeError:
tests/settings.py
67 67
        },
68 68
    }
69 69
}
70

  
71
LOGGED_REQUESTS_MAX_SIZE = 4999
tests/test_api_access.py
208 208
    oxyd.set_log_level('DEBUG')  # log request payload and response headers/content
209 209
    settings.LOGGED_CONTENT_TYPES_MESSAGES = 'foo/bar'  # response content to log
210 210

  
211
    assert settings.LOGGED_REQUEST_MAX_SIZE == 5000
212
    assert settings.LOGGED_RESPONSES_MAX_SIZE == 4096
211
    assert oxyd.logging_parameters.requests_max_size == 4999
212
    assert oxyd.logging_parameters.responses_max_size == 5000
213 213
    with utils.mock_url(oxyd.URL, response, headers=headers):
214 214
        result = app.post_json(endpoint_url, params=payload)
215 215
    assert len(ResourceLog.objects.all()) == 4
......
227 227
    assert len(ResourceLog.objects.all()[3].extra['body']) == 86
228 228

  
229 229
    # troncate logs
230
    settings.LOGGED_REQUESTS_MAX_SIZE = 10
231
    settings.LOGGED_RESPONSES_MAX_SIZE = 20
230
    parameters = oxyd.logging_parameters
231
    parameters.requests_max_size = 10
232
    parameters.save()
233
    parameters = oxyd.logging_parameters
234
    parameters.responses_max_size = 20
235
    parameters.save()
232 236
    with utils.mock_url(oxyd.URL, response, headers=headers):
233 237
        result = app.post_json(endpoint_url, params=payload)
234 238
    assert len(ResourceLog.objects.all()) == 8
tests/test_requests.py
105 105
            assert not hasattr(record, 'response_content')
106 106
            assert not hasattr(record, 'response_headers')
107 107

  
108
def test_log_error_request_max_size(caplog, log_level, settings):
108
def test_log_error_http_max_sizes(caplog, log_level, settings):
109 109
    url = 'https://httperror.org/plop'
110 110

  
111 111
    logger = logging.getLogger('requests')
112 112
    logger.setLevel(log_level)
113 113

  
114
    assert settings.LOGGED_REQUESTS_MAX_SIZE == 4999
115
    assert settings.LOGGED_RESPONSES_MAX_SIZE == 5000
114 116
    settings.LOGGED_REQUESTS_MAX_SIZE = 8
117
    settings.LOGGED_RESPONSES_MAX_SIZE = 7
115 118
    with HTTMock(http400_mock):
116 119
        requests = Request(logger=logger)
117 120
        response = requests.post(url, json={'name':'josh'})
118 121

  
119
    print logger.level
120 122
    if logger.level == 10:  # DEBUG
121 123
        records = [record for record in caplog.records if record.name == 'requests']
122 124
        assert records[0].request_payload == '\'{"name":\''
125
        assert records[0].response_content == '\'{"foo":\''
123 126

  
124 127

  
125 128
@pytest.fixture(params=['xml', 'whatever', 'jpeg', 'pdf'])
126
-