Skip to content

Commit c05af12

Browse files
committed
process PR feedback
1 parent 5008ad8 commit c05af12

File tree

14 files changed

+789
-161
lines changed

14 files changed

+789
-161
lines changed

.github/workflows/ci.yml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,16 @@ jobs:
2323

2424
name: Run the test suite (Python ${{ matrix.python }}, Django ${{ matrix.django }})
2525

26+
services:
27+
postgres:
28+
image: postgres:14
29+
env:
30+
POSTGRES_HOST_AUTH_METHOD: trust
31+
ports:
32+
- 5432:5432
33+
# needed because the postgres container does not provide a healthcheck
34+
options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5
35+
2636
steps:
2737
- uses: actions/checkout@v3
2838
- uses: actions/setup-python@v4

README.rst

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -115,8 +115,9 @@ To use this with your project you need to follow these steps:
115115
res = requests.get("https://httpbin.org/json")
116116
print(res.json())
117117
118-
#. Check stdout for the printable output, and navigate to ``/admin/log_outgoing_requests/outgoingrequestslog/`` to see
119-
the saved log records. The settings for saving logs can by overridden under ``/admin/log_outgoing_requests/outgoingrequestslogconfig/``.
118+
#. Check stdout for the printable output, and navigate to ``Admin > Miscellaneous > Outgoing Requests Logs``
119+
to see the saved log records. In order to override the settings for saving logs, navigate to
120+
``Admin > Miscellaneous > Outgoing Requests Log Configuration``.
120121

121122

122123
Local development

docs/quickstart.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,13 @@ Installation
6060
LOG_OUTGOING_REQUESTS_DB_SAVE = True # save logs enabled/disabled based on the boolean value
6161
LOG_OUTGOING_REQUESTS_SAVE_BODY = True # save request/response body
6262
LOG_OUTGOING_REQUESTS_LOG_BODY_TO_STDOUT = True # log request/response body to STDOUT
63+
LOG_OUTGOING_REQUESTS_CONTENT_TYPES = [
64+
"text/*",
65+
"application/json",
66+
"application/xml",
67+
] # save request/response bodies with matching content type
68+
LOG_OUTGOING_REQUESTS_MAX_CONTENT_LENGTH = 32000 # maximal size (in bytes) for the request/response body
69+
LOG_OUTGOING_REQUESTS_REQUIRE_CONTENT_LENGTH = True # if True, the content size of request/response bodies is not checked
6370
6471
6572
#. Run ``python manage.py migrate`` to create the necessary database tables.

log_outgoing_requests/admin.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from django import forms
12
from django.contrib import admin
23
from django.utils.translation import gettext as _
34

@@ -62,13 +63,27 @@ class Media:
6263
}
6364

6465

66+
class ConfigAdminForm(forms.ModelForm):
67+
class Meta:
68+
model = OutgoingRequestsLogConfig
69+
fields = ("allowed_content_types",)
70+
widgets = {"allowed_content_types": forms.CheckboxSelectMultiple}
71+
72+
6573
@admin.register(OutgoingRequestsLogConfig)
6674
class OutgoingRequestsLogConfigAdmin(SingletonModelAdmin):
75+
form = ConfigAdminForm
6776
fields = (
6877
"save_to_db",
6978
"save_body",
79+
"allowed_content_types",
80+
"require_content_length",
81+
"max_content_length",
7082
)
7183
list_display = (
7284
"save_to_db",
7385
"save_body",
86+
"allowed_content_types",
87+
"require_content_length",
88+
"max_content_length",
7489
)

log_outgoing_requests/formatters.py

Lines changed: 27 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -8,32 +8,40 @@ class HttpFormatter(logging.Formatter):
88
def _formatHeaders(self, d):
99
return "\n".join(f"{k}: {v}" for k, v in d.items())
1010

11-
def _formatBody(self, content: dict, request_or_response: str) -> str:
11+
def _formatBody(self, content: str, request_or_response: str) -> str:
1212
if settings.LOG_OUTGOING_REQUESTS_LOG_BODY_TO_STDOUT:
1313
return f"\n{request_or_response} body:\n{content}"
1414
return ""
1515

