Skip to main content

Auditrum — a PostgreSQL audit system with rich contextual logging and Django integration

Project description

#auditrum

A PostgreSQL audit system for tracking database changes with rich contextual information, featuring seamless Django integration.


✨ Features

  • Automatic Change Tracking – PostgreSQL triggers to log all INSERT, UPDATE, and DELETE
  • Rich Context – Who, when, why, and from where
  • Partitioned Storage – Time-partitioned audit table
  • Django Integration – Models, admin, middleware
  • Flexible Configuration – Track/include/exclude fields, add conditions
  • Performance Optimized – Minimal database overhead

📦 Installation

With pip

pip install auditrum

To include Django support:

pip install auditrum[django]

With uv

uv add auditrum
uv add auditrum[django]

🚀 Quick Start

🔧 With Django

  1. Add to INSTALLED_APPS:
INSTALLED_APPS = [
    # ...
    'auditrum.integrations.django',
]
  1. Add middleware:
MIDDLEWARE = [
    # ...
    'auditrum.integrations.django.middleware.RequestIDMiddleware',
    'auditrum.integrations.django.middleware.AuditrumMiddleware',
]
  1. Register models:

Create a new audit.py file inside your Django app (e.g., yourapp/audit.py) and register models there:

# yourapp/audit.py

from auditrum.integrations.django.audit import register
from .models import User

register(User, track_only=["name", "email"])

The auditrum integration will automatically discover and execute this file (like admin.py), so no need to import it manually.

  1. Run migrations:
python manage.py migrate

This will create the audit table and set up triggers automatically.

  1. Set up monthly partitions:

Add a cron job to run the following command on the last day of each month. This will ensure partitions exist for upcoming months:

python manage.py audit_add_partitions --months 3

This will create audit table partitions for the next 3 months.

⏰ Cron Job Example

To automatically run the partition creation every month (e.g. on the 28th at 23:50), add the following cron entry:

50 23 28 * * /path/to/your/venv/bin/python /path/to/your/project/manage.py audit_add_partitions --months 3
  • Replace /path/to/your/venv/bin/python with the full path to your virtualenv's Python interpreter.
  • Replace /path/to/your/project/manage.py with the path to your project's manage.py file.
  • Adjust the --months 3 argument as needed to define how far ahead partitions should be created.
  1. Enable audit context for management commands (optional)

To track changes made via manage.py commands (e.g. migrate, shell) with proper source and change_reason in audit logs, you can modify your manage.py like this:

# manage.py

from auditrum import audit_tracked  # noqa (re-export from auditrum.utils)


def main():
    """Run administrative tasks."""
    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "your_app.settings")

    AUDIT_COMMAND_SOURCES = {
        "shell": "shell",
        "migrate": "migration",
        "makemigrations": "migration",
    }

    cmd = sys.argv[1] if len(sys.argv) > 1 else None
    audit_source = AUDIT_COMMAND_SOURCES.get(cmd)

    if audit_source:
        try:
            audit_tracked(source=audit_source).__enter__()
        except Exception as e:
            print(f"[audit] failed to enable tracking for source='{audit_source}': {e}")

    if cmd == "shell":
        try:
            import apps.audit.shell_context  # noqa
        except ImportError:
            print("[audit] Warning: shell_context not found. Skipping shell audit.")

    ...

This ensures your migrations and shell activity are properly attributed in the audit log.


🧩 Without Django

from auditrum.schema import generate_auditlog_table_sql
from auditrum.triggers import generate_trigger_sql
import psycopg

with psycopg.connect("your_connection_string") as conn, conn.cursor() as cursor:
    # Create audit table
    cursor.execute(generate_auditlog_table_sql("auditlog"))

    # Create trigger for a table
    sql = generate_trigger_sql(
        table_name="users",
        track_only=["name", "email"]
    )
    cursor.execute(sql)
    conn.commit()

⚙️ Configuration Examples

✅ Track only specific fields

register(User, track_only=["name", "email"])

❌ Exclude specific fields

register(Product, exclude_fields=["created_at", "updated_at"])

📐 Add conditions

register(Subscription, log_conditions="NEW.is_active = TRUE")

📚 Adding Context to Changes

Using decorator:

from auditrum.context import with_change_reason


@with_change_reason("User requested password reset")
def reset_password(user_id):
    ...

Using context manager:

from auditrum.context import audit_context

with audit_context.use_change_reason("Bulk update for compliance"):
    ...

🛡️ Hardening Guide

For compliance-sensitive deployments, the audit log should be append-only and tamper-detectable. auditrum ships three building blocks that can be enabled independently:

1. Revoke mutating privileges (append-only)

auditrum harden --app-role myapp --admin-role myapp_admin

This runs REVOKE UPDATE, DELETE, TRUNCATE ON auditlog FROM PUBLIC, myapp and grants full privileges to a dedicated myapp_admin role. After this step the regular application role can only append audit rows; maintenance (partition drops, retention) must run as myapp_admin.

2. Retention / purge

Delete rows older than a cutoff or drop entire month partitions:

# DELETE-based (fine for smaller tables)
auditrum purge --older-than '2 years'

# Partition-based (fast, WAL-friendly)
auditrum purge --older-than '2 years' --drop-partitions

3. Tamper detection via SHA-256 hash chain

Enable optional row hashing + a chain of prev_hash pointers:

auditrum enable-hash-chain

BEFORE INSERT triggers on the audit log compute a SHA-256 of (id, changed_at, operation, table_name, old_data, new_data, prev_hash) using pgcrypto. Writes are serialized via pg_advisory_xact_lock, so peak insert throughput drops — pair this with careful benchmarking if your workload is high-write. To check integrity:

auditrum verify-chain

This runs a server-side recomputation across the whole log and reports any rows whose stored row_hash or prev_hash disagrees with the expected value.


📖 Documentation

Full documentation lives under docs/ and renders directly on GitHub. Start with:

Changelog: CHANGELOG.md Roadmap to 1.0: ROADMAP.md


🪪 License

This project is licensed under the MIT License. See the LICENSE file for details.

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

auditrum-0.4.0.tar.gz (108.0 kB view details)

Uploaded Source

Built Distribution

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

auditrum-0.4.0-py3-none-any.whl (87.4 kB view details)

Uploaded Python 3

File details

Details for the file auditrum-0.4.0.tar.gz.

File metadata

  • Download URL: auditrum-0.4.0.tar.gz
  • Upload date:
  • Size: 108.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.11.15

File hashes

Hashes for auditrum-0.4.0.tar.gz
Algorithm Hash digest
SHA256 f9edf8866a1bbc5b1abdedb7d22c226ecd1a2309f6dee95b7f3d1891c5edaa33
MD5 85d68dba04d68ac92df83b76dfef747c
BLAKE2b-256 91272dfc623049291df5c285bbb467841e8f3d48f2b9d041493a2eec5b72c0b4

See more details on using hashes here.

File details

Details for the file auditrum-0.4.0-py3-none-any.whl.

File metadata

  • Download URL: auditrum-0.4.0-py3-none-any.whl
  • Upload date:
  • Size: 87.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.11.15

File hashes

Hashes for auditrum-0.4.0-py3-none-any.whl
Algorithm Hash digest
SHA256 0c833d90ebb8c7ecc4e29a32ed07d2fc491cc20dcf43b6a911183ee4033e4388
MD5 9646faa2293f63d596780486248cebde
BLAKE2b-256 bf74cd8086a4f30b326d8a8ab0b90fdd11c919faa642f37789150655c803ddf2

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