Skip to main content

Foxtrot November shared audit-logging library for Django/DRF micro-services

Project description

ftn-audit

Shared Django/DRF audit logging library with asynchronous OTLP delivery via Celery.

What this package includes

  • Request and user context capture via middleware (AuditContextMiddleware)
  • Late actor hydration in receivers when middleware context has no user yet
  • Signal receivers (pre_save, post_save, post_delete, m2m_changed)
  • Formatter registry with route-aware and event-aware selection
  • Generic formatter fallback when no custom formatter is registered
  • Delivery strategies (NoopAuditStrategy, OtlpAuditStrategy)
  • Celery task for OTLP emission
  • Unit and integration tests with pytest + pytest-django

Requirements

  • Python 3.11+
  • pip

Minimal Django integration

1) Install app and middleware

# settings.py
INSTALLED_APPS = [
    # ...
    "ftn_audit.apps.FtnAuditConfig",
]

MIDDLEWARE = [
    # ...
    "ftn_audit.middleware.AuditContextMiddleware",
]

2) Configure Celery task discovery

# celery.py
from celery import Celery

app = Celery("service_name")
app.config_from_object("django.conf:settings", namespace="CELERY")
app.autodiscover_tasks()

3) Register formatters in each app

# my_app/audit_formatters.py
from ftn_audit import register_audit_formatter, AbstractAuditLogFormatter
from .models import MyModel


@register_audit_formatter(MyModel)
class MyFormatter(AbstractAuditLogFormatter):
    def get_description(self) -> str:
        return f"Updated MyModel #{self.subject.pk}"

    def get_attributes(self):
        attrs = self.get_default_attributes()
        attrs["my.attr"] = "value"
        return attrs

Formatter resolution order:

  1. (raw_route + event_type)
  2. (raw_route)
  3. (event_type)
  4. (default)

priority is only applied inside the same specificity tier.

4) Recommended settings

AUDIT_ENABLED = True
AUDIT_DELIVERY_MODE = "celery"  # celery | sync | noop
AUDIT_STRICT_FORMATTER_VALIDATION = True
AUDIT_AUTO_CONNECT_SIGNALS = True  # optional, default True
AUDIT_AUTO_DISCOVER_FORMATTERS = True  # optional, default True
AUDIT_CHANGESET_CHECK_RELATIONSHIP = True  # optional, include FK changes in changeset

Migration notes

  • Foreign-key auditing: AUDIT_CHANGESET_CHECK_RELATIONSHIP defaults to True. Set it to False only if your service explicitly wants to skip FK changes in update changesets.
  • Formatter raw_route compatibility: middleware now emits canonical placeholders (:name). Update formatter raw_route= registrations from legacy forms (<type:name>, (?P<name>...)) to :name.
  • Import path rename: ftn_audit.jobs was renamed to ftn_audit.tasks. Update imports, e.g. from ftn_audit.tasks import emit_audit_log_task.

Error handling and safeguards

  • Audit code should never break request execution: all middleware/receiver/strategy exceptions are caught and logged.
  • Receivers resolve actor as best-effort: if AuditContext.user_id is empty, actor fields are hydrated from current request.user when available.
  • Enqueue failures are logged and dropped.
  • OTLP emission failures in Celery use bounded retries.
  • After max retries, the task logs an error and drops the event.
  • OTLP enqueue is registered via transaction.on_commit, so events are emitted only after commit.
  • Signal handlers ignore raw=True fixture loads to avoid spurious audit events.
  • Changesets are filtered by update_fields when present, so only persisted fields are audited.
  • M2M payload safety: PK sets are capped (M2M_PK_SET_MAX) to avoid oversized OTLP payloads.
  • Celery audit task uses ignore_result=True to avoid unnecessary result-backend writes.
  • Celery audit task uses late acknowledgements and reject_on_worker_lost=True to improve redelivery on worker crash.

