aschn / drf-tracking

Utils to track requests to Django Rest Framework API views
http://drf-tracking.readthedocs.org/
ISC License
366 stars 95 forks source link

Feature Request: Skip logging the response #133

Open rrauenza opened 5 years ago

rrauenza commented 5 years ago

I'd like to not log the response. I could override handle_log to NULL it out, but that means I'm still doing the _clean_data on it.

What do you think of adding a get_response method to the BaseLoggingMixin that one could override to just return None?

I can do a PR, but I thought perhaps you might want to do this with all of the possible fields instead of a one-off for this field -- so would like your input first.

robertour commented 4 years ago

If you are looking for saving space in your database, you could override the _clean_data method instead. This will not only nullify the response but also the parameters and data columns:

from rest_framework_tracking.mixins import LoggingMixin as Base
class LoggingMixin(Base):
    def _clean_data(self, data):
        return None

A more detailed version will overwrite the entire finalize_response:

from django.db import connection
from rest_framework_tracking.base_mixins import logger
from rest_framework_tracking.base_mixins import BaseLoggingMixin
from rest_framework_tracking.models import APIRequestLog

class LoggingMixin(BaseLoggingMixin):

    def _clean_data(self, data):
        return None

    def handle_log(self):
        """
        Hook to define what happens with the log.
        Defaults on saving the data on the db.
        """
        APIRequestLog(**self.log).save()

    def finalize_response(self, request, response, *args, **kwargs):
        response = super(BaseLoggingMixin, self).finalize_response(
            request, response, *args, **kwargs)

        # Ensure backward compatibility for those using _should_log hook
        should_log = self._should_log if hasattr(
            self, '_should_log') else self.should_log

        if should_log(request, response):
            # if response.streaming:
            #     rendered_content = None
            # elif hasattr(response, 'rendered_content'):
            #     rendered_content = response.rendered_content
            # else:
            #     rendered_content = response.getvalue()

            self.log.update(
                {
                    'remote_addr': self._get_ip_address(request),
                    'view': self._get_view_name(request),
                    'view_method': self._get_view_method(request),
                    'path': request.path,
                    'host': request.get_host(),
                    'method': request.method,
                    'query_params': self._clean_data(request.query_params.dict()),
                    'user': self._get_user(request),
                    'response_ms': self._get_response_ms(),
                    'response': None,
                    'status_code': response.status_code,
                }
            )
            try:
                if not connection.settings_dict.get('ATOMIC_REQUESTS'):
                    self.handle_log()
                else:
                    if getattr(response, 'exception', None) and connection.in_atomic_block:
                        # response with exception (HTTP status like: 401, 404, etc)
                        # pointwise disable atomic block for handle log
                        # (TransactionManagementError)
                        connection.set_rollback(True)
                        connection.set_rollback(False)
                    self.handle_log()
            except Exception:
                # ensure that all exceptions raised by handle_log
                # doesn't prevent API call to continue as expected
                logger.exception('Logging API call raise exception!')

        return response

I do agree that it would be nice if this part of the code is factorized so that one can more easily override the method to select the fields that will be actually store in the database:

            self.log.update(
                {
                    'remote_addr': self._get_ip_address(request),
                    'view': self._get_view_name(request),
                    'view_method': self._get_view_method(request),
                    'path': request.path,
                    'host': request.get_host(),
                    'method': request.method,
                    'query_params': self._clean_data(request.query_params.dict()),
                    'user': self._get_user(request),
                    'response_ms': self._get_response_ms(),
                    'response': None,
                    'status_code': response.status_code,
                }
            )