Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ A `no_logging` decorator is included for views with sensitive data.
By default, value of Http headers `HTTP_AUTHORIZATION` and `HTTP_PROXY_AUTHORIZATION` are replaced wih `*****`. You can use `REQUEST_LOGGING_SENSITIVE_HEADERS` setting to override this default behaviour with your list of sensitive headers.

## Django settings
You can customized some behaves of django-request-logging by following settings in Django `settings.py`.
You can customize some behaviours of django-request-logging by following settings in Django `settings.py`.
### REQUEST_LOGGING_DATA_LOG_LEVEL
By default, data will log in DEBUG level, you can change to other valid level (Ex. logging.INFO) if need.
### REQUEST_LOGGING_ENABLE_COLORIZE
Expand All @@ -73,6 +73,11 @@ By default, HTTP status codes between 400 - 499 are logged at ERROR level. You
If you set `REQUEST_LOGGING_HTTP_4XX_LOG_LEVEL=logging.INFO` they will be logged the same as normal requests.
### REQUEST_LOGGING_SENSITIVE_HEADERS
The value of the headers defined in this settings will be replaced with `'*****'` to hide the sensitive information while logging. By default it is set as `REQUEST_LOGGING_SENSITIVE_HEADERS = ["HTTP_AUTHORIZATION", "HTTP_PROXY_AUTHORIZATION"]`
### REQUEST_LOGGING_SENSITIVE_VIEWS
A list of views (or view methods in case of class based views) that should not be logged. This has the same behaviour as the no_logging decorator, but can be used in case of 3rd party modules where the source cannot be modified
`REQUEST_LOGGING_SENSITIVE_VIEWS = ['dj_rest_auth.views.LoginView.post']`
### REQUEST_LOGGING_LOG_PROCESSING_TIME
Calculate the processing time of the request in milliseconds and append them in the response logging. Defaults to `False`


## Deploying, Etc.
Expand Down
42 changes: 37 additions & 5 deletions request_logging/middleware.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import logging
import re
import time

from django import VERSION as django_version
from django.conf import settings
Expand All @@ -22,13 +23,17 @@
] if IS_DJANGO_VERSION_GTE_3_2_0 else [
"HTTP_AUTHORIZATION", "HTTP_PROXY_AUTHORIZATION"
]
DEFAULT_SENSITIVE_VIEWS = []
DEFAULT_LOG_PROCESSING_TIME = False
SETTING_NAMES = {
"log_level": "REQUEST_LOGGING_DATA_LOG_LEVEL",
"http_4xx_log_level": "REQUEST_LOGGING_HTTP_4XX_LOG_LEVEL",
"legacy_colorize": "REQUEST_LOGGING_DISABLE_COLORIZE",
"colorize": "REQUEST_LOGGING_ENABLE_COLORIZE",
"max_body_length": "REQUEST_LOGGING_MAX_BODY_LENGTH",
"sensitive_headers": "REQUEST_LOGGING_SENSITIVE_HEADERS",
"sensitive_views": "REQUEST_LOGGING_SENSITIVE_VIEWS",
"log_processing_time": "REQUEST_LOGGING_LOG_PROCESSING_TIME",
}
BINARY_REGEX = re.compile(r"(.+Content-Type:.*?)(\S+)/(\S+)(?:\r\n)*(.+)", re.S | re.I)
BINARY_TYPES = ("image", "application")
Expand Down Expand Up @@ -77,6 +82,8 @@ def __init__(self, get_response=None):
self.log_level = getattr(settings, SETTING_NAMES["log_level"], DEFAULT_LOG_LEVEL)
self.http_4xx_log_level = getattr(settings, SETTING_NAMES["http_4xx_log_level"], DEFAULT_HTTP_4XX_LOG_LEVEL)
self.sensitive_headers = getattr(settings, SETTING_NAMES["sensitive_headers"], DEFAULT_SENSITIVE_HEADERS)
self.sensitive_views = getattr(settings, SETTING_NAMES["sensitive_views"], DEFAULT_SENSITIVE_VIEWS)
self.log_processing_time = getattr(settings, SETTING_NAMES["log_processing_time"], DEFAULT_LOG_PROCESSING_TIME)
if not isinstance(self.sensitive_headers, list):
raise ValueError(
"{} should be list. {} is not list.".format(SETTING_NAMES["sensitive_headers"], self.sensitive_headers)
Expand Down Expand Up @@ -116,9 +123,19 @@ def __init__(self, get_response=None):

def __call__(self, request):
self.cached_request_body = request.body

if self.log_processing_time:
processing_start = time.time()

response = self.get_response(request)

if self.log_processing_time:
processing_time = time.time() - processing_start
else:
processing_time = None

self.process_request(request, response)
self.process_response(request, response)
self.process_response(request, response, processing_time)
return response

def process_request(self, request, response=None):
Expand All @@ -129,6 +146,20 @@ def process_request(self, request, response=None):
else:
return self._log_request(request, response)

def _should_log_view(self, func):
if func:
full_path = '.'.join([func.__module__, func.__qualname__])
else:
full_path = None

if full_path is not None and full_path in self.sensitive_views:
no_logging = True
no_logging_msg = NO_LOGGING_MSG
else:
no_logging = getattr(func, NO_LOGGING_ATTR, False)
no_logging_msg = getattr(func, NO_LOGGING_MSG_ATTR, None)
return no_logging, no_logging_msg

def _should_log_route(self, request):
# request.urlconf may be set by middleware or application level code.
# Use this urlconf if present or default to None.
Expand Down Expand Up @@ -156,9 +187,7 @@ def _should_log_route(self, request):
elif hasattr(view, "view_class"):
# This is for django class-based views
func = getattr(view.view_class, method, None)
no_logging = getattr(func, NO_LOGGING_ATTR, False)
no_logging_msg = getattr(func, NO_LOGGING_MSG_ATTR, None)
return no_logging, no_logging_msg
return self._should_log_view(func)

def _skip_logging_request(self, request, reason):
method_path = "{} {}".format(request.method, request.get_full_path())
Expand Down Expand Up @@ -208,8 +237,11 @@ def _log_request_body(self, request, logging_context, log_level):
else:
self.logger.log(log_level, self._chunked_to_max(self.cached_request_body), logging_context)

def process_response(self, request, response):
def process_response(self, request, response, processing_time=None):
resp_log = "{} {} - {}".format(request.method, request.get_full_path(), response.status_code)
if processing_time:
resp_log += ' [{} ms]'.format(int(processing_time*1000))

skip_logging, because = self._should_log_route(request)
if skip_logging:
if because is not None:
Expand Down