Known limitations

  • ContextVars do not cross process boundaries (e.g. Celery workers). For background jobs, pass actor metadata explicitly and set AuditContext in task scope.
  • AUDIT_OTLP_FALLBACK is reserved but not currently enforced by runtime behavior.
  • Django bulk operations bypass per-instance signals (bulk_create, bulk_update, queryset update, queryset delete). If needed, add explicit auditing at service-layer boundaries.
  • Reverse-side M2M modifications (reverse=True) are not audited by default. This can be extended with explicit configuration if your domain needs it.
  • One-to-many collection semantics should be expressed through child create/update/delete formatters (FK changes). There is no dedicated O2M signal registry yet.

Install

python3 -m venv .venv
source .venv/bin/activate
pip install -U pip
pip install -e .[test]

Makefile workflow

Bootstrap environment:

make bootstrap

Run tests:

make test

Shortcuts:

  • make test-unit
  • make test-integration
  • make docker-build
  • make docker-test

Test commands

Run all tests:

pytest -q

Run focused unit tests:

pytest -q tests/test_middleware.py tests/test_strategy.py tests/test_receiver_unit.py tests/test_tasks.py

Run integration tests:

pytest -q tests/test_receiver_integration.py

Minimum test coverage checklist

Shared library tests cover:

  • autodiscovery imports both audit_formatters.py modules and audit_formatters packages
  • middleware canonical raw_route
  • registry selection (raw_route match -> default -> generic)
  • M2M receiver firing only for allowlisted collections
  • commit safety (rollback prevents enqueue)
  • enqueue failure (log + drop)
  • OTLP task retries are bounded

Microservice-level expectations:

  • formatter unit tests for key routes
  • startup integration test verifies registrations are loaded into registry

Acceptance criteria status

  • No explicit audit calls in business logic: yes (signal + middleware driven).
  • Audit events include raw_route and user metadata when available: yes.
  • Formatter selection is based on HTTP verb + route template: yes (raw_route = METHOD|/template).
  • M2M relations are audited only when allowlisted (@register_audit_collection): yes.
  • OTLP emission happens only after commit: yes (transaction.on_commit).
  • Audit failures never break the request path: yes (exceptions are swallowed and logged).

Notes

  • Tests use minimal Django settings from tests/settings.py.
  • Test app is under tests/test_app.
  • OTLP network emission is mocked in tests (emit_audit_log_task.delay).

Docker

Build image:

docker compose build

Run tests in container:

docker compose run --rm test

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

ftn_audit-1.0.0b4.tar.gz (31.7 kB view details)

Uploaded Source

Built Distribution

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

ftn_audit-1.0.0b4-py3-none-any.whl (27.4 kB view details)

Uploaded Python 3

File details

Details for the file ftn_audit-1.0.0b4.tar.gz.

File metadata

  • Download URL: ftn_audit-1.0.0b4.tar.gz
  • Upload date:
  • Size: 31.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.3

File hashes

Hashes for ftn_audit-1.0.0b4.tar.gz
Algorithm Hash digest
SHA256 8c2ac50f1943bfcc46dddc8da33beada7a41d660db304b793e1f616fe5bf4309
MD5 db3c7d2ebfcde1e2be64b58e229acc30
BLAKE2b-256 5ddb1f06e4676f5620dd0bf589de9915814ded2749a4a2120cfe9caac3819e48

See more details on using hashes here.

File details

Details for the file ftn_audit-1.0.0b4-py3-none-any.whl.

File metadata

  • Download URL: ftn_audit-1.0.0b4-py3-none-any.whl
  • Upload date:
  • Size: 27.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.3

File hashes

Hashes for ftn_audit-1.0.0b4-py3-none-any.whl
Algorithm Hash digest
SHA256 4fc448700da06a1bd0990b0818960f725cfe17c839645ab14f446bd19d59045c
MD5 1e6dc244f0ca77ce5a0fd5e288200db7
BLAKE2b-256 386c712abdbf765370286fa2f60c826e14e684650e9f928a853a4a29b16378e7

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