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, andDELETE - 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
- Add to
INSTALLED_APPS:
INSTALLED_APPS = [
# ...
'auditrum.integrations.django',
]
- Add middleware:
MIDDLEWARE = [
# ...
'auditrum.integrations.django.middleware.RequestIDMiddleware',
'auditrum.integrations.django.middleware.AuditrumMiddleware',
]
- 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.
- Run migrations:
python manage.py migrate
This will create the audit table and set up triggers automatically.
- 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/pythonwith the full path to your virtualenv's Python interpreter. - Replace
/path/to/your/project/manage.pywith the path to your project's manage.py file. - Adjust the
--months 3argument as needed to define how far ahead partitions should be created.
- 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:
- Getting started — install and first audit event
- Core concepts —
TrackSpec,TriggerManager, context flow - Django integration / SQLAlchemy integration
- Time travel — reconstruct rows at any past timestamp
auditrum blame— git-style per-row history from the CLI- Hardening and compliance — hash chain, retention, append-only
- Observability — OpenTelemetry, Prometheus, Sentry
- CLI reference — every subcommand, every option
- Architecture — schema, indexes, trigger flow, concurrency
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
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 auditrum-0.4.1.tar.gz.
File metadata
- Download URL: auditrum-0.4.1.tar.gz
- Upload date:
- Size: 115.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.15
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7bb93a2c75b89270b7f12b295b04c797f8b7be336f1abdcfffeb7b7ab3b4247e
|
|
| MD5 |
b3cf76718904fe76bf7e7a023930cc84
|
|
| BLAKE2b-256 |
7787b415d6bf18943a77e3e1be74982197ae7f112c5685aa5c6868d6f092e519
|
File details
Details for the file auditrum-0.4.1-py3-none-any.whl.
File metadata
- Download URL: auditrum-0.4.1-py3-none-any.whl
- Upload date:
- Size: 91.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.15
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
447e304bd86f435b1ded85ef12b29ae42ce28f885daf70bffac4e865f5b92620
|
|
| MD5 |
16c59ee914c966d0ad1b56969576a37a
|
|
| BLAKE2b-256 |
06f27afd1fbc0b26c514d4b57de70bac95ad8752b2ccd5ed3675dc25a0f120c9
|