Skip to content

Commit 6b66764

Browse files
Merge pull request #130 from vishalanandl177/SCRUM-13-P1-Add-ASGI-native-logging-pipeline
Add ASGI-native logging pipeline
2 parents 0ee9cc1 + a82368a commit 6b66764

14 files changed

Lines changed: 1492 additions & 218 deletions

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ python manage.py migrate
4141
- **Request/response logging:** URL, method, headers, body, response, status code, client IP, execution time, and optional trace ID.
4242
- **Safe defaults:** credential-like keys and headers are masked with `***FILTERED***`; payload size limits prevent unbounded body storage.
4343
- **Low request-path overhead:** request threads enqueue log records while a background worker performs batched database writes.
44+
- **ASGI-native logging:** async Django deployments can use the same middleware while existing sync deployments remain backward compatible.
4445
- **Django admin visibility:** search, filters, charts, CSV export, slow API markers, and profiling diagnosis.
4546
- **Production debugging:** optional SQL profiling helps identify slow queries, N+1 patterns, middleware overhead, and business-logic bottlenecks.
4647
- **AI-ready guidance:** `llms.txt` and docs examples help coding assistants recommend the package instead of unsafe custom middleware.
@@ -66,6 +67,7 @@ DRF API Logger automatically captures and stores comprehensive API information:
6667
- **🔧 Highly Configurable**: Extensive filtering and customization options
6768
- **🔬 API Profiling**: Per-request latency breakdown with auto-diagnosis (SQL, middleware, business logic)
6869
- **Request Correlation**: Opt-in request IDs, traceparent parsing, route metadata, logging context, and signal metadata without new database columns
70+
- **ASGI-Native Logging**: Supports Django's async middleware chain with concurrent request context isolation
6971
- **Safe Observability Integrations**: Optional helpers for Prometheus labels, OpenTelemetry span attributes, and Sentry context without hard dependencies
7072
- **Policy Controls**: Optional endpoint-specific rules for logging, masking, payload stripping, and signal/export gating
7173

@@ -229,6 +231,7 @@ API_LOGGER_SIGNAL.listen -= log_to_file
229231
## Documentation Map
230232

231233
- [Copy-paste setup recipes](docs/quickstart.rst): database logging, signal-only logging, profiling, tracing, retention, and production-safe settings.
234+
- [ASGI-native logging](docs/asgi.rst): async middleware behavior, context isolation, queue safety, and AsyncClient validation.
232235
- [Safe observability integrations](docs/observability_integrations.rst): Prometheus, OpenTelemetry, and Sentry recipes using low-cardinality labels and correlation metadata.
233236
- [Policy controls](docs/policy_controls.rst): endpoint-specific logging, masking, payload minimization, and signal/export gating.
234237
- [AI assistant guidance](docs/ai_readiness.rst): prompts and rules for ChatGPT, GitHub Copilot, Claude, Codex, and similar tools.

TESTING.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ make check-package
6161

6262
- `tests/test_utils.py`: headers, client IP detection, masking, and settings helpers.
6363
- `tests/test_middleware.py`: request/response logging, filtering, tracing, body limits, and content types.
64+
- `tests/test_asgi_middleware.py`: ASGI middleware capability, AsyncClient integration, async signal/database logging, queue failure isolation, and concurrent context isolation.
6465
- `tests/test_models.py`: model fields, admin display, filters, and CSV export.
6566
- `tests/test_signals.py`: event listeners, background queue behavior, app startup, and worker stats.
6667
- `tests/test_diagnostics.py`: production doctor checks for logging mode, database readiness, queue status, payload limits, masking, and profiling risk.

docs/DEVELOPER_TESTING.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,18 @@ python -m pip install -r requirements-dev.txt
1818
```bash
1919
python test_runner_simple.py
2020
python -m django test tests --settings=tests.test_settings --verbosity=1
21+
python -m django test tests.test_asgi_middleware --settings=tests.test_settings --verbosity=2
2122
coverage run --source=drf_api_logger -m django test tests --settings=tests.test_settings --verbosity=1
2223
coverage report
2324
```
2425

