Skip to content

Commit 0ca638e

Browse files
committed
SIANXKE-178: enhanced configuration options
1 parent e46e8d0 commit 0ca638e

File tree

3 files changed

+158
-15
lines changed

3 files changed

+158
-15
lines changed

README.md

Lines changed: 49 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ LOGGING = {
4444

4545
## Details
4646

47-
Most of times you don't have to care about these details. But in case you need to dig deep:
47+
Most of the times you don't have to care about these details. But in case you need to dig deep:
4848

4949
* All logs are configured using logger name "django.request".
5050
* If HTTP status code is between 400 - 599, URIs are logged at ERROR level, otherwise they are logged at INFO level.
@@ -53,7 +53,40 @@ Most of times you don't have to care about these details. But in case you need t
5353
See `REQUEST_LOGGING_HTTP_4XX_LOG_LEVEL` setting to override this.
5454

5555

56-
A `no_logging` decorator is included for views with sensitive data.
56+
A `no_logging` decorator is included for views with sensitive data. This decorator allows control over logging behaviour of single views via the following parameters:
57+
```
58+
* value
59+
* False: the view does NOT log any activity at all (overrules settings of log_headers, log_body, log_response and automatically sets them to False).
60+
* True: the view logs incoming requests (potentially log headers, body and response, depending on their specific settings)
61+
* None: NO_LOGGING_DEFAULT_VALUE is used (can be defined in settings file as DJANGO_REQUEST_LOGGING_NO_LOGGING_DEFAULT_VALUE)
62+
* msg
63+
* Reason for deactivation of logging gets logged instead of request itself (only if silent=True and value=False)
64+
* NO_LOGGING_MSG is used by default
65+
* log_headers
66+
* False: request headers will not get logged
67+
* True: request headers will get logged (if value is True)
68+
* None: LOG_HEADERS_DEFAULT_VALUE is used (can be defined in settings file as DJANGO_REQUEST_LOGGING_LOG_HEADERS_DEFAULT_VALUE)
69+
* no_header_logging_msg
70+
* Reason for deactivation of header logging gets logged instead of headers (only if silent=True and log_headers=False)
71+
* NO_HEADER_LOGGING_MSG is used by default
72+
* log_body
73+
* False: request body will not get logged
74+
* True: request headers will get logged (if value is True)
75+
* None: LOG_BODY_DEFAULT_VALUE is used (can be defined in settings file as DJANGO_REQUEST_LOGGING_LOG_BODY_DEFAULT_VALUE)
76+
* no_body_logging_msg
77+
* Reason for deactivation of body logging gets logged instead of body (only if silent=True and log_body=False)
78+
* NO_BODY_LOGGING_MSG is used by default
79+
* log_response
80+
* False: response will not get logged
81+
* True: response will get logged (if value is True)
82+
* None: LOG_RESPONSE_DEFAULT_VALUE is used (can be defined in settings file as DJANGO_REQUEST_LOGGING_LOG_RESPONSE_DEFAULT_VALUE)
83+
* no_response_logging_msg
84+
* Reason for deactivation of body logging gets logged instead of body (only if silent=True and log_body=False)
85+
* NO_RESPONSE_LOGGING_MSG is used by default
86+
* silent
87+
* True: deactivate logging of alternative messages case parts of the logging are deactivated (request/header/body/response)
88+
* False: alternative messages for deactivated parts of logging (request/header/body/response) are logged instead
89+
```
5790

5891
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.
5992

@@ -73,13 +106,23 @@ By default, HTTP status codes between 400 - 499 are logged at ERROR level. You
73106
If you set `REQUEST_LOGGING_HTTP_4XX_LOG_LEVEL=logging.INFO` they will be logged the same as normal requests.
74107
### REQUEST_LOGGING_SENSITIVE_HEADERS
75108
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"]`
109+
### DJANGO_REQUEST_LOGGING_LOGGER_NAME
110+
Name of the logger that is used to log django.request occurrances with the new LoggingMiddleware. Defaults to "django.request".
111+
### DJANGO_REQUEST_LOGGING_NO_LOGGING_DEFAULT_VALUE
112+
Global default to activate/deactivate logging of all views. Can be overruled for each individual view by using the @no_logging decator's "value" parameter.
113+
### DJANGO_REQUEST_LOGGING_LOG_HEADERS_DEFAULT_VALUE = True
114+
Global default to activate/deactivate logging of request headers for all views. Can be overruled for each individual view by using the @no_logging decator's "log_headers" parameter.
115+
### DJANGO_REQUEST_LOGGING_LOG_BODY_DEFAULT_VALUE = True
116+
Global default to activate/deactivate logging of request bodys for all views. Can be overruled for each individual view by using the @no_logging decator's "log_body" parameter.
117+
### DJANGO_REQUEST_LOGGING_LOG_RESPONSE_DEFAULT_VALUE = True
118+
Global default to activate/deactivate logging of responses for all views. Can be overruled for each individual view by using the @no_logging decator's "log_response" parameter.
76119

77120

78121
## Deploying, Etc.
79122

80123
### Maintenance
81124

82-
Use `pyenv` to maintain a set of virtualenvs for 2.7 and a couple versions of Python 3.
125+
Use `pyenv` to maintain a set of virtualenvs for 2.7 and a couple versions of Python 3.
83126
Make sure the `requirements-dev.txt` installs for all of them, at least until we give up on 2.7.
84127
At that point, update this README to let users know the last version they can use with 2.7.
85128

@@ -93,17 +136,17 @@ At that point, update this README to let users know the last version they can us
93136
index-servers=
94137
testpypi
95138
pypi
96-
139+
97140
[testpypi]
98141
username = rhumbix
99142
password = password for dev@rhumbix.com at Pypi
100-
143+
101144
[pypi]
102145
username = rhumbix
103146
password = password for dev@rhumbix.com at Pypi
104147
```
105148

106-
### Publishing
149+
### Publishing
107150

108151
- Bump the version value in `request_logging/__init__.py`
109152
- Run `python setup.py publish`

request_logging/decorators.py

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,29 @@
1-
from .middleware import NO_LOGGING_ATTR, NO_LOGGING_MSG_ATTR, NO_LOGGING_MSG
1+
from .middleware import NO_LOGGING_ATTR, NO_LOGGING_MSG_ATTR, NO_LOGGING_MSG, NO_LOGGING_DEFAULT_VALUE, \
2+
LOG_HEADERS_ATTR, LOG_HEADERS_DEFAULT_VALUE, LOG_BODY_ATTR, LOG_BODY_DEFAULT_VALUE, LOG_RESPONSE_ATTR, \
3+
LOG_RESPONSE_DEFAULT_VALUE, NO_RESPONSE_LOGGING_MSG_ATTR, NO_RESPONSE_LOGGING_MSG, NO_HEADER_LOGGING_MSG_ATTR, \
4+
NO_HEADER_LOGGING_MSG, NO_BODY_LOGGING_MSG_ATTR, NO_BODY_LOGGING_MSG
25

36

4-
def no_logging(msg=None, silent=False):
7+
def no_logging(msg=None, silent=False, value=None, log_headers=None, no_header_logging_msg=None, log_body=None,
8+
no_body_logging_msg=None, log_response=None, no_response_logging_msg=None):
9+
def _set_attr(func, attr_name, value, default_value=None):
10+
setattr(func, attr_name, value if value is not None else default_value)
11+
12+
def _set_attr_msg(func, silent_value, msg_attr_name, message, default_message=None):
13+
setattr(func, msg_attr_name, (message if message else default_message) if not silent_value else None)
14+
515
def wrapper(func):
6-
setattr(func, NO_LOGGING_ATTR, True)
7-
setattr(func, NO_LOGGING_MSG_ATTR, (msg if msg else NO_LOGGING_MSG) if not silent else None)
16+
_set_attr(func, NO_LOGGING_ATTR, value, NO_LOGGING_DEFAULT_VALUE)
17+
_set_attr_msg(func, silent, NO_LOGGING_MSG_ATTR, msg, NO_LOGGING_MSG)
18+
19+
_set_attr(func, LOG_HEADERS_ATTR, log_headers, LOG_HEADERS_DEFAULT_VALUE)
20+
_set_attr_msg(func, silent, NO_HEADER_LOGGING_MSG_ATTR, no_header_logging_msg, NO_HEADER_LOGGING_MSG)
21+
22+
_set_attr(func, LOG_BODY_ATTR, log_body, LOG_BODY_DEFAULT_VALUE)
23+
_set_attr_msg(func, silent, NO_BODY_LOGGING_MSG_ATTR, no_body_logging_msg, NO_BODY_LOGGING_MSG)
24+
25+
_set_attr(func, LOG_RESPONSE_ATTR, log_response, LOG_RESPONSE_DEFAULT_VALUE)
26+
_set_attr_msg(func, silent, NO_RESPONSE_LOGGING_MSG_ATTR, no_response_logging_msg, NO_RESPONSE_LOGGING_MSG)
827
return func
928

1029
return wrapper

request_logging/middleware.py

Lines changed: 86 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,42 @@
3535
NO_LOGGING_ATTR = "no_logging"
3636
NO_LOGGING_MSG_ATTR = "no_logging_msg"
3737
NO_LOGGING_MSG = "No logging for this endpoint"
38-
request_logger = logging.getLogger("django.request")
38+
NO_LOGGING_DEFAULT_VALUE = getattr(
39+
settings,
40+
"DJANGO_REQUEST_LOGGING_NO_LOGGING_DEFAULT_VALUE",
41+
False
42+
)
43+
LOG_HEADERS_ATTR = "log_headers"
44+
LOG_HEADERS_DEFAULT_VALUE = getattr(
45+
settings,
46+
"DJANGO_REQUEST_LOGGING_LOG_HEADERS_DEFAULT_VALUE",
47+
True
48+
)
49+
NO_HEADER_LOGGING_MSG_ATTR = "no_header_logging_msg"
50+
NO_HEADER_LOGGING_MSG = "No header logging for this endpoint"
51+
LOG_BODY_ATTR = "log_body"
52+
LOG_BODY_DEFAULT_VALUE = getattr(
53+
settings,
54+
"DJANGO_REQUEST_LOGGING_LOG_BODY_DEFAULT_VALUE",
55+
True
56+
)
57+
NO_BODY_LOGGING_MSG_ATTR = "no_body_logging_msg"
58+
NO_BODY_LOGGING_MSG = "No body logging for this endpoint"
59+
LOG_RESPONSE_ATTR = "response_logging"
60+
LOG_RESPONSE_DEFAULT_VALUE = getattr(
61+
settings,
62+
"DJANGO_REQUEST_LOGGING_LOG_RESPONSE_DEFAULT_VALUE",
63+
True
64+
)
65+
NO_RESPONSE_LOGGING_MSG_ATTR = "no_response_logging_msg"
66+
NO_RESPONSE_LOGGING_MSG = "No response logging for this endpoint"
67+
68+
LOGGER_NAME = getattr(
69+
settings,
70+
"DJANGO_REQUEST_LOGGING_LOGGER_NAME",
71+
"django.request"
72+
)
73+
request_logger = logging.getLogger(LOGGER_NAME)
3974

4075

4176
class Logger:
@@ -118,8 +153,8 @@ def __init__(self, get_response=None):
118153
self.logger = ColourLogger("cyan", "magenta") if enable_colorize else Logger()
119154

120155
def __call__(self, request):
121-
# cache in a local reference (instead of a member reference) and then pass in as argument
122-
# in order to avoid other threads overwriting the original self.cached_request_body reference,
156+
# cache in a local reference (instead of a member reference) and then pass in as argument
157+
# in order to avoid other threads overwriting the original self.cached_request_body reference,
123158
# is this done to preserve the original value in case it is mutated during the get_response invocation?
124159
cached_request_body = request.body
125160
response = self.get_response(request)
@@ -135,7 +170,7 @@ def process_request(self, request, response, cached_request_body):
135170
else:
136171
return self._log_request(request, response, cached_request_body)
137172

138-
def _should_log_route(self, request):
173+
def _get_func(self, request):
139174
# request.urlconf may be set by middleware or application level code.
140175
# Use this urlconf if present or default to None.
141176
# https://docs.djangoproject.com/en/2.1/topics/http/urls/#how-django-processes-a-request
@@ -162,10 +197,33 @@ def _should_log_route(self, request):
162197
elif hasattr(view, "view_class"):
163198
# This is for django class-based views
164199
func = getattr(view.view_class, method, None)
165-
no_logging = getattr(func, NO_LOGGING_ATTR, False)
200+
201+
return func
202+
203+
def _should_log_route(self, request):
204+
func = self._get_func(request)
205+
no_logging = getattr(func, NO_LOGGING_ATTR, NO_LOGGING_DEFAULT_VALUE)
166206
no_logging_msg = getattr(func, NO_LOGGING_MSG_ATTR, None)
167207
return no_logging, no_logging_msg
168208

209+
def _should_log_headers(self, request):
210+
func = self._get_func(request)
211+
header_logging = getattr(func, LOG_HEADERS_ATTR, LOG_HEADERS_DEFAULT_VALUE)
212+
no_header_logging_msg = getattr(func, NO_HEADER_LOGGING_MSG_ATTR, None)
213+
return header_logging, no_header_logging_msg
214+
215+
def _should_log_body(self, request):
216+
func = self._get_func(request)
217+
body_logging = getattr(func, LOG_BODY_ATTR, LOG_BODY_DEFAULT_VALUE)
218+
no_body_logging_msg = getattr(func, NO_BODY_LOGGING_MSG_ATTR, None)
219+
return body_logging, no_body_logging_msg
220+
221+
def _should_log_response(self, request):
222+
func = self._get_func(request)
223+
response_logging = getattr(func, LOG_RESPONSE_ATTR, LOG_RESPONSE_DEFAULT_VALUE)
224+
no_response_logging_msg = getattr(func, NO_RESPONSE_LOGGING_MSG_ATTR, None)
225+
return response_logging, no_response_logging_msg
226+
169227
def _skip_logging_request(self, request, reason):
170228
method_path = "{} {}".format(request.method, request.get_full_path())
171229
no_log_context = {
@@ -191,6 +249,14 @@ def _log_request(self, request, response, cached_request_body):
191249
self._log_request_body(request, logging_context, log_level, cached_request_body)
192250

193251
def _log_request_headers(self, request, logging_context, log_level):
252+
log_headers, because = self._should_log_headers(request)
253+
if not log_headers:
254+
if because is not None:
255+
self.logger.log_error(
256+
logging.INFO, "no headers logged", {"args": {}, "kwargs": {"extra": {"no_header_logging": because}}}
257+
)
258+
return None
259+
194260
if IS_DJANGO_VERSION_GTE_3_2_0:
195261
headers = {k: v if k not in self.sensitive_headers else "*****" for k, v in request.headers.items()}
196262
else:
@@ -204,6 +270,14 @@ def _log_request_headers(self, request, logging_context, log_level):
204270
self.logger.log(log_level, headers, logging_context)
205271

206272
def _log_request_body(self, request, logging_context, log_level, cached_request_body):
273+
log_body, because = self._should_log_body(request)
274+
if not log_body:
275+
if because is not None:
276+
self.logger.log_error(
277+
logging.INFO, "no body logged", {"args": {}, "kwargs": {"extra": {"log_body": because}}}
278+
)
279+
return None
280+
207281
if cached_request_body is not None:
208282
content_type = request.META.get("CONTENT_TYPE", "")
209283
is_multipart = content_type.startswith("multipart/form-data")
@@ -222,6 +296,13 @@ def process_response(self, request, response):
222296
logging.INFO, resp_log, {"args": {}, "kwargs": {"extra": {"no_logging": because}}}
223297
)
224298
return response
299+
log_response, because = self._should_log_response(request)
300+
if not log_response:
301+
if because is not None:
302+
self.logger.log_error(
303+
logging.INFO, resp_log, {"args": {}, "kwargs": {"extra": {"log_response": because}}}
304+
)
305+
return response
225306
logging_context = self._get_logging_context(request, response)
226307

227308
if response.status_code in range(400, 500):

0 commit comments

Comments
 (0)