1616
def formatMessage(self, record):
17-
result = super().formatMessage(record)
18-
if record.name == "requests":
19-
result += textwrap.dedent(
20-
"""
21-
---------------- request ----------------
22-
{req.method} {req.url}
23-
{reqhdrs} {request_body}
24-
25-
---------------- response ----------------
26-
{res.status_code} {res.reason} {res.url}
27-
{reshdrs} {response_body}
17+
if record.name != "requests":
18+
return
2819

20+
result = super().formatMessage(record)
21+
result += textwrap.dedent(
2922
"""
30-
).format(
31-
req=record.req,
32-
res=record.res,
33-
reqhdrs=self._formatHeaders(record.req.headers),
34-
reshdrs=self._formatHeaders(record.res.headers),
35-
request_body=self._formatBody(record.req.body, "Request"),
36-
response_body=self._formatBody(record.res.json(), "Response"),
23+
---------------- request ----------------
24+
{req.method} {req.url}
25+
{reqhdrs} {request_body}
26+
27+
---------------- response ----------------
28+
{res.status_code} {res.reason} {res.url}
29+
{reshdrs} {response_body}
30+
31+
"""
32+
).format(
33+
req=record.req,
34+
res=record.res,
35+
reqhdrs=self._formatHeaders(record.req.headers),
36+
reshdrs=self._formatHeaders(record.res.headers),
37+
request_body=self._formatBody(record.req.body, "Request")
38+
if settings.LOG_OUTGOING_REQUESTS_LOG_BODY_TO_STDOUT is True
39+
else "",
40+
response_body=self._formatBody(
41+
record.res.content.decode("utf-8"), "Response"
3742
)
43+
if settings.LOG_OUTGOING_REQUESTS_LOG_BODY_TO_STDOUT is True
44+
else "",
45+
)
3846

3947
return result

log_outgoing_requests/handlers.py

Lines changed: 25 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,7 @@
22
import traceback
33
from urllib.parse import urlparse
44

5-
from django.conf import settings
6-
7-
ALLOWED_CONTENT_TYPES = [
8-
"application/json",
9-
"multipart/form-data",
10-
"text/html",
11-
"text/plain",
12-
"",
13-
None,
14-
]
5+
from .validators import validate_content
156

167

178
class DatabaseOutgoingRequestsHandler(logging.Handler):
@@ -20,29 +11,19 @@ def emit(self, record):
2011

2112
config = OutgoingRequestsLogConfig.get_solo()
2213

23-
if config.save_to_db or settings.LOG_OUTGOING_REQUESTS_DB_SAVE:
14+
if config.save_logs_enabled:
2415
from .models import OutgoingRequestsLog
2516

26-
trace = None
17+
trace = ""
2718

2819
# skip requests not coming from the library requests
2920
if not record or not record.getMessage() == "Outgoing request":
3021
return
3122

32-
# skip requests with non-allowed content
33-
request_content_type = record.req.headers.get("Content-Type", "")
34-
response_content_type = record.res.headers.get("Content-Type", "")
35-
36-
if not (
37-
request_content_type in ALLOWED_CONTENT_TYPES
38-
and response_content_type in ALLOWED_CONTENT_TYPES
39-
):
40-
return
41-
42-
safe_req_headers = record.req.headers.copy()
23+
scrubbed_req_headers = record.req.headers.copy()
4324

44-
if "Authorization" in safe_req_headers:
45-
safe_req_headers["Authorization"] = "***hidden***"
25+
if "Authorization" in scrubbed_req_headers:
26+
scrubbed_req_headers["Authorization"] = "***hidden***"
4627

4728
if record.exc_info:
4829
trace = traceback.format_exc()
@@ -54,18 +35,31 @@ def emit(self, record):
5435
"params": parsed_url.params,
5536
"status_code": record.res.status_code,
5637
"method": record.req.method,
57-
"req_content_type": record.req.headers.get("Content-Type", ""),
58-
"res_content_type": record.res.headers.get("Content-Type", ""),
38+
"req_content_type": (
39+
request_content_type := record.req.headers.get("Content-Type", "")
40+
),
41+
"res_content_type": (
42+
response_content_type := record.res.headers.get("Content-Type", "")
43+
),
5944
"timestamp": record.requested_at,
6045
"response_ms": int(record.res.elapsed.total_seconds() * 1000),
61-
"req_headers": self.format_headers(safe_req_headers),
46+
"req_headers": self.format_headers(scrubbed_req_headers),
6247
"res_headers": self.format_headers(record.res.headers),
6348
"trace": trace,
6449
}
6550

66-
if config.save_body or settings.LOG_OUTGOING_REQUESTS_SAVE_BODY:
67-
kwargs["req_body"] = (record.req.body,)
68-
kwargs["res_body"] = (record.res.json(),)
51+
if config.save_body_enabled:
52+
request_content_length = record.req.headers.get("Content-Length")
53+
if validate_content(
54+
request_content_length, request_content_type, config
55+
):
56+
kwargs["req_body"] = record.req.body or ""
57+
58+
response_content_length = record.res.headers.get("Content-Length")
59+
if validate_content(
60+
response_content_length, response_content_type, config
61+
):
62+
kwargs["res_body"] = record.res.content.decode("utf-8") or ""
6963

7064
OutgoingRequestsLog.objects.create(**kwargs)
7165

0 commit comments

Comments
 (0)