Structured security and audit logging for Django with OpenTelemetry LogRecord-shaped JSONL output
Project description
django-sec-audit
Structured security and audit logging for Django, emitting OpenTelemetry LogRecord-shaped JSONL events.
Captures HTTP request/response metadata, auth events (login, logout, failures), model changes (via django-auditlog), and DRF view metadata out of the box.
Features
- HTTP middleware — automatic capture of requests, responses, status codes, timing, client IP, routes, DRF metadata
- Auth signals — automatic logging of
user_logged_in,user_logged_out,user_login_failed - Model forwarding — forward django-auditlog entries as structured audit events (optional
[model]extra) - DRF integration — auto-detects
drf_action,drf_view_class, serializer, auth/permission classes (optional[drf]extra) - Request body capture — opt-in JSON body capture with scrubbing and size limits
- OTel JSONL output — every event is a single JSON line following the OTel LogRecord envelope, ready for Loki/Grafana
- Pluggable pipeline — custom filters and enrichers run before emission
Dependencies
| Package | Role |
|---|---|
django-sec-audit (this) |
Django integration |
sec-audit |
Core (events, context, IP resolution, scrubbing) |
sec-audit-logging |
Logging runtime, formatters, SIEM handlers |
Quick Start
# settings.py
INSTALLED_APPS = [
'sec_audit.django.apps.SecAuditConfig', # early, before your apps
# ...
]
MIDDLEWARE = [
# ... Django security middleware
'django.contrib.sessions.middleware.SessionMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'sec_audit.django.middleware.AuditMiddleware', # last or near-last
]
SEC_AUDIT = {
'core': {
'source': 'myapp', # appears as resource.service.name
'log_request_bodies': False, # opt-in
'log_ok_responses': False, # only 4xx/5xx by default
},
'logging': {
'schema_version': '1.0',
},
'django': {
'include_usernames': False, # opt-in for GDPR considerations
},
}
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'formatters': {
# Injects the resolved SEC_AUDIT config at construction.
'audit_jsonl': {
'()': 'sec_audit.django.logging.formatters.audit_jsonl_formatter',
},
},
'handlers': {
'audit_stdout': {
'class': 'logging.StreamHandler',
'formatter': 'audit_jsonl',
},
},
'loggers': {
'sec_audit.audit': {
'handlers': ['audit_stdout'],
'level': 'INFO',
'propagate': False,
},
},
}
Stdout is the supported delivery path (stdout JSONL -> Grafana Alloy -> Loki).
For local file output, swap the handler for a stdlib
logging.handlers.RotatingFileHandler using the same audit_jsonl formatter.
Usage Guide
For a step-by-step setup and configuration walkthrough, see docs/how-to-use.md.
Monitoring with Loki/Grafana
Audit events are OTel JSONL, ready to ship to Loki (sec_audit.audit JSONL → Grafana
Alloy → Loki → Grafana). The bundled sec-audit-loki-init command generates the whole
local stack — see
docs/loki-setup.md.
Configuration
All settings live under the SEC_AUDIT dict in settings.py. Three sections are recognised:
core (CoreAuditConfig)
| Setting | Default | Description |
|---|---|---|
source |
'sec-audit' |
Service name in resource.service.name |
ignore_paths |
() |
Regex patterns; matching paths are skipped |
ignore_status_codes |
frozenset() |
Status code values to skip (e.g. {301, 302}) |
sample_rate |
1.0 |
Sampling rate for successful (2xx) responses |
log_ok_responses |
False |
Enable logging for 2xx responses |
log_request_bodies |
False |
Enable request body capture |
log_body_paths |
() |
Regex patterns; only matching paths capture bodies |
body_methods |
{'POST', 'PUT', 'PATCH'} |
HTTP methods eligible for body capture |
max_body_bytes |
4096 |
Maximum JSON body size in bytes |
sensitive_keys |
DEFAULT_SENSITIVE_KEYS |
Built-in key patterns to scrub (incl. password, secret, token, apikey, etc.) |
sensitive_key_allowlist |
() |
Exact (compacted) field names never redacted, even when a sensitive_keys substring matches — e.g. credit_card_last4, token_expiry |
sensitive_value_patterns |
() |
Regex patterns matching sensitive values to scrub |
logging (LoggingAuditConfig)
| Setting | Default | Description |
|---|---|---|
schema_version |
'1.0' |
Schema version string in every event |
projection_limits |
ProjectionLimits() |
Bounds for dict/list nesting and string sizes (accepts a dict or ProjectionLimits) |
File-rotation parameters (maxBytes/backupCount/filename) are configured on
the handler in Django's LOGGING dict, not here.
django (DjangoAuditConfig)
| Setting | Default | Description |
|---|---|---|
include_usernames |
False |
Include user.name in events (opt-in for GDPR) |
trusted_proxy_cidrs |
() |
CIDR ranges considered trusted proxies |
trusted_proxy_count |
None |
Number of trusted proxies (leftmost N IPs are strip) |
emit_session_id |
False |
Emit correlated session.id in events (opt-in; writes to request.session) |
filters |
() |
Dotted paths to filter callable/classes |
enrichers |
() |
Dotted paths to enricher callable/classes |
Event Types
auth.login.success auth.login.failed
auth.logout.success auth.logout.unknown
http.response.success http.response.client_error http.response.server_error
model.create model.update model.delete model.access
Output Format
Each event is a single JSON line:
{
"timestamp": 1712345678000000000,
"observed_timestamp": 1712345678000000000,
"severity_text": "WARNING",
"severity_number": 13,
"body": "http.response",
"resource": { "service.name": "sec-audit" },
"instrumentation_scope": { "name": "sec_audit.django.middleware", "version": "1.0.0" },
"attributes": {
"event_type": "http.response.client_error",
"source.address": "203.0.113.10",
"http.request.method": "POST",
"http.response.status_code": 404,
"url.full": "https://example.test/api/transfer",
"url.path": "/api/transfer",
"user.id": "42"
},
"event_name": "http.response.client_error"
}
Optional Extras
| Extra | Dependencies |
|---|---|
pip install django-sec-audit[model] |
django-auditlog |
pip install django-sec-audit[drf] |
djangorestframework |
pip install django-sec-audit[full] |
Both |
Development
pip install -e "packages/django-sec-audit[dev]"
pytest
ruff check .
License
MIT
Project details
Release history Release notifications | RSS feed
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_sec_audit-0.1.0a1.tar.gz.
File metadata
- Download URL: django_sec_audit-0.1.0a1.tar.gz
- Upload date:
- Size: 40.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.9.6
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
48e9ff7d696f59795129eab0c98b037b16827cda8798be6405e860c0715db12a
|
|
| MD5 |
b2a71f619584f34ea35074bf737059bb
|
|
| BLAKE2b-256 |
003ce1db800676ebb886d3124df714d20f94ea1245c523dfe18b795674ee194e
|
File details
Details for the file django_sec_audit-0.1.0a1-py3-none-any.whl.
File metadata
- Download URL: django_sec_audit-0.1.0a1-py3-none-any.whl
- Upload date:
- Size: 28.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.9.6
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
795e92f01dd03d1e35676a945d530222d38b4c7406635afe755ff22ae9772ebd
|
|
| MD5 |
13e4732caeef94fbb9cf3e53973f4848
|
|
| BLAKE2b-256 |
8ab80d3d626d5107ba9fe223bd30fb4eb00a0795e172415949287ae98c9ff3d7
|