A Django application that logs requests and responses to your project for auditing or troubleshooting purposes.
Project description
Django Rapyd Wiretap
A Django middleware that logs HTTP requests and responses to your database for auditing or troubleshooting purposes. Unlike similar packages, it works in production — logging is not gated on settings.DEBUG.
Inspired by nathforge/django-wiretap.
Install
pip install django-rapyd-wiretap
Quickstart
-
Add
wiretaptoINSTALLED_APPS:INSTALLED_APPS = [ ... "wiretap", ]
-
Add the middleware to
MIDDLEWARE:MIDDLEWARE = [ ... "wiretap.middleware.WiretapMiddleware", ]
-
Apply migrations:
python manage.py migrate
Usage
In the Django admin, create a Tap to configure which requests to capture:
- Path — a Python regex matched against the full request path with
re.search. - Is active — deactivate a tap without deleting it.
Each matched request is stored as a Message with the request method, path, headers, body, response status, and timing information. Messages are read-only in the admin.
Tap recipes
| Goal | Path regex |
|---|---|
Capture all /api/ traffic |
^/api/ |
| Capture a specific endpoint exactly | ^/api/v1/payments/$ |
| Capture a versioned endpoint (any version) | ^/api/v\d+/payments/ |
| Capture everything | / |
Capture everything except /admin/ and /static/ |
^/(?!admin/|static/) |
| Capture POSTs to webhooks (combine with another middleware filtering by method) | ^/webhooks/ |
The path regex is matched with re.search, not re.fullmatch — anchor with ^ if you want a prefix match, and add $ if you want an exact match. Query strings are not part of the path; they're available on Message.request_path only if Django's path router includes them (it normally doesn't).
Reading captured messages
Each Message row has the following fields:
| Field | Description |
|---|---|
started_at / ended_at |
When request logging began and response logging finished (UTC, indexed). |
duration |
Whole seconds between started_at and ended_at (indexed). For sub-second precision, compute from the timestamps directly. |
remote_addr |
Client IP from REMOTE_ADDR (indexed). If you sit behind a proxy, install django.middleware.common.CommonMiddleware-style IP unwrapping before WiretapMiddleware. |
request_method |
GET, POST, etc. (indexed). |
request_path |
Full request path (no query string). |
request_headers_json / response_headers_json |
JSON-encoded header dicts. Use the request_headers / response_headers properties to get a dict. |
request_body_raw / response_body_raw |
UTF-8-decoded body. Set to empty string for empty bodies; non-UTF-8 bodies are skipped (the row is still saved). |
request_body_pretty / response_body_pretty |
JSON-pretty-printed body, populated only when the corresponding Content-Type contains json and the body parses. NULL otherwise. |
response_status_code / response_reason_phrase |
HTTP status (indexed). |
Helper methods on Message:
message.get_request_header("Content-Type") # raises KeyError if missing
message.get_request_header("X-Custom", default=None) # returns default if missing
message.get_response_header("Location", "")
Header lookups are case-insensitive (titled-cased internally).
Operations
Pruning old messages
Message rows accumulate forever once a Tap is active. A 100 RPS endpoint matched by ^/api/ produces ~8.6M rows/day — plan storage accordingly and prune on a schedule.
# delete messages older than 30 days
python manage.py wiretap_prune --older-than-days 30
# preview without deleting
python manage.py wiretap_prune --older-than-days 30 --dry-run
Run as a daily cron / scheduled job. The command issues a single bulk DELETE filtered on started_at.
Middleware ordering
Place WiretapMiddleware near the top of MIDDLEWARE, after security/common middleware but before any middleware that mutates request bodies or response content. The earlier it sits, the closer the captured payload is to the on-the-wire form.
If you sit behind a load balancer or reverse proxy, install IP-unwrapping middleware (e.g., django.middleware.common.CommonMiddleware plus USE_X_FORWARDED_HOST, or a dedicated package) before WiretapMiddleware, so remote_addr reflects the real client IP rather than the proxy's.
Indexed fields and ad-hoc queries
started_at, ended_at, duration, remote_addr, request_method, response_status_code, and response_reason_phrase are indexed. Filtering/ordering by those scales. Filtering by request_path (a TextField) on a large table will table-scan; if you do this often, add a custom index in your project's migrations.
Sensitive data
Wiretap captures everything in matched requests, including Authorization and Cookie headers, request/response bodies, and any tokens or PII passing through. Tap conservatively, and keep the Message table on storage with the same threat model as your secrets store.
Built-in opt-in redaction is on the roadmap — see issue #10.
Supported versions
| Package | Versions |
|---|---|
| Python | 3.10, 3.11, 3.12, 3.13 |
| Django | 4.2 LTS, 5.2 LTS |
Contributing
See CONTRIBUTING.md for development setup, test commands, and the release process.
License
Apache 2.0. See LICENSE.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file django_rapyd_wiretap-0.2.0.tar.gz.
File metadata
- Download URL: django_rapyd_wiretap-0.2.0.tar.gz
- Upload date:
- Size: 87.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
42a2a79c0e2cbae6bbfb6a4e155402295e7f64722ff6b8cde15fbb6ef35eae80
|
|
| MD5 |
a27cd94b65ee429f97436eed035e1ea2
|
|
| BLAKE2b-256 |
a49c5d109e94d4eae0640edf8e550f08f3b5f12030615ec6adc2b5c6f84ce173
|
Provenance
The following attestation bundles were made for django_rapyd_wiretap-0.2.0.tar.gz:
Publisher:
publish.yml on karthicraghupathi/django_rapyd_wiretap
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
django_rapyd_wiretap-0.2.0.tar.gz -
Subject digest:
42a2a79c0e2cbae6bbfb6a4e155402295e7f64722ff6b8cde15fbb6ef35eae80 - Sigstore transparency entry: 1486239751
- Sigstore integration time:
-
Permalink:
karthicraghupathi/django_rapyd_wiretap@ed3ef4c3e966317971424dc9eb1a2eb9fbbb9dba -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/karthicraghupathi
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@ed3ef4c3e966317971424dc9eb1a2eb9fbbb9dba -
Trigger Event:
push
-
Statement type:
File details
Details for the file django_rapyd_wiretap-0.2.0-py3-none-any.whl.
File metadata
- Download URL: django_rapyd_wiretap-0.2.0-py3-none-any.whl
- Upload date:
- Size: 18.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1ca0d046416bbdbb47674797dbaa1d07665a88044a75a0f3b6b80ff2c7f2b036
|
|
| MD5 |
f922de4eb651c31a07e17b3597feba6e
|
|
| BLAKE2b-256 |
721a65d9350cb105fb99fec6c75f0bc08a2148906046bcac9fd6479b9d3ab802
|
Provenance
The following attestation bundles were made for django_rapyd_wiretap-0.2.0-py3-none-any.whl:
Publisher:
publish.yml on karthicraghupathi/django_rapyd_wiretap
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
django_rapyd_wiretap-0.2.0-py3-none-any.whl -
Subject digest:
1ca0d046416bbdbb47674797dbaa1d07665a88044a75a0f3b6b80ff2c7f2b036 - Sigstore transparency entry: 1486239786
- Sigstore integration time:
-
Permalink:
karthicraghupathi/django_rapyd_wiretap@ed3ef4c3e966317971424dc9eb1a2eb9fbbb9dba -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/karthicraghupathi
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@ed3ef4c3e966317971424dc9eb1a2eb9fbbb9dba -
Trigger Event:
push
-
Statement type: