Skip to main content

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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

django_sec_audit-0.1.0a1.tar.gz (40.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

django_sec_audit-0.1.0a1-py3-none-any.whl (28.3 kB view details)

Uploaded Python 3

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

Hashes for django_sec_audit-0.1.0a1.tar.gz
Algorithm Hash digest
SHA256 48e9ff7d696f59795129eab0c98b037b16827cda8798be6405e860c0715db12a
MD5 b2a71f619584f34ea35074bf737059bb
BLAKE2b-256 003ce1db800676ebb886d3124df714d20f94ea1245c523dfe18b795674ee194e

See more details on using hashes here.

File details

Details for the file django_sec_audit-0.1.0a1-py3-none-any.whl.

File metadata

File hashes

Hashes for django_sec_audit-0.1.0a1-py3-none-any.whl
Algorithm Hash digest
SHA256 795e92f01dd03d1e35676a945d530222d38b4c7406635afe755ff22ae9772ebd
MD5 13e4732caeef94fbb9cf3e53973f4848
BLAKE2b-256 8ab80d3d626d5107ba9fe223bd30fb4eb00a0795e172415949287ae98c9ff3d7

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page