Skip to content

Commit 73bc7f4

Browse files
Merge pull request #126 from vishalanandl177/codex/SCRUM-9-request-correlation-no-db
Add opt-in request correlation
2 parents 9895a9a + c052678 commit 73bc7f4

16 files changed

Lines changed: 951 additions & 3 deletions

README.md

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ DRF API Logger automatically captures and stores comprehensive API information:
6565
- **📈 Analytics**: Built-in admin dashboard with charts and performance metrics
6666
- **🔧 Highly Configurable**: Extensive filtering and customization options
6767
- **🔬 API Profiling**: Per-request latency breakdown with auto-diagnosis (SQL, middleware, business logic)
68+
- **Request Correlation**: Opt-in request IDs, traceparent parsing, route metadata, logging context, and signal metadata without new database columns
6869

6970
### 🌐 Community & Support
7071

@@ -434,6 +435,74 @@ def my_api_view(request):
434435
return Response({'status': 'ok'})
435436
```
436437

438+
### Request Correlation
439+
440+
Enable correlation when you need to connect DRF API Logger events with
441+
application logs, upstream gateway request IDs, distributed traces, or metrics
442+
labels without changing the log table schema.
443+
444+
```python
445+
DRF_API_LOGGER_ENABLE_CORRELATION = True
446+
DRF_API_LOGGER_CORRELATION_REQUEST_ID_HEADERS = ["X-Request-ID", "X-Correlation-ID"]
447+
DRF_API_LOGGER_CORRELATION_TRACE_ID_HEADERS = ["traceparent", "X-Trace-ID"]
448+
DRF_API_LOGGER_ENABLE_LOGGING_CONTEXT = True
449+
```
450+
451+
Correlation is intentionally not persisted to `APILogsModel`. It does not add
452+
model fields, migrations, admin columns, database indexes, or synthetic payload
453+
fields to queued database log rows. When enabled, metadata is available through:
454+
455+
- `request.api_logger_correlation`
456+
- `request.api_logger_low_cardinality`
457+
- `request.api_logger_request_id`
458+
- `request.api_logger_trace_id`
459+
- `drf_api_logger.logging_context.get_correlation_context()`
460+
- signal payload keys: `correlation` and `low_cardinality`
461+
462+
Example signal listener:
463+
464+
```python
465+
from drf_api_logger import API_LOGGER_SIGNAL
466+
467+
def forward_to_metrics(**kwargs):
468+
labels = kwargs.get("low_cardinality", {})
469+
correlation = kwargs.get("correlation", {})
470+
metrics.count(
471+
"drf_api_logger.request",
472+
tags={
473+
"route": labels.get("route"),
474+
"status_class": labels.get("status_class"),
475+
},
476+
)
477+
logger.info(
478+
"api request observed",
479+
extra={
480+
"request_id": correlation.get("request_id"),
481+
"trace_id": correlation.get("trace_id"),
482+
},
483+
)
484+
485+
API_LOGGER_SIGNAL.listen += forward_to_metrics
486+
```
487+
488+
Add opaque, non-sensitive context values through an allowlisted callback:
489+
490+
```python
491+
DRF_API_LOGGER_CORRELATION_CONTEXT_FUNC = "myapp.logging.api_logger_context"
492+
493+
def api_logger_context(request):
494+
return {
495+
"actor_id": getattr(request.user, "pk", None),
496+
"tenant_id": getattr(request, "tenant_id", None),
497+
"api_consumer_id": getattr(request, "api_consumer_id", None),
498+
"client_id": getattr(request, "client_id", None),
499+
}
500+
```
501+
502+
Only `actor_id`, `tenant_id`, `api_consumer_id`, and `client_id` are accepted
503+
from the callback. Use opaque IDs, not names, emails, tokens, or other
504+
identifying values.
505+
437506
## 📊 Programmatic Access
438507

439508
### Querying Log Data

docs/compliance.rst

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,26 @@ records before they enter the background queue:
6161
6262
Returning ``None`` intentionally drops that log entry.
6363

64+
Request Correlation Controls
65+
----------------------------
66+
67+
Request correlation is disabled by default. When
68+
``DRF_API_LOGGER_ENABLE_CORRELATION`` is enabled, request IDs, trace IDs, route
69+
metadata, and allowlisted opaque context are exposed through request attributes,
70+
logging context, and signal payloads. The package does not persist correlation
71+
metadata to ``APILogsModel`` and does not add migrations, model fields, admin
72+
columns, database indexes, or synthetic database payload fields.
73+
74+
For privacy-sensitive deployments:
75+
76+
- Treat ``request_id`` and ``trace_id`` as high-cardinality operational IDs.
77+
- Send only route, URL name, namespace, app name, and status class to metrics
78+
labels through ``low_cardinality``.
79+
- Use opaque values for ``actor_id``, ``tenant_id``, ``api_consumer_id``, and
80+
``client_id``.
81+
- Do not return names, emails, tokens, session IDs, or regulated identifiers
82+
from ``DRF_API_LOGGER_CORRELATION_CONTEXT_FUNC``.
83+
6484
Storage Controls
6585
----------------
6686

docs/index.rst

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ Key Features
4040
- Database logging and/or real-time signal notifications
4141
- Built-in admin dashboard with charts and performance metrics
4242
- Per-request API profiling with auto-diagnosis of bottlenecks
43+
- Request correlation through request attributes, logging context, and signals
4344

4445

4546
Supported Versions
@@ -176,6 +177,8 @@ Signal data structure:
176177
'tracing_id': 'uuid4-string', # if tracing enabled
177178
'profiling_data': { ... }, # if profiling enabled
178179
'sql_query_count': 5, # if profiling enabled
180+
'correlation': { ... }, # if correlation enabled
181+
'low_cardinality': { ... }, # if correlation enabled
179182
}
180183
181184
@@ -347,6 +350,26 @@ Configuration Reference
347350
- str
348351
- ``None``
349352
- Header name to read tracing ID from
353+
* - ``DRF_API_LOGGER_ENABLE_CORRELATION``
354+
- bool
355+
- ``False``
356+
- Enable request correlation metadata without adding database columns
357+
* - ``DRF_API_LOGGER_CORRELATION_REQUEST_ID_HEADERS``
358+
- list
359+
- ``['X-Request-ID', 'X-Correlation-ID']``
360+
- Headers checked for inbound request IDs
361+
* - ``DRF_API_LOGGER_CORRELATION_TRACE_ID_HEADERS``
362+
- list
363+
- ``['traceparent', 'X-Trace-ID']``
364+
- Headers checked for W3C traceparent or trace IDs
365+
* - ``DRF_API_LOGGER_CORRELATION_CONTEXT_FUNC``
366+
- str
367+
- ``None``
368+
- Dotted-path callback returning allowlisted opaque context IDs
369+
* - ``DRF_API_LOGGER_ENABLE_LOGGING_CONTEXT``
370+
- bool
371+
- ``False``
372+
- Expose correlation metadata through a ContextVar during the view call
350373
* - ``DRF_API_LOGGER_CUSTOM_HANDLER``
351374
- str
352375
- ``None``
@@ -440,6 +463,79 @@ Read tracing ID from request header:
440463
DRF_API_LOGGER_TRACING_ID_HEADER_NAME = 'X-Trace-ID'
441464
442465
466+
Request Correlation
467+
===================
468+
469+
Enable request correlation to connect API logger events with application logs,
470+
upstream gateways, distributed traces, or metrics labels:
471+
472+
.. code-block:: python
473+
474+
DRF_API_LOGGER_ENABLE_CORRELATION = True
475+
DRF_API_LOGGER_CORRELATION_REQUEST_ID_HEADERS = ["X-Request-ID", "X-Correlation-ID"]
476+
DRF_API_LOGGER_CORRELATION_TRACE_ID_HEADERS = ["traceparent", "X-Trace-ID"]
477+
DRF_API_LOGGER_ENABLE_LOGGING_CONTEXT = True
478+
479+
Correlation metadata is intentionally not stored in ``APILogsModel``. Enabling
480+
it does not add model fields, migrations, admin columns, database indexes, or
481+
synthetic fields in queued database log rows.
482+
483+
When enabled, request handlers can read:
484+
485+
- ``request.api_logger_correlation``
486+
- ``request.api_logger_low_cardinality``
487+
- ``request.api_logger_request_id``
488+
- ``request.api_logger_trace_id``
489+
- ``drf_api_logger.logging_context.get_correlation_context()``
490+
491+
Signal listeners receive ``correlation`` and ``low_cardinality`` keys. Use the
492+
low-cardinality dictionary for metrics tags such as route, URL name, and status
493+
class. Keep high-cardinality request and trace IDs in logs or trace systems, not
494+
metrics labels.
495+
496+
.. code-block:: python
497+
498+
from drf_api_logger import API_LOGGER_SIGNAL
499+
500+
def forward_api_event(**kwargs):
501+
labels = kwargs.get("low_cardinality", {})
502+
correlation = kwargs.get("correlation", {})
503+
metrics.count(
504+
"drf_api_logger.request",
505+
tags={
506+
"route": labels.get("route"),
507+
"status_class": labels.get("status_class"),
508+
},
509+
)
510+
app_logger.info(
511+
"api request observed",
512+
extra={
513+
"request_id": correlation.get("request_id"),
514+
"trace_id": correlation.get("trace_id"),
515+
},
516+
)
517+
518+
API_LOGGER_SIGNAL.listen += forward_api_event
519+
520+
Add opaque, non-sensitive context through an allowlisted callback:
521+
522+
.. code-block:: python
523+
524+
DRF_API_LOGGER_CORRELATION_CONTEXT_FUNC = "myapp.logging.api_logger_context"
525+
526+
def api_logger_context(request):
527+
return {
528+
"actor_id": getattr(request.user, "pk", None),
529+
"tenant_id": getattr(request, "tenant_id", None),
530+
"api_consumer_id": getattr(request, "api_consumer_id", None),
531+
"client_id": getattr(request, "client_id", None),
532+
}
533+
534+
Only ``actor_id``, ``tenant_id``, ``api_consumer_id``, and ``client_id`` are
535+
accepted from the callback. Do not return names, emails, tokens, or other
536+
identifying values.
537+
538+
443539
Path Configuration
444540
==================
445541

docs/operations.rst

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,24 @@ Important fields:
110110
``inserted_count``
111111
Number of rows successfully inserted by the current worker.
112112

113+
Request Correlation Operations
114+
------------------------------
115+
116+
``DRF_API_LOGGER_ENABLE_CORRELATION`` is designed for joining DRF API Logger
117+
events with application logs, traces, and metrics without changing database
118+
storage. It does not add model fields, migrations, admin columns, or database
119+
indexes, and queued database log rows keep the existing payload shape.
120+
121+
Recommended operating pattern:
122+
123+
- Send ``low_cardinality`` signal metadata to metrics labels, such as route,
124+
URL name, and status class.
125+
- Keep high-cardinality ``request_id`` and ``trace_id`` values in logs or trace
126+
systems, not metrics label sets.
127+
- Use ``DRF_API_LOGGER_ENABLE_LOGGING_CONTEXT`` when application log records
128+
inside the view should include the same request correlation metadata.
129+
- Return only opaque IDs from ``DRF_API_LOGGER_CORRELATION_CONTEXT_FUNC``.
130+
113131
Database Indexes
114132
----------------
115133

docs/quickstart.rst

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,57 @@ Or accept an upstream request ID:
119119
DRF_API_LOGGER_ENABLE_TRACING = True
120120
DRF_API_LOGGER_TRACING_ID_HEADER_NAME = "X-Request-ID"
121121
122+
Request Correlation Without New DB Columns
123+
------------------------------------------
124+
125+
Use correlation when the application needs request IDs, W3C ``traceparent``
126+
trace IDs, route metadata, or metrics labels without adding fields to the
127+
``drf_api_logs`` table.
128+
129+
.. code-block:: python
130+
131+
DRF_API_LOGGER_SIGNAL = True
132+
DRF_API_LOGGER_ENABLE_CORRELATION = True
133+
DRF_API_LOGGER_CORRELATION_REQUEST_ID_HEADERS = ["X-Request-ID", "X-Correlation-ID"]
134+
DRF_API_LOGGER_CORRELATION_TRACE_ID_HEADERS = ["traceparent", "X-Trace-ID"]
135+
DRF_API_LOGGER_ENABLE_LOGGING_CONTEXT = True
136+
137+
Correlation data is exposed on the request object, in
138+
``drf_api_logger.logging_context.get_correlation_context()``, and in signal
139+
payload keys named ``correlation`` and ``low_cardinality``. It is not written to
140+
``APILogsModel`` and does not require migrations, model fields, admin columns,
141+
or database indexes.
142+
143+
.. code-block:: python
144+
145+
from drf_api_logger import API_LOGGER_SIGNAL
146+
147+
def send_api_metrics(**kwargs):
148+
labels = kwargs.get("low_cardinality", {})
149+
metrics.count(
150+
"drf_api_logger.request",
151+
tags={
152+
"route": labels.get("route"),
153+
"status_class": labels.get("status_class"),
154+
},
155+
)
156+
157+
API_LOGGER_SIGNAL.listen += send_api_metrics
158+
159+
For extra context, return only opaque IDs from an allowlisted callback:
160+
161+
.. code-block:: python
162+
163+
DRF_API_LOGGER_CORRELATION_CONTEXT_FUNC = "myapp.logging.api_logger_context"
164+
165+
def api_logger_context(request):
166+
return {
167+
"actor_id": getattr(request.user, "pk", None),
168+
"tenant_id": getattr(request, "tenant_id", None),
169+
"api_consumer_id": getattr(request, "api_consumer_id", None),
170+
"client_id": getattr(request, "client_id", None),
171+
}
172+
122173
Retention and Pruning
123174
---------------------
124175

docs/tutorials.rst

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,41 @@ Log the same ID in application code:
134134
tracing_id = getattr(request, "tracing_id", "missing")
135135
logger.info("example_view started", extra={"tracing_id": tracing_id})
136136
137+
Correlate Logs Without Persisting IDs
138+
-------------------------------------
139+
140+
Use request correlation when application logs, gateway IDs, and metrics need to
141+
line up, but the log table should keep its existing schema.
142+
143+
.. code-block:: python
144+
145+
DRF_API_LOGGER_SIGNAL = True
146+
DRF_API_LOGGER_ENABLE_CORRELATION = True
147+
DRF_API_LOGGER_ENABLE_LOGGING_CONTEXT = True
148+
DRF_API_LOGGER_CORRELATION_REQUEST_ID_HEADERS = ["X-Request-ID", "X-Correlation-ID"]
149+
DRF_API_LOGGER_CORRELATION_TRACE_ID_HEADERS = ["traceparent", "X-Trace-ID"]
150+
151+
.. code-block:: python
152+
153+
from drf_api_logger import API_LOGGER_SIGNAL
154+
155+
def send_metrics(**kwargs):
156+
labels = kwargs.get("low_cardinality", {})
157+
metrics.count(
158+
"drf_api_logger.request",
159+
tags={
160+
"route": labels.get("route"),
161+
"status_class": labels.get("status_class"),
162+
},
163+
)
164+
165+
API_LOGGER_SIGNAL.listen += send_metrics
166+
167+
Correlation adds ``correlation`` and ``low_cardinality`` to signal payloads and
168+
sets request attributes such as ``request.api_logger_request_id``. It does not
169+
add model fields, migrations, admin columns, database indexes, or extra queued
170+
database payload fields.
171+
137172
Community Snippets
138173
------------------
139174

0 commit comments

Comments
 (0)