django-activity-audit 1.3.0.dev20__tar.gz → 1.3.0.dev22__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. {django_activity_audit-1.3.0.dev20 → django_activity_audit-1.3.0.dev22}/PKG-INFO +4 -1
  2. {django_activity_audit-1.3.0.dev20 → django_activity_audit-1.3.0.dev22}/README.md +3 -0
  3. django_activity_audit-1.3.0.dev22/README.rst +341 -0
  4. {django_activity_audit-1.3.0.dev20 → django_activity_audit-1.3.0.dev22}/activity_audit/config.py +2 -0
  5. {django_activity_audit-1.3.0.dev20 → django_activity_audit-1.3.0.dev22}/activity_audit/formatters.py +5 -1
  6. {django_activity_audit-1.3.0.dev20 → django_activity_audit-1.3.0.dev22}/activity_audit/middleware.py +14 -18
  7. {django_activity_audit-1.3.0.dev20 → django_activity_audit-1.3.0.dev22}/activity_audit/shared_processors.py +71 -0
  8. {django_activity_audit-1.3.0.dev20 → django_activity_audit-1.3.0.dev22}/pyproject.toml +1 -1
  9. django_activity_audit-1.3.0.dev20/README.rst +0 -263
  10. {django_activity_audit-1.3.0.dev20 → django_activity_audit-1.3.0.dev22}/.claude/settings.local.json +0 -0
  11. {django_activity_audit-1.3.0.dev20 → django_activity_audit-1.3.0.dev22}/.gitignore +0 -0
  12. {django_activity_audit-1.3.0.dev20 → django_activity_audit-1.3.0.dev22}/.pre-commit-config.yaml +0 -0
  13. {django_activity_audit-1.3.0.dev20 → django_activity_audit-1.3.0.dev22}/LICENSE +0 -0
  14. {django_activity_audit-1.3.0.dev20 → django_activity_audit-1.3.0.dev22}/MANIFEST.in +0 -0
  15. {django_activity_audit-1.3.0.dev20 → django_activity_audit-1.3.0.dev22}/activity_audit/__init__.py +0 -0
  16. {django_activity_audit-1.3.0.dev20 → django_activity_audit-1.3.0.dev22}/activity_audit/apps.py +0 -0
  17. {django_activity_audit-1.3.0.dev20 → django_activity_audit-1.3.0.dev22}/activity_audit/constants.py +0 -0
  18. {django_activity_audit-1.3.0.dev20 → django_activity_audit-1.3.0.dev22}/activity_audit/handlers.py +0 -0
  19. {django_activity_audit-1.3.0.dev20 → django_activity_audit-1.3.0.dev22}/activity_audit/logger_levels.py +0 -0
  20. {django_activity_audit-1.3.0.dev20 → django_activity_audit-1.3.0.dev22}/activity_audit/protocols.py +0 -0
  21. {django_activity_audit-1.3.0.dev20 → django_activity_audit-1.3.0.dev22}/activity_audit/settings.py +0 -0
  22. {django_activity_audit-1.3.0.dev20 → django_activity_audit-1.3.0.dev22}/activity_audit/signals.py +0 -0
  23. {django_activity_audit-1.3.0.dev20 → django_activity_audit-1.3.0.dev22}/activity_audit/unregistered.py +0 -0
  24. {django_activity_audit-1.3.0.dev20 → django_activity_audit-1.3.0.dev22}/activity_audit/utils.py +0 -0
  25. {django_activity_audit-1.3.0.dev20 → django_activity_audit-1.3.0.dev22}/docs/django-activity-audit.md +0 -0
  26. {django_activity_audit-1.3.0.dev20 → django_activity_audit-1.3.0.dev22}/docs/hipaa-audit-gaps.md +0 -0
  27. {django_activity_audit-1.3.0.dev20 → django_activity_audit-1.3.0.dev22}/docs/improvements.md +0 -0
  28. {django_activity_audit-1.3.0.dev20 → django_activity_audit-1.3.0.dev22}/docs/structlog-integration.md +0 -0
  29. {django_activity_audit-1.3.0.dev20 → django_activity_audit-1.3.0.dev22}/docs/user-activity-feed-plan.md +0 -0
  30. {django_activity_audit-1.3.0.dev20 → django_activity_audit-1.3.0.dev22}/hatch +0 -0
  31. {django_activity_audit-1.3.0.dev20 → django_activity_audit-1.3.0.dev22}/pytest.ini +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: django-activity-audit