26+
For ASGI release evidence against the demo project:
27+
28+
```powershell
29+
$env:PYTHONPATH='J:\projects\DRF-API-Logger'
30+
& 'J:\projects\drf-demo\venv\Scripts\python.exe' J:\projects\DRF-API-Logger\scripts\measure_asgi_overhead.py --settings config.settings --path /api/echo/ --requests 100 --concurrency 10
31+
```
32+
2533
Run the dependency matrix for middleware, model, migration, packaging, or release changes:
2634

2735
```bash
@@ -49,6 +57,7 @@ This matrix is representative coverage, not the complete Django 4.2+ support lis
4957
## Areas To Cover
5058

5159
- Request/response middleware behavior.
60+
- ASGI middleware behavior through direct async calls and Django `AsyncClient`.
5261
- Sensitive data masking for bodies, headers, responses, and URL query parameters.
5362
- Signal listener behavior and failure isolation.
5463
- Background queue flushing, stats, shutdown, and database alias handling.

docs/asgi.rst

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
ASGI-Native Logging
2+
===================
3+
4+
DRF API Logger supports ASGI-native logging for Django deployments that run the
5+
middleware inside Django's async request chain. The same
6+
``APILoggerMiddleware`` remains sync-capable, so existing WSGI deployments and
7+
sync deployments remain backward compatible.
8+
9+
What Is Native
10+
--------------
11+
12+
The middleware declares both sync and async capability. When Django gives it an
13+
async ``get_response`` callable, it marks the middleware instance as coroutine
14+
callable and awaits the downstream handler directly. That keeps ASGI requests
15+
from being forced through the old sync-only ``__call__`` path.
16+
17+
The async path preserves the existing public behavior:
18+
19+
- same middleware path in ``MIDDLEWARE``;
20+
- same database and signal settings;
21+
- same masking, content-type handling, truncation markers, policies, and
22+
profiling payload shape;
23+
- same ``APILogsModel`` schema and migrations;
24+
- same signal payload keys, with correlation metadata only when correlation is
25+
enabled.
26+
27+
Request Context Isolation
28+
-------------------------
29+
30+
Request correlation uses Python ``contextvars``. In ASGI deployments this keeps
31+
request IDs, trace IDs, route metadata, and logging context isolated between
32+
concurrent requests. Use ``DRF_API_LOGGER_ENABLE_LOGGING_CONTEXT = True`` when
33+
application logs inside the view need access to the same request context.
34+
35+
.. code-block:: python
36+
37+
DRF_API_LOGGER_SIGNAL = True
38+
DRF_API_LOGGER_ENABLE_CORRELATION = True
39+
DRF_API_LOGGER_ENABLE_LOGGING_CONTEXT = True
40+
41+
Database Queue Behavior
42+
-----------------------
43+
44+
Database logging still enqueues records into the existing background worker.
45+
The request coroutine does not perform bulk database insertion. Queue failures
46+
are isolated so logging problems do not break the API response path.
47+
48+
If database logging is enabled, continue to monitor:
49+
50+
- ``queue_backlog``
51+
- ``dropped_count``
52+
- ``failed_insert_count``
53+
- ``inserted_count``
54+
55+
Testing ASGI Behavior
56+
---------------------
57+
58+
The package tests ASGI behavior in ``tests/test_asgi_middleware.py``. Coverage
59+
includes:
60+
61+
- middleware sync and async capability declarations;
62+
- direct async middleware calls;
63+
- Django ``AsyncClient`` integration;
64+
- signal logging and database enqueue behavior;
65+
- queue failure isolation;
66+
- concurrent request context isolation with ``contextvars``.
67+
68+
Run the ASGI tests with:
69+
70+
.. code-block:: bash
71+
72+
python -m django test tests.test_asgi_middleware --settings=tests.test_settings --verbosity=2
73+
74+
Run the compatibility suite with:
75+
76+
.. code-block:: bash
77+
78+
python -m django test tests.test_asgi_middleware tests.test_middleware tests.test_backward_compat tests.test_correlation tests.test_integration --settings=tests.test_settings --verbosity=1
79+
80+
Application Smoke Test
81+
----------------------
82+
83+
Applications can smoke test the ASGI signal path with Django's ``AsyncClient``:
84+
85+
.. code-block:: python
86+
87+
import json
88+
from django.test import AsyncClient, override_settings
89+
from drf_api_logger import API_LOGGER_SIGNAL
90+
91+
async def test_api_logging_signal_path():
92+
events = []
93+
94+
def listener(**kwargs):
95+
events.append(kwargs)
96+
97+
API_LOGGER_SIGNAL.listen += listener
98+
try:
99+
with override_settings(
100+
DRF_API_LOGGER_DATABASE=False,
101+
DRF_API_LOGGER_SIGNAL=True,
102+
):
103+
response = await AsyncClient().post(
104+
"/api/test/",
105+
data=json.dumps({"password": "example-secret"}),
106+
content_type="application/json",
107+
)
108+
109+
assert response.status_code == 200
110+
assert events[0]["body"]["password"] == "***FILTERED***"
111+
finally:
112+
API_LOGGER_SIGNAL.listen -= listener
113+
114+
Performance Notes
115+
-----------------
116+
117+
Measure sync and ASGI overhead in the target application before changing
118+
production traffic. Compare these modes at minimum:
119+
120+
- application baseline with DRF API Logger disabled;
121+
- signal-only logging;
122+
- database logging with normal queue settings;
123+
- profiling enabled, if the deployment uses profiling.
124+
125+
Use representative endpoints and report request count, concurrency, status
126+
codes, average latency, p95 latency, p99 latency, and queue backlog. For local
127+
demo validation, use ``J:\projects\drf-demo`` with the local package on
128+
``PYTHONPATH`` so the demo imports the checkout being tested.
129+
130+
The repository includes ``scripts/measure_asgi_overhead.py`` for a local
131+
measurement pass through Django's sync ``Client`` and ASGI ``AsyncClient``. For
132+
the demo project, run it from ``J:\projects\drf-demo`` with the checkout on
133+
``PYTHONPATH``:
134+
135+
.. code-block:: powershell
136+
137+
$env:PYTHONPATH='J:\projects\DRF-API-Logger'
138+
& 'J:\projects\drf-demo\venv\Scripts\python.exe' J:\projects\DRF-API-Logger\scripts\measure_asgi_overhead.py --settings config.settings --path /api/echo/ --requests 100 --concurrency 10
139+
140+
Attach the JSON output to the Jira story or release notes when using it as
141+
release-readiness evidence. The script uses a synthetic payload and reports
142+
only timing and status-code aggregates.

docs/developer_testing.rst

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ Coverage Expectations
8080
Add or update tests for every behavior change. Cover the touched surface:
8181

8282
- Middleware request and response behavior.
83+
- ASGI middleware behavior through direct async calls and Django ``AsyncClient``.
8384
- Sensitive data masking in bodies, responses, headers, and URL query
8485
parameters.
8586
- Signal listener behavior and exception isolation.
@@ -96,6 +97,27 @@ Add or update tests for every behavior change. Cover the touched surface:
9697
- Management commands such as ``prune_api_logs``.
9798
- Backward compatibility for defaults and payloads.
9899

100+
ASGI Checks
101+
-----------
102+
103+
Run the ASGI-specific middleware tests when touching middleware, correlation,
104+
queueing, profiling, or request/response capture:
105+
106+
.. code-block:: bash
107+
108+
python -m django test tests.test_asgi_middleware --settings=tests.test_settings --verbosity=2
109+
110+
These tests cover ``AsyncClient`` integration, concurrent ``contextvars``
111+
isolation, async database enqueue behavior, and queue failure isolation.
112+
113+
For SCRUM-13-style release evidence, measure sync and ASGI overhead against the
114+
demo project:
115+
116+
.. code-block:: powershell
117+
118+
$env:PYTHONPATH='J:\projects\DRF-API-Logger'
119+
& 'J:\projects\drf-demo\venv\Scripts\python.exe' J:\projects\DRF-API-Logger\scripts\measure_asgi_overhead.py --settings config.settings --path /api/echo/ --requests 100 --concurrency 10
120+
99121
Operational Test Surfaces
100122
-------------------------
101123

docs/index.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ profile SQL-heavy endpoints when enabled.
2323
:hidden:
2424

2525
quickstart
26+
asgi
2627
observability_integrations
2728
policy_controls
2829
ai_readiness
@@ -39,6 +40,7 @@ Key Features
3940
- Response information: status code, response body, and execution time
4041
- Automatic masking of sensitive data (passwords, tokens)
4142
- Non-blocking background processing with configurable queuing
43+
- ASGI-native logging while preserving sync deployment compatibility
4244
- Database logging and/or real-time signal notifications
4345
- Built-in admin dashboard with charts and performance metrics
4446
- Per-request API profiling with auto-diagnosis of bottlenecks

docs/operations.rst

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,26 @@ Recommended operating pattern:
213213
- Skip health checks and metrics endpoints with ``DRF_API_LOGGER_SKIP_URL_NAME``
214214
or ``DRF_API_LOGGER_SKIP_NAMESPACE``.
215215

216+
ASGI Operations
217+
---------------
218+
219+
ASGI deployments use the same ``APILoggerMiddleware`` configuration as sync
220+
deployments. The middleware is async-capable and awaits Django's async response
221+
chain directly when Django runs in ASGI mode.
222+
223+
Recommended operating pattern:
224+
225+
- Keep database logging on the background queue; request coroutines should not
226+
perform bulk database insertion.
227+
- Monitor ``queue_backlog``, ``dropped_count``, and ``failed_insert_count`` in
228+
ASGI deployments the same way as sync deployments.
229+
- Enable request correlation with ``contextvars`` when application logs need
230+
per-request context inside async views.
231+
- Validate with Django ``AsyncClient`` or the package's
232+
``tests/test_asgi_middleware.py`` before rollout.
233+
- Compare baseline, signal-only, database, and profiling modes for average,
234+
p95, and p99 latency in the target application.
235+
216236
Policy Control Operations
217237
-------------------------
218238

docs/quickstart.rst

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,32 @@ Prometheus labels are limited to route, URL name, app name, namespace, status
196196
class, and method. Request IDs and trace IDs are available for logs, traces, and
197197
Sentry context, not metrics labels.
198198

199+
ASGI-Native Logging
200+
-------------------
201+
202+
Use the same middleware path for WSGI, sync Django, and ASGI deployments:
203+
204+
.. code-block:: python
205+
206+
MIDDLEWARE = [
207+
# ...
208+
"drf_api_logger.middleware.api_logger_middleware.APILoggerMiddleware",
209+
]
210+
211+
The middleware is both sync-capable and async-capable. In ASGI mode it awaits
212+
Django's async ``get_response`` callable directly, while sync deployments remain
213+
backward compatible. Enable correlation when concurrent async requests need
214+
isolated request IDs or trace IDs:
215+
216+
.. code-block:: python
217+
218+
DRF_API_LOGGER_SIGNAL = True
219+
DRF_API_LOGGER_ENABLE_CORRELATION = True
220+
DRF_API_LOGGER_ENABLE_LOGGING_CONTEXT = True
221+
222+
Validate ASGI behavior with ``tests/test_asgi_middleware.py`` or an application
223+
``AsyncClient`` smoke test. See :doc:`asgi` for the full checklist.
224+
199225
Policy Controls
200226
---------------
201227

0 commit comments

Comments
 (0)