When you’re building Django applications in regulated domains like healthcare or finance, every action matters - whether it’s a doctor updating a record or an API serving patient data. These interactions form your audit trail and need to be captured with precision for compliance, debugging, and accountability.
Packages like django-easy-audit attempt to solve this but often rely on database-backed audit models, which add overhead and complicate analysis.
This is where django-activity-audit comes in. Instead of persisting audits in your primary database, it extends Django’s native logging to record CRUD operations and API activity as structured JSON, keeping auditing in the logging layer where it belongs.
The Problem with Django Audit Logging Using Database Tables
Django already has community packages such as django-easy-audit that record CRUD activity and API usage. While useful, their design relies on creating separate database models to store every event.
Here’s what that means in practice:
- Extra Database Writes – Every insert, update, or delete triggers a parallel insert into an audit table. This inflates the number of queries per request.
- Longer Transactions – With more queries, transactions take longer to complete, which can affect overall response times.
- Bloated Audit Tables – Since all activity is logged in the primary database, tables grow fast, requiring periodic cleanups and adding to maintenance.
- Unstructured Data – Many implementations dump event details into a single JSON field, making it difficult to filter or run analytics later.
These trade-offs mean you end up paying the cost of auditability with performance overhead and poor log usability.
How django-activity-audit Simplifies Django Audit Trails
To address these limitations, we built django-activity-audit. Unlike packages that create audit tables, it integrates with Django’s logging system to capture activity where it naturally belongs.
What makes it different?
- Log-level separation → CRUD and API events aren’t mixed with application noise.
- Structured output → everything is in JSON, consistent and easy to consume.
- Database-free auditing → no extra tables, no cleanup jobs.
The package is designed for developers who want audit trails without database baggage.
Making Audit Trails Native to Django’s Logging System
The purpose of django-activity-audit is to rethink how auditing fits into a Django project. Traditional packages bolt on extra tables, but this package treats auditing as part of the application’s logging fabric. That design choice matters because:
- Auditing aligns with observability → the same logs that power debugging and metrics also capture compliance events.
- Developers don’t manage two systems → one logging pipeline handles application activity and audit trails.
- Audit data becomes future-proof → once in structured logs, you can decide later whether to keep them in files, send them to ELK, or push them into ClickHouse.
Instead of adding weight to the database, django-activity-audit makes audit trails flow through the same logging channels you already use, simple, consistent, and ready for whatever stack your team runs.
How django-activity-audit Works: Logging CRUD and API Activity in Django
django-activity-audit extends Django’s default logging system to automatically capture both CRUD events and API request–response cycles. It introduces two custom log levels:
- AUDIT (21) → logs model changes (Create, Read, Update, Delete).
- API (22) → logs request/response activity, both internal and external.
1. Installation & Setup
pip install django-activity-audit
Add the app to INSTALLED_APPS in settings.py:
INSTALLED_APPS = [
...
'activity_audit',
]Enable the middleware:
MIDDLEWARE = [
...
'activity_audit.middleware.AuditLoggingMiddleware',
]Configure logging in settings.py:
from activity_audit import *
LOGGING = {
"version": 1,
"disable_existing_loggers": False,
"formatters": {
"json": get_json_formatter(),
"verbose": get_console_formatter(),
},
"handlers": {
"console": {
"level": "DEBUG",
"class": "logging.StreamHandler",
"formatter": "verbose",},
"file": get_json_handler(level="DEBUG", formatter="json"),
"api_file": get_api_file_handler(),
"audit_file": get_audit_handler(),
},
"root": {"level": "DEBUG", "handlers": ["console", "file"]},
"loggers": {
"audit.request": {
"handlers": ["api_file"],
"level": "API",
"propagate": False,
},
"audit.model": {
"handlers": ["audit_file"],
"level": "AUDIT",
"propagate": False,
},
"django": {
"handlers": ["console", "file"],
"level": "INFO",
"propagate": False,
},
}
}
2. CRUD Logs
Whenever a model is created, updated, or deleted, a structured JSON log is emitted at the AUDIT level:
{
"timestamp": "2025-08-16 17:06:32.403",
"level": "AUDIT",
"name": "audit.model",
"message": "CREATE event for User (id: 6f77b814-f9c1-4cab-a737-6677734bc303)",
"model": "User",
"event_type": "CREATE",
"instance_id": "6f77b814-f9c1-4cab-a737-6677734bc303",
"instance_repr" : {
"name": "Test Model",
"is_active": true,
"created_at": "2025-08-29T08:18:54Z",
"updated_at": "2025-08-29T08:18:54Z"
},
"user_id": "cae8ffb4-ba52-409c-9a6f-e10362bfaf97",
"user_info": {
"title": "mr",
"email": "example@source.com",
"first_name": "mohamlal",
"middle_name": "v",
"last_name": "nair","sex": "m",
},
"extra": {}
}3. Request–Response Logs
Each HTTP request and response is automatically logged at the API level. Example incoming request log:
{
"timestamp": "2025-05-19 15:25:27.836",
"level": "API",
"name": "audit.request",
"message": "Audit Internal Request",
"service_name": "review_board",
"request_type": "internal",
"protocol": "http",
"user_id": "14ab1197-ebdd-4300-a618-5910e0219936",
"user_info": {
"title": "mr",
"email": "example@email.com",
"first_name": "mohanlal",
"middle_name": "",
"last_name": "nair",
"sex": "male",
"date_of_birth": "21/30/1939"
},
"request_repr": {
"method": "GET",
"path": "/api/v1/health/",
"query_params": {},
"headers": {
"Content-Type": "application/json",
},
"user": null,
"body": {
"title": "hello"
}
},
"response_repr": {
"status_code": 200,
"headers": {
"Content-Type": "application/json",
},
"body": {
"status": "ok"
}
},
"error_message": null,
"execution_time": 5.376734018325806}External services can also be tracked by extending the provided HTTPClient or SFTPClient.
Key Benefits of Structured Audit Logging in Django
By extending Django’s native logging instead of introducing audit tables, django-activity-audit offers a simpler and more maintainable way to track activity:
- Zero Database Overhead
- Structured JSON Logs
- Unified Audit Trail Both model changes and request–response cycles are logged through the same system, ensuring a single source of truth for activity tracking.
- Custom Log Levels With AUDIT (21) and API (22), audit events are clearly separated from standard application logs, giving developers fine-grained control.
- Drop-in Simplicity Installation and setup follow Django conventions - add the app, enable middleware, configure logging, and you’re ready.
The Final Lines
django-activity-audit shifts auditing from being an afterthought in the database to a first-class part of Django’s logging layer. Instead of bolting on extra tables or relying on ad-hoc middleware, the package gives you a structured, standards-aligned way to capture the events that matter. Whether it’s CRUD operations or full request–response cycles, everything is logged with clarity, separation, and consistency. That means developers spend less time wrestling with overhead and more time using clean logs for compliance, debugging, or analytics, especially when building scalable observability and monitoring systems.
Looking ahead, the package is designed to fit naturally into modern observability practices without locking you into a specific stack. By emitting structured JSON, it leaves you free to integrate with whatever pipeline your team prefers, from simple log files to enterprise-grade monitoring systems. If you’d like to see where this project goes next, share your feedback, or collaborate on improvements, connect with Shreeshan Panicker on LinkedIn.
Shreeshan Panicker
SDE2
Shreeshan Panicker is a Software Engineer at HouseWorks Inc, focused on building reliable software systems while continuously sharpening his technical craft. Driven by curiosity and growth, he approaches engineering with a learner's mindset and a strong ambition to evolve into a well-rounded technocrat.