3
- Version: 1.3.0.dev20
3
+ Version: 1.3.0.dev22
4
4
  Summary: A Django package for easy CRUD operation logging and container logs
5
5
  Project-URL: Homepage, https://github.com/shree256/django-activity-audit
6
6
  Project-URL: Repository, https://github.com/shree256/django-activity-audit
@@ -225,6 +225,7 @@ INFO 2025-04-30 08:51:10,403 /app/patients/api/utils.py utils create_patient_wit
225
225
  "level": "AUDIT",
226
226
  "name": "audit.model",
227
227
  "message": "CREATE event by User (id: 6f77b814-f9c1-4cab-a737-6677734bc303)",
228
+ "request_id": "f3c9a1b2-0001-4abc-beef-deadbeef0001",
228
229
  "model": "User",
229
230
  "event_type": "CREATE",
230
231
  "instance_id": "6f77b814-f9c1-4cab-a737-6677734bc303",
@@ -255,6 +256,7 @@ INFO 2025-04-30 08:51:10,403 /app/patients/api/utils.py utils create_patient_wit
255
256
  "level": "API",
256
257
  "name": "audit.request",
257
258
  "message": "Audit Internal Request",
259
+ "request_id": "f3c9a1b2-0001-4abc-beef-deadbeef0001",
258
260
  "service_name": "my_service",
259
261
  "request_type": "internal",
260
262
  "protocol": "http",
@@ -301,6 +303,7 @@ INFO 2025-04-30 08:51:10,403 /app/patients/api/utils.py utils create_patient_wit
301
303
  "level": "API",
302
304
  "name": "audit.request",
303
305
  "message": "Audit External Service",
306
+ "request_id": "f3c9a1b2-0001-4abc-beef-deadbeef0001",
304
307
  "service_name": "apollo",
305
308
  "request_type": "external",
306
309
  "protocol": "http",
@@ -183,6 +183,7 @@ INFO 2025-04-30 08:51:10,403 /app/patients/api/utils.py utils create_patient_wit
183
183
  "level": "AUDIT",
184
184
  "name": "audit.model",
185
185
  "message": "CREATE event by User (id: 6f77b814-f9c1-4cab-a737-6677734bc303)",
186
+ "request_id": "f3c9a1b2-0001-4abc-beef-deadbeef0001",
186
187
  "model": "User",
187
188
  "event_type": "CREATE",
188
189
  "instance_id": "6f77b814-f9c1-4cab-a737-6677734bc303",
@@ -213,6 +214,7 @@ INFO 2025-04-30 08:51:10,403 /app/patients/api/utils.py utils create_patient_wit
213
214
  "level": "API",
214
215
  "name": "audit.request",
215
216
  "message": "Audit Internal Request",
217
+ "request_id": "f3c9a1b2-0001-4abc-beef-deadbeef0001",
216
218
  "service_name": "my_service",
217
219
  "request_type": "internal",
218
220
  "protocol": "http",
@@ -259,6 +261,7 @@ INFO 2025-04-30 08:51:10,403 /app/patients/api/utils.py utils create_patient_wit
259
261
  "level": "API",
260
262
  "name": "audit.request",
261
263
  "message": "Audit External Service",
264
+ "request_id": "f3c9a1b2-0001-4abc-beef-deadbeef0001",
262
265
  "service_name": "apollo",
263
266
  "request_type": "external",
264
267
  "protocol": "http",
