django-activity-audit 1.3.0.dev20__tar.gz → 1.3.0.dev21__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.
- {django_activity_audit-1.3.0.dev20 → django_activity_audit-1.3.0.dev21}/PKG-INFO +4 -1
- {django_activity_audit-1.3.0.dev20 → django_activity_audit-1.3.0.dev21}/README.md +3 -0
- django_activity_audit-1.3.0.dev21/README.rst +341 -0
- {django_activity_audit-1.3.0.dev20 → django_activity_audit-1.3.0.dev21}/activity_audit/formatters.py +5 -1
- {django_activity_audit-1.3.0.dev20 → django_activity_audit-1.3.0.dev21}/activity_audit/middleware.py +14 -18
- {django_activity_audit-1.3.0.dev20 → django_activity_audit-1.3.0.dev21}/pyproject.toml +1 -1
- django_activity_audit-1.3.0.dev20/README.rst +0 -263
- {django_activity_audit-1.3.0.dev20 → django_activity_audit-1.3.0.dev21}/.claude/settings.local.json +0 -0
- {django_activity_audit-1.3.0.dev20 → django_activity_audit-1.3.0.dev21}/.gitignore +0 -0
- {django_activity_audit-1.3.0.dev20 → django_activity_audit-1.3.0.dev21}/.pre-commit-config.yaml +0 -0
- {django_activity_audit-1.3.0.dev20 → django_activity_audit-1.3.0.dev21}/LICENSE +0 -0
- {django_activity_audit-1.3.0.dev20 → django_activity_audit-1.3.0.dev21}/MANIFEST.in +0 -0
- {django_activity_audit-1.3.0.dev20 → django_activity_audit-1.3.0.dev21}/activity_audit/__init__.py +0 -0
- {django_activity_audit-1.3.0.dev20 → django_activity_audit-1.3.0.dev21}/activity_audit/apps.py +0 -0
- {django_activity_audit-1.3.0.dev20 → django_activity_audit-1.3.0.dev21}/activity_audit/config.py +0 -0
- {django_activity_audit-1.3.0.dev20 → django_activity_audit-1.3.0.dev21}/activity_audit/constants.py +0 -0
- {django_activity_audit-1.3.0.dev20 → django_activity_audit-1.3.0.dev21}/activity_audit/handlers.py +0 -0
- {django_activity_audit-1.3.0.dev20 → django_activity_audit-1.3.0.dev21}/activity_audit/logger_levels.py +0 -0
- {django_activity_audit-1.3.0.dev20 → django_activity_audit-1.3.0.dev21}/activity_audit/protocols.py +0 -0
- {django_activity_audit-1.3.0.dev20 → django_activity_audit-1.3.0.dev21}/activity_audit/settings.py +0 -0
- {django_activity_audit-1.3.0.dev20 → django_activity_audit-1.3.0.dev21}/activity_audit/shared_processors.py +0 -0
- {django_activity_audit-1.3.0.dev20 → django_activity_audit-1.3.0.dev21}/activity_audit/signals.py +0 -0
- {django_activity_audit-1.3.0.dev20 → django_activity_audit-1.3.0.dev21}/activity_audit/unregistered.py +0 -0
- {django_activity_audit-1.3.0.dev20 → django_activity_audit-1.3.0.dev21}/activity_audit/utils.py +0 -0
- {django_activity_audit-1.3.0.dev20 → django_activity_audit-1.3.0.dev21}/docs/django-activity-audit.md +0 -0
- {django_activity_audit-1.3.0.dev20 → django_activity_audit-1.3.0.dev21}/docs/hipaa-audit-gaps.md +0 -0
- {django_activity_audit-1.3.0.dev20 → django_activity_audit-1.3.0.dev21}/docs/improvements.md +0 -0
- {django_activity_audit-1.3.0.dev20 → django_activity_audit-1.3.0.dev21}/docs/structlog-integration.md +0 -0
- {django_activity_audit-1.3.0.dev20 → django_activity_audit-1.3.0.dev21}/docs/user-activity-feed-plan.md +0 -0
- {django_activity_audit-1.3.0.dev20 → django_activity_audit-1.3.0.dev21}/hatch +0 -0
- {django_activity_audit-1.3.0.dev20 → django_activity_audit-1.3.0.dev21}/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.
|
|
3
|
+
Version: 1.3.0.dev21
|
|
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.
|
{django_activity_audit-1.3.0.dev20 → django_activity_audit-1.3.0.dev21}/activity_audit/formatters.py
RENAMED
|
@@ -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:
|
{django_activity_audit-1.3.0.dev20 → django_activity_audit-1.3.0.dev21}/activity_audit/middleware.py
RENAMED
|
@@ -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
|
|
@@ -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.
|
{django_activity_audit-1.3.0.dev20 → django_activity_audit-1.3.0.dev21}/.claude/settings.local.json
RENAMED
|
File without changes
|
|
File without changes
|
{django_activity_audit-1.3.0.dev20 → django_activity_audit-1.3.0.dev21}/.pre-commit-config.yaml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{django_activity_audit-1.3.0.dev20 → django_activity_audit-1.3.0.dev21}/activity_audit/__init__.py
RENAMED
|
File without changes
|
{django_activity_audit-1.3.0.dev20 → django_activity_audit-1.3.0.dev21}/activity_audit/apps.py
RENAMED
|
File without changes
|
{django_activity_audit-1.3.0.dev20 → django_activity_audit-1.3.0.dev21}/activity_audit/config.py
RENAMED
|
File without changes
|
{django_activity_audit-1.3.0.dev20 → django_activity_audit-1.3.0.dev21}/activity_audit/constants.py
RENAMED
|
File without changes
|
{django_activity_audit-1.3.0.dev20 → django_activity_audit-1.3.0.dev21}/activity_audit/handlers.py
RENAMED
|
File without changes
|
|
File without changes
|
{django_activity_audit-1.3.0.dev20 → django_activity_audit-1.3.0.dev21}/activity_audit/protocols.py
RENAMED
|
File without changes
|
{django_activity_audit-1.3.0.dev20 → django_activity_audit-1.3.0.dev21}/activity_audit/settings.py
RENAMED
|
File without changes
|
|
File without changes
|
{django_activity_audit-1.3.0.dev20 → django_activity_audit-1.3.0.dev21}/activity_audit/signals.py
RENAMED
|
File without changes
|
|
File without changes
|
{django_activity_audit-1.3.0.dev20 → django_activity_audit-1.3.0.dev21}/activity_audit/utils.py
RENAMED
|
File without changes
|
|
File without changes
|
{django_activity_audit-1.3.0.dev20 → django_activity_audit-1.3.0.dev21}/docs/hipaa-audit-gaps.md
RENAMED
|
File without changes
|
{django_activity_audit-1.3.0.dev20 → django_activity_audit-1.3.0.dev21}/docs/improvements.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|