@@ -0,0 +1,341 @@
1
+ Django Activity Audit
2
+ =====================
3
+
4
+ A Django package that extends the default logging mechanism to track CRUD operations and container logs.
5
+
6
+ Features
7
+ --------
8
+
9
+ - Automatic logging of CRUD operations (Create, Read, Update, Delete)
10
+ - Tracks both HTTP requests and model changes
11
+ - Custom log levels Audit(21) and API(22) for CRUD and Request-Response auditing.
12
+ - Structured JSON logs for audit trails
13
+ - Human-readable container logs
14
+ - Separate log files for audit and container logs
15
+ - Console and file output options
16
+
17
+ Installation
18
+ ------------
19
+
20
+ 1. Install the package:
21
+
22
+ .. code-block:: bash
23
+
24
+ pip install django-activity-audit
25
+
26
+ This also installs `structlog <https://www.structlog.org/>`_ and `orjson <https://github.com/ijl/orjson>`_ as required dependencies.
27
+
28
+ 2. Add ``activity_audit`` to your ``INSTALLED_APPS`` in ``settings.py``:
29
+
30
+ .. code-block:: python
31
+
32
+ INSTALLED_APPS = [
33
+ ...
34
+ 'activity_audit',
35
+ ]
36
+
37
+ 3. Add the middleware to your ``MIDDLEWARE`` in ``settings.py``:
38
+
39
+ .. code-block:: python
40
+
41
+ MIDDLEWARE = [
42
+ ...
43
+ 'activity_audit.middleware.AuditLoggingMiddleware',
44
+ ]
45
+
46
+ 4. Configure logging in ``settings.py``.
47
+
48
+ Import the formatter helpers from ``activity_audit.config``:
49
+
50
+ .. code-block:: python
51
+
52
+ from activity_audit.config import get_plain_formatter, get_stdlib_formatter
53
+
54
+ - ``get_stdlib_formatter()`` — structlog JSON renderer. Use in staging/production where logs are ingested by a pipeline (Vector, CloudWatch, etc.).
55
+ - ``get_plain_formatter()`` — structlog plain-text renderer. Use locally for human-readable console output.
56
+
57
+ **Local development** (plain text output):
58
+
59
+ .. code-block:: python
60
+
61
+ from activity_audit.config import get_plain_formatter, get_stdlib_formatter
62
+
63
+ LOGGING = {
64
+ "version": 1,
65
+ "disable_existing_loggers": False,
66
+ "formatters": {
67
+ "structlog": get_stdlib_formatter(),
68
+ "default": get_plain_formatter(),
69
+ },
70
+ "handlers": {
71
+ "console": {"class": "logging.StreamHandler", "formatter": "default"},
72
+ "console_struct": {"class": "logging.StreamHandler", "formatter": "structlog"},
73
+ },
74
+ "root": {
75
+ "level": "INFO",
76
+ "handlers": ["console"], # plain text fallback for all loggers
77
+ },
78
+ "loggers": {
79
+ # Structlog owns these — explicit handler, no propagation to avoid double output
80
+ "audit.model": {"handlers": ["console_struct"], "propagate": False},
81
+ "audit.request": {"handlers": ["console_struct"], "propagate": False},
82
+ "audit.login": {"handlers": ["console_struct"], "propagate": False},
83
+
84
+ # Celery — structlog formats log_type correctly
85
+ "celery": {"level": "INFO", "handlers": ["console_struct"], "propagate": False},
86
+ "celery.task": {"level": "INFO", "handlers": ["console_struct"], "propagate": False},
87
+ "celery.beat": {"level": "INFO", "handlers": ["console_struct"], "propagate": False},
88
+
89
+ # Third-party noise control — WARNING only, routed to root
90
+ "django.db.backends": {"level": "WARNING", "handlers": [], "propagate": True},
91
+ "boto3": {"level": "WARNING", "handlers": [], "propagate": True},
92
+ "botocore": {"level": "WARNING", "handlers": [], "propagate": True},
93
+
94
+ # Framework loggers
95
+ "django": {"level": "INFO", "handlers": [], "propagate": True},
96
+ "uvicorn": {"level": "INFO", "handlers": [], "propagate": True},
97
+ "uvicorn.error": {"level": "INFO", "handlers": [], "propagate": True},
98
+ "uvicorn.access":{"level": "INFO", "handlers": [], "propagate": True},
99
+ },
100
+ }
101
+
102
+ **Staging / production** (structured JSON output): identical structure, but the ``root`` handler also uses ``console_struct`` (or keep ``console`` for mixed output — both handlers use the same ``StreamHandler`` class):
103
+
104
+ .. code-block:: python
105
+
106
+ "root": {
107
+ "level": "INFO",
108
+ "handlers": ["console"],
109
+ }
110
+
111
+ ---
112
+
113
+ When to add a logger entry
114
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~
115
+
116
+ Add an explicit logger entry when you need **any** of the following:
117
+
118
+ .. list-table::
119
+ :header-rows: 1
120
+ :widths: 60 40
121
+
122
+ * - Situation
123
+ - What to set
124
+ * - Route to structured JSON (``console_struct``)
125
+ - ``handlers: ["console_struct"], propagate: False``
126
+ * - Suppress a noisy third-party library
127
+ - ``level: "WARNING", handlers: [], propagate: True``
128
+ * - Prevent double output for a structlog-owned logger
129
+ - ``handlers: ["console_struct"], propagate: False``
130
+ * - Change the log level for a specific namespace
131
+ - Set ``level`` explicitly
132
+
133
+ **Do not** add a logger entry if the default behaviour is acceptable — a logger with no entry propagates to ``root`` and is emitted in plain text at INFO level. That is the correct behaviour for most application loggers.
134
+
135
+ Silencing audit loggers (route to root instead of structlog)
136
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
137
+
138
+ By default ``audit.model``, ``audit.request``, and ``audit.login`` are pointed at ``console_struct`` with ``propagate: False`` so only the structlog-formatted JSON line is emitted.
139
+
140
+ To stop structlog from handling them and fall back to the plain-text root logger instead, set ``handlers: []`` and ``propagate: True``:
141
+
142
+ .. code-block:: python
143
+
144
+ "loggers": {
145
+ "audit.model": {"handlers": [], "propagate": True},
146
+ "audit.request": {"handlers": [], "propagate": True},
147
+ "audit.login": {"handlers": [], "propagate": True},
148
+ }
149
+
150
+ This routes all three through the ``root`` logger (``console`` handler, ``default`` / plain-text formatter). Use this when you want to completely disable structured audit output — for example, in a minimal local environment or during debugging.
151
+
152
+ 5. Configure the service name in ``settings.py`` (optional, defaults to ``"default"``):
153
+
154
+ .. code-block:: python
155
+
156
+ AUDIT_SERVICE_NAME = "my_service"
157
+
158
+ 6. For external services logging, extend ``HTTPClient`` or ``SFTPClient``:
159
+
160
+ .. code-block:: python
161
+
162
+ class ExternalService(HTTPClient):
163
+ def __init__(self):
164
+ super().__init__("service_name")
165
+
166
+ def connect(self):
167
+ url = "https://www.sample.com"
168
+ response = self.get(url) # sample log structure below
169
+
170
+ 8. Create ``audit_logs`` folder in project directory
171
+
172
+ Log Types
173
+ ---------
174
+
175
+ Container Logs
176
+ ~~~~~~~~~~~~~~
177
+
178
+ Console Log Format::
179
+
180
+ '%(levelname)s %(asctime)s %(pathname)s %(module)s %(funcName)s %(message)s'
181
+ -----------------------------------------------------------------------------
182
+ INFO 2025-04-30 08:51:10,403 /app/patients/api/utils.py utils create_patient_with_contacts_and_diseases Patient 'd6c9a056-0b57-453a-8c0f-44319004b761 - Patient3' created.
183
+
184
+ APP Log
185
+ ~~~~~~~
186
+
187
+ .. code-block:: json
188
+
189
+ {
190
+ "timestamp": "2025-05-15 13:38:02.141",
191
+ "level": "DEBUG",
192
+ "name": "botocore.auth",
193
+ "path": "/opt/venv/lib/python3.11/site-packages/botocore/auth.py",
194
+ "module": "auth",
195
+ "function": "add_auth",
196
+ "message": "Calculating signature using v4 auth.",
197
+ "exception": ""
198
+ }
199
+
200
+ CRUD Log
201
+ ~~~~~~~~
202
+
203
+ .. code-block:: json
204
+
205
+ {
206
+ "timestamp": "2025-08-16 17:06:32.403",
207
+ "level": "AUDIT",
208
+ "name": "audit.model",
209
+ "message": "CREATE event by User (id: 6f77b814-f9c1-4cab-a737-6677734bc303)",
210
+ "model": "User",
211
+ "event_type": "CREATE",
212
+ "instance_id": "6f77b814-f9c1-4cab-a737-6677734bc303",
213
+ "instance_repr": {
214
+ "name": "Test Model",
215
+ "is_active": true,
216
+ "created_at": "2025-08-29T08:18:54Z",
217
+ "updated_at": "2025-08-29T08:18:54Z"
218
+ },
219
+ "user_id": "cae8ffb4-ba52-409c-9a6f-e10362bfaf97",
220
+ "user_info": {
221
+ "title": "mr",
222
+ "email": "example@source.com",
223
+ "first_name": "mohamlal",
224
+ "middle_name": "v",
225
+ "last_name": "nair",
226
+ "sex": "m"
227
+ },
228
+ "extra": {}
229
+ }
230
+
231
+ Request-Response Log
232
+ ~~~~~~~~~~~~~~~~~~~~
233
+
234
+ Incoming Log Format:
235
+
236
+ .. code-block:: json
237
+
238
+ {
239
+ "timestamp": "2025-05-19 15:25:27.836",
240
+ "level": "API",
241
+ "name": "audit.request",
242
+ "message": "Audit Internal Request",
243
+ "service_name": "my_service",
244
+ "request_type": "internal",
245
+ "protocol": "http",
246
+ "user_id": "14ab1197-ebdd-4300-a618-5910e0219936",
247
+ "user_info": {
248
+ "title": "mr",
249
+ "email": "example@email.com",
250
+ "first_name": "mohanlal",
251
+ "middle_name": "",
252
+ "last_name": "nair",
253
+ "sex": "male",
254
+ "date_of_birth": "21/30/1939"
255
+ },
256
+ "request_repr": {
257
+ "method": "GET",
258
+ "path": "/api/v1/health/",
259
+ "query_params": {},
260
+ "headers": {
261
+ "Content-Type": "application/json"
262
+ },
263
+ "body": {
264
+ "title": "hello"
265
+ }
266
+ },
267
+ "response_repr": {
268
+ "headers": {
269
+ "Content-Type": "application/json"
270
+ },
271
+ "body": {
272
+ "status": "ok"
273
+ }
274
+ },
275
+ "error_message": null,
276
+ "execution_time": 5.376734018325806
277
+ }
278
+
279
+ External Log Format:
280
+
281
+ .. code-block:: json
282
+
283
+ {
284
+ "timestamp": "2025-05-19 15:25:27.717",
285
+ "level": "API",
286
+ "name": "audit.request",
287
+ "message": "Audit External Service",
288
+ "service_name": "apollo",
289
+ "request_type": "external",
290
+ "protocol": "http",
291
+ "user_id": "14ab1197-ebdd-4300-a618-5910e0219936",
292
+ "user_info": {
293
+ "title": "mr",
294
+ "email": "example@email.com",
295
+ "first_name": "mohanlal",
296
+ "middle_name": "",
297
+ "last_name": "nair",
298
+ "sex": "male",
299
+ "date_of_birth": "21/30/1939"
300
+ },
301
+ "request_repr": {
302
+ "endpoint": "example.com",
303
+ "method": "GET",
304
+ "headers": {},
305
+ "body": {}
306
+ },
307
+ "response_repr": {
308
+ "status_code": 200,
309
+ "body": {
310
+ "title": "title",
311
+ "expiresIn": 3600,
312
+ "error": "",
313
+ "errorDescription": ""
314
+ }
315
+ },
316
+ "error_message": "",
317
+ "execution_time": 5.16809344291687
318
+ }
319
+
320
+ Notes
321
+ -----
322
+
323
+ - Compatible with **Django 4.2+** and **Python 3.8+**.
324
+ - Designed for easy integration with observability stacks using Vector, ClickHouse, and Grafana.
325
+ - Capture Django CRUD operations automatically
326
+ - Write structured JSON logs
327
+ - Ready for production-grade logging pipelines
328
+ - Simple pip install, reusable across projects
329
+ - Zero additional database overhead!
330
+
331
+ Related Tools
332
+ -------------
333
+
334
+ - `Vector.dev <https://vector.dev/>`_
335
+ - `ClickHouse <https://clickhouse.com/>`_
336
+ - `Grafana <https://grafana.com/>`_
337
+
338
+ License
339
+ -------
340
+
341
+ This project is licensed under the MIT License - see the LICENSE file for details.
@@ -4,6 +4,7 @@ from activity_audit.shared_processors import (
4
4
  _json_default,
5
5
  _orjson_dumps,
6
6
  shared_processors,
7
+ trim_log_fields,
7
8
  )
8
9
 
9
10
 
@@ -46,6 +47,7 @@ def get_stdlib_formatter() -> dict:
46
47
  "()": structlog.stdlib.ProcessorFormatter,
47
48
  "processors": [
48
49
  structlog.stdlib.ProcessorFormatter.remove_processors_meta,
50
+ trim_log_fields,
49
51
  structlog.processors.JSONRenderer(
50
52
  serializer=_orjson_dumps, default=_json_default
51
53
  ),
@@ -63,10 +63,13 @@ class AppFormatter(logging.Formatter):
63
63
  "log_type": self.log_type,
64
64
  }
65
65
 
66
- # Add exception info if present for ERROR
67
66
  if record.exc_info:
68
67
  log_data["exception"] = "{}".format(self.formatException(record.exc_info))
69
68
 
69
+ extra = getattr(record, "extra", "")
70
+ if extra:
71
+ log_data["extra"] = extra
72
+
70
73
  return json.dumps(log_data, default=_json_default)
71
74
 
72
75
 
@@ -106,6 +109,7 @@ class APIFormatter(logging.Formatter):
106
109
  "response_repr",
107
110
  "error_message",
108
111
  "execution_time",
112
+ "extra",
109
113
  ]
110
114
 
111
115
  for field in audit_fields:
@@ -118,6 +118,18 @@ class AuditLoggingMiddleware(MiddlewareMixin):
118
118
  }
119
119
  """
120
120
 
121
+ def _init_log_data(self):
122
+ return {
123
+ "service_name": SERVICE_NAME,
124
+ "request_type": REQUEST_TYPES[0],
125
+ "protocol": None,
126
+ "request_repr": {},
127
+ "response_repr": {},
128
+ "error_message": None,
129
+ "execution_time": 0,
130
+ "extra": {},
131
+ }
132
+
121
133
  def __init__(self, get_response):
122
134
  self.get_response = get_response
123
135
 
@@ -135,15 +147,7 @@ class AuditLoggingMiddleware(MiddlewareMixin):
135
147
  if not should_log_url(request.path):
136
148
  return self.get_response(request)
137
149
 
138
- log_data = {
139
- "service_name": SERVICE_NAME,
140
- "request_type": REQUEST_TYPES[0],
141
- "protocol": None,
142
- "request_repr": {},
143
- "response_repr": {},
144
- "error_message": None,
145
- "execution_time": 0,
146
- }
150
+ log_data = self._init_log_data()
147
151
  start_time = time.time()
148
152
 
149
153
  # Log request
@@ -209,15 +213,7 @@ class AuditLoggingMiddleware(MiddlewareMixin):
209
213
  if not should_log_url(request.path):
210
214
  return await self.get_response(request)
211
215
 
212
- log_data = {
213
- "service_name": SERVICE_NAME,
214
- "request_type": REQUEST_TYPES[0],
215
- "protocol": None,
216
- "request_repr": {},
217
- "response_repr": {},
218
- "error_message": None,
219
- "execution_time": 0,
220
- }
216
+ log_data = self._init_log_data()
221
217
  start_time = time.time()
222
218
 
223
219
  # Log request
@@ -82,6 +82,77 @@ def _add_log_type(logger, method, event_dict):
82
82
  return event_dict
83
83
 
84
84
 
85
+ _APP_FIELDS = frozenset(
86
+ {
87
+ "timestamp",
88
+ "level",
89
+ "name",
90
+ "message",
91
+ "log_type",
92
+ "filename",
93
+ "func_name",
94
+ "request_id",
95
+ "exception",
96
+ "extra",
97
+ }
98
+ )
99
+
100
+ _AUDIT_FIELDS = frozenset(
101
+ {
102
+ "timestamp",
103
+ "level",
104
+ "name",
105
+ "message",
106
+ "log_type",
107
+ "model",
108
+ "event_type",
109
+ "request_id",
110
+ "instance_id",
111
+ "instance_repr",
112
+ "user_id",
113
+ "user_info",
114
+ "extra",
115
+ }
116
+ )
117
+
118
+ _API_FIELDS = frozenset(
119
+ {
120
+ "timestamp",
121
+ "level",
122
+ "name",
123
+ "message",
124
+ "log_type",
125
+ "service_name",
126
+ "request_type",
127
+ "protocol",
128
+ "request_id",
129
+ "user_id",
130
+ "user_info",
131
+ "request_repr",
132
+ "response_repr",
133
+ "error_message",
134
+ "execution_time",
135
+ "extra",
136
+ }
137
+ )
138
+
139
+ _FIELDS_BY_LOG_TYPE = {
140
+ LogType.APP: _APP_FIELDS,
141
+ LogType.CELERYBEAT: _APP_FIELDS,
142
+ LogType.CELERYWORKER: _APP_FIELDS,
143
+ LogType.AUDIT: _AUDIT_FIELDS,
144
+ LogType.API: _API_FIELDS,
145
+ }
146
+
147
+
148
+ def trim_log_fields(logger, method, event_dict):
149
+ """Keep only the required fields for each log type."""
150
+ allowed = _FIELDS_BY_LOG_TYPE.get(event_dict.get("log_type"))
151
+ if allowed is not None:
152
+ return {k: v for k, v in event_dict.items() if k in allowed}
153
+ return event_dict
154
+
155
+
85
156
  _STANDARD_KEYS = frozenset(
86
157
  {
87
158
  "timestamp",
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "django-activity-audit"
3
- version = "1.3.0.dev20"
3
+ version = "1.3.0.dev22"
4
4
  description = "A Django package for easy CRUD operation logging and container logs"
5
5
  authors = [
6
6
  { name = "Shreeshan", email = "shreeshan256@gmail.com" }
@@ -1,263 +0,0 @@
1
- Django Activity Audit
2
- =====================
3
-
4
- A Django package that extends the default logging mechanism to track CRUD operations and container logs.
5
-
6
- Features
7
- --------
8
-
9
- - Automatic logging of CRUD operations (Create, Read, Update, Delete)
10
- - Tracks both HTTP requests and model changes
11
- - Custom log levels Audit(21) and API(22) for CRUD and Request-Response auditing.
12
- - Structured JSON logs for audit trails
13
- - Human-readable container logs
14
- - Separate log files for audit and container logs
15
- - Console and file output options
16
-
17
- Installation
18
- ------------
19
-
20
- 1. Install the package::
21
-
22
- pip install django-activity-audit
23
-
24
- 2. Add ``activity_audit`` to your ``INSTALLED_APPS`` in ``settings.py``::
25
-
26
- INSTALLED_APPS = [
27
- ...
28
- 'activity_audit',
29
- ]
30
-
31
- 3. Add the middleware to your ``MIDDLEWARE`` in ``settings.py``::
32
-
33
- MIDDLEWARE = [
34
- ...
35
- 'activity_audit.middleware.AuditLoggingMiddleware',
36
- ]
37
-
38
- 4. Configure logging in ``settings.py``::
39
-
40
- from activity_audit import *
41
-
42
- LOGGING = {
43
- "version": 1,
44
- "disable_existing_loggers": False,
45
- "formatters": {
46
- "json": get_json_formatter(),
47
- "verbose": get_console_formatter(),
48
- },
49
- "handlers": {
50
- "console": {
51
- "level": "DEBUG",
52
- "class": "logging.StreamHandler",
53
- "formatter": "verbose",
54
- },
55
- "file": get_json_handler(level="DEBUG", formatter="json"),
56
- "api_file": get_api_file_handler(),
57
- "audit_file": get_audit_handler(),
58
- },
59
- "root": {"level": "DEBUG", "handlers": ["console", "file"]},
60
- "loggers": {
61
- "audit.request": {
62
- "handlers": ["api_file"],
63
- "level": "API",
64
- "propagate": False,
65
- },
66
- "audit.model": {
67
- "handlers": ["audit_file"],
68
- "level": "AUDIT",
69
- "propagate": False,
70
- },
71
- "django": {
72
- "handlers": ["console", "file"],
73
- "level": "INFO",
74
- "propagate": False,
75
- },
76
- }
77
- }
78
-
79
- 5. Configure the service name in ``settings.py`` (optional, defaults to ``"default"``)::
80
-
81
- AUDIT_SERVICE_NAME = "my_service"
82
-
83
- 6. For external services logging, extend ``HTTPClient`` or ``SFTPClient``::
84
-
85
- class ExternalService(HTTPClient):
86
- def __init__(self):
87
- super().__init__("service_name")
88
-
89
- def connect(self):
90
- url = "https://www.sample.com"
91
- response = self.get(url) # sample log structure below
92
-
93
- 7. Create ``audit_logs`` folder in project directory
94
-
95
- Log Types
96
- ---------
97
-
98
- Container Logs
99
- --------------
100
-
101
- Console Log Format::
102
-
103
- '%(levelname)s %(asctime)s %(pathname)s %(module)s %(funcName)s %(message)s'
104
- -----------------------------------------------------------------------------
105
- INFO 2025-04-30 08:51:10,403 /app/patients/api/utils.py utils create_patient_with_contacts_and_diseases Patient 'd6c9a056-0b57-453a-8c0f-44319004b761 - Patient3' created.
106
-
107
- APP Log
108
- -------
109
-
110
- ::
111
-
112
- {
113
- "timestamp": "2025-05-15 13:38:02.141",
114
- "level": "DEBUG",
115
- "name": "botocore.auth",
116
- "path": "/opt/venv/lib/python3.11/site-packages/botocore/auth.py",
117
- "module": "auth",
118
- "function": "add_auth",
119
- "message": "Calculating signature using v4 auth.",
120
- "exception": ""
121
- }
122
-
123
- CRUD Log
124
- --------
125
-
126
- ::
127
-
128
- {
129
- "timestamp": "2025-08-16 17:06:32.403",
130
- "level": "AUDIT",
131
- "name": "audit.model",
132
- "message": "CREATE event by User (id: 6f77b814-f9c1-4cab-a737-6677734bc303)",
133
- "model": "User",
134
- "event_type": "CREATE",
135
- "instance_id": "6f77b814-f9c1-4cab-a737-6677734bc303",
136
- "instance_repr" : {
137
- "name": "Test Model",
138
- "is_active": true,
139
- "created_at": "2025-08-29T08:18:54Z",
140
- "updated_at": "2025-08-29T08:18:54Z"
141
- },
142
- "user_id": "14ab1197-ebdd-4300-a618-5910e0219936",
143
- "user_info": {
144
- "title": "mr",
145
- "email": "example@email.com",
146
- "first_name": "mohanlal",
147
- "middle_name": "",
148
- "last_name": "nair",
149
- "sex": "male",
150
- "date_of_birth": "21/30/1939"
151
- },
152
- "extra": {}
153
- }
154
-
155
- Request-Response Log
156
- --------------------
157
-
158
- Incoming Log Format::
159
-
160
- {
161
- "timestamp": "2025-05-19 15:25:27.836",
162
- "level": "API",
163
- "name": "audit.request",
164
- "message": "Audit Internal Request",
165
- "service_name": "my_service",
166
- "request_type": "internal",
167
- "protocol": "http",
168
- "user_id": "14ab1197-ebdd-4300-a618-5910e0219936",
169
- "user_info": {
170
- "title": "mr",
171
- "email": "example@email.com",
172
- "first_name": "mohanlal",
173
- "middle_name": "",
174
- "last_name": "nair",
175
- "sex": "male",
176
- "date_of_birth": "21/30/1939"
177
- },
178
- "request_repr": {
179
- "method": "GET",
180
- "path": "/api/v1/health/",
181
- "query_params": {},
182
- "headers": {
183
- "Content-Type": "application/json",
184
- },
185
- "user": null,
186
- "body": {
187
- "title": "hello"
188
- }
189
- },
190
- "response_repr": {
191
- "status_code": 200,
192
- "headers": {
193
- "Content-Type": "application/json",
194
- },
195
- "body": {
196
- "status": "ok"
197
- }
198
- },
199
- "error_message": null,
200
- "execution_time": 5.376734018325806
201
- }
202
-
203
- External Log format::
204
-
205
- {
206
- "timestamp": "2025-05-19 15:25:27.717",
207
- "level": "API",
208
- "name": "audit.request",
209
- "message": "Audit External Service",
210
- "service_name": "apollo",
211
- "request_type": "external",
212
- "protocol": "http",
213
- "user_id": "14ab1197-ebdd-4300-a618-5910e0219936",
214
- "user_info": {
215
- "title": "mr",
216
- "email": "example@email.com",
217
- "first_name": "mohanlal",
218
- "middle_name": "",
219
- "last_name": "nair",
220
- "sex": "male",
221
- "date_of_birth": "21/30/1939"
222
- },
223
- "request_repr": {
224
- "endpoint": "example.com",
225
- "method": "GET",
226
- "headers": {},
227
- "body": {}
228
- },
229
- "response_repr": {
230
- "status_code": 200,
231
- "body": {
232
- "title": "title",
233
- "expiresIn": 3600,
234
- "error": "",
235
- "errorDescription": ""
236
- }
237
- },
238
- "error_message": "",
239
- "execution_time": 5.16809344291687
240
- }
241
-
242
- Notes
243
- -----
244
-
245
- - Compatible with **Django 3.2+** and **Python 3.7+**.
246
- - Designed for easy integration with observability stacks using Vector, ClickHouse, and Grafana.
247
- - Capture Django CRUD operations automatically
248
- - Write structured JSON logs
249
- - Ready for production-grade logging pipelines
250
- - Simple pip install, reusable across projects
251
- - Zero additional database overhead!
252
-
253
- Related Tools
254
- -------------
255
-
256
- - `Vector.dev <https://vector.dev/>`_
257
- - `ClickHouse <https://clickhouse.com/>`_
258
- - `Grafana <https://grafana.com/>`_
259
-
260
- License
261
- -------
262
-
263
- This project is licensed under the MIT License - see the LICENSE file for details.