django-activity-audit 1.3.0.dev19__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.
Files changed (31) hide show
  1. {django_activity_audit-1.3.0.dev19 → django_activity_audit-1.3.0.dev21}/PKG-INFO +90 -32
  2. {django_activity_audit-1.3.0.dev19 → django_activity_audit-1.3.0.dev21}/README.md +87 -28
  3. django_activity_audit-1.3.0.dev21/README.rst +341 -0
  4. {django_activity_audit-1.3.0.dev19 → django_activity_audit-1.3.0.dev21}/activity_audit/config.py +16 -0
  5. {django_activity_audit-1.3.0.dev19 → django_activity_audit-1.3.0.dev21}/activity_audit/formatters.py +5 -1
  6. {django_activity_audit-1.3.0.dev19 → django_activity_audit-1.3.0.dev21}/activity_audit/middleware.py +26 -34
  7. {django_activity_audit-1.3.0.dev19 → django_activity_audit-1.3.0.dev21}/activity_audit/shared_processors.py +20 -0
  8. {django_activity_audit-1.3.0.dev19 → django_activity_audit-1.3.0.dev21}/activity_audit/utils.py +1 -1
  9. {django_activity_audit-1.3.0.dev19 → django_activity_audit-1.3.0.dev21}/pyproject.toml +3 -3
  10. django_activity_audit-1.3.0.dev19/README.rst +0 -263
  11. {django_activity_audit-1.3.0.dev19 → django_activity_audit-1.3.0.dev21}/.claude/settings.local.json +0 -0
  12. {django_activity_audit-1.3.0.dev19 → django_activity_audit-1.3.0.dev21}/.gitignore +0 -0
  13. {django_activity_audit-1.3.0.dev19 → django_activity_audit-1.3.0.dev21}/.pre-commit-config.yaml +0 -0
  14. {django_activity_audit-1.3.0.dev19 → django_activity_audit-1.3.0.dev21}/LICENSE +0 -0
  15. {django_activity_audit-1.3.0.dev19 → django_activity_audit-1.3.0.dev21}/MANIFEST.in +0 -0
  16. {django_activity_audit-1.3.0.dev19 → django_activity_audit-1.3.0.dev21}/activity_audit/__init__.py +0 -0
  17. {django_activity_audit-1.3.0.dev19 → django_activity_audit-1.3.0.dev21}/activity_audit/apps.py +0 -0
  18. {django_activity_audit-1.3.0.dev19 → django_activity_audit-1.3.0.dev21}/activity_audit/constants.py +0 -0
  19. {django_activity_audit-1.3.0.dev19 → django_activity_audit-1.3.0.dev21}/activity_audit/handlers.py +0 -0
  20. {django_activity_audit-1.3.0.dev19 → django_activity_audit-1.3.0.dev21}/activity_audit/logger_levels.py +0 -0
  21. {django_activity_audit-1.3.0.dev19 → django_activity_audit-1.3.0.dev21}/activity_audit/protocols.py +0 -0
  22. {django_activity_audit-1.3.0.dev19 → django_activity_audit-1.3.0.dev21}/activity_audit/settings.py +0 -0
  23. {django_activity_audit-1.3.0.dev19 → django_activity_audit-1.3.0.dev21}/activity_audit/signals.py +0 -0
  24. {django_activity_audit-1.3.0.dev19 → django_activity_audit-1.3.0.dev21}/activity_audit/unregistered.py +0 -0
  25. {django_activity_audit-1.3.0.dev19 → django_activity_audit-1.3.0.dev21}/docs/django-activity-audit.md +0 -0
  26. {django_activity_audit-1.3.0.dev19 → django_activity_audit-1.3.0.dev21}/docs/hipaa-audit-gaps.md +0 -0
  27. {django_activity_audit-1.3.0.dev19 → django_activity_audit-1.3.0.dev21}/docs/improvements.md +0 -0
  28. {django_activity_audit-1.3.0.dev19 → django_activity_audit-1.3.0.dev21}/docs/structlog-integration.md +0 -0
  29. {django_activity_audit-1.3.0.dev19 → django_activity_audit-1.3.0.dev21}/docs/user-activity-feed-plan.md +0 -0
  30. {django_activity_audit-1.3.0.dev19 → django_activity_audit-1.3.0.dev21}/hatch +0 -0
  31. {django_activity_audit-1.3.0.dev19 → 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.dev19
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
@@ -26,19 +26,18 @@ Classifier: Programming Language :: Python :: 3.12
26
26
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
27
27
  Requires-Python: >=3.8
28
28
  Requires-Dist: django<6.0,>=4.2
29
+ Requires-Dist: orjson>=3.0
30
+ Requires-Dist: structlog>=24.0
29
31
  Provides-Extra: dev
30
32
  Requires-Dist: pre-commit>=3.5; (python_version >= '3.9') and extra == 'dev'
31
33
  Requires-Dist: pre-commit~=3.5; (python_version < '3.9') and extra == 'dev'
32
34
  Requires-Dist: ruff>=0.1.11; extra == 'dev'
33
- Provides-Extra: structlog
34
- Requires-Dist: structlog>=24.0; extra == 'structlog'
35
35
  Provides-Extra: test
36
36
  Requires-Dist: djangorestframework>=3.14.0; extra == 'test'
37
37
  Requires-Dist: factory-boy>=3.2.0; extra == 'test'
38
38
  Requires-Dist: pytest-cov>=4.0.0; extra == 'test'
39
39
  Requires-Dist: pytest-django>=4.0.0; extra == 'test'
40
40
  Requires-Dist: pytest>=6.0.0; extra == 'test'
41
- Requires-Dist: structlog>=24.0; extra == 'test'
42
41
  Description-Content-Type: text/markdown
43
42
 
44
43
  # Django Activity Audit
@@ -62,6 +61,8 @@ A Django package that extends the default logging mechanism to track CRUD operat
62
61
  pip install django-activity-audit
63
62
  ```
64
63
 
64
+ This also installs [`structlog`](https://www.structlog.org/) and [`orjson`](https://github.com/ijl/orjson) as required dependencies.
65
+
65
66
  2. Add 'activity_audit' to your INSTALLED_APPS in settings.py:
66
67
  ```python
67
68
  INSTALLED_APPS = [
@@ -79,47 +80,101 @@ MIDDLEWARE = [
79
80
  ```
80
81
 
81
82
  4. Configure logging in settings.py:
83
+
84
+ Import the formatter helpers from `activity_audit.config`:
85
+
82
86
  ```python
83
- from activity_audit import *
87
+ from activity_audit.config import get_plain_formatter, get_stdlib_formatter
88
+ ```
89
+
90
+ - `get_stdlib_formatter()` — structlog JSON renderer. Use in staging/production where logs are ingested by a pipeline (Vector, CloudWatch, etc.).
91
+ - `get_plain_formatter()` — structlog plain-text renderer. Use locally for human-readable console output.
92
+
93
+ **Local development** (plain text output):
94
+
95
+ ```python
96
+ from activity_audit.config import get_plain_formatter, get_stdlib_formatter
84
97
 
85
98
  LOGGING = {
86
99
  "version": 1,
87
100
  "disable_existing_loggers": False,
88
101
  "formatters": {
89
- "json": get_json_formatter(),
90
- "verbose": get_console_formatter(),
102
+ "structlog": get_stdlib_formatter(),
103
+ "default": get_plain_formatter(),
91
104
  },
92
105
  "handlers": {
93
- "console": {
94
- "level": "DEBUG",
95
- "class": "logging.StreamHandler",
96
- "formatter": "verbose",
97
- },
98
- "file": get_json_handler(level="DEBUG", formatter="json"),
99
- "api_file": get_api_file_handler(),
100
- "audit_file": get_audit_handler(),
106
+ "console": {"class": "logging.StreamHandler", "formatter": "default"},
107
+ "console_struct": {"class": "logging.StreamHandler", "formatter": "structlog"},
108
+ },
109
+ "root": {
110
+ "level": "INFO",
111
+ "handlers": ["console"], # plain text fallback for all loggers
101
112
  },
102
- "root": {"level": "DEBUG", "handlers": ["console", "file"]},
103
113
  "loggers": {
104
- "audit.request": {
105
- "handlers": ["api_file"],
106
- "level": "API",
107
- "propagate": False,
108
- },
109
- "audit.model": {
110
- "handlers": ["audit_file"],
111
- "level": "AUDIT",
112
- "propagate": False,
113
- },
114
- "django": {
115
- "handlers": ["console", "file"],
116
- "level": "INFO",
117
- "propagate": False,
118
- },
119
- }
114
+ # Structlog owns these — explicit handler, no propagation to avoid double output
115
+ "audit.model": {"handlers": ["console_struct"], "propagate": False},
116
+ "audit.request": {"handlers": ["console_struct"], "propagate": False},
117
+ "audit.login": {"handlers": ["console_struct"], "propagate": False},
118
+
119
+ # Celery — structlog formats log_type correctly
120
+ "celery": {"level": "INFO", "handlers": ["console_struct"], "propagate": False},
121
+ "celery.task": {"level": "INFO", "handlers": ["console_struct"], "propagate": False},
122
+ "celery.beat": {"level": "INFO", "handlers": ["console_struct"], "propagate": False},
123
+
124
+ # Third-party noise control — WARNING only, routed to root
125
+ "django.db.backends": {"level": "WARNING", "handlers": [], "propagate": True},
126
+ "boto3": {"level": "WARNING", "handlers": [], "propagate": True},
127
+ "botocore": {"level": "WARNING", "handlers": [], "propagate": True},
128
+
129
+ # Framework loggers
130
+ "django": {"level": "INFO", "handlers": [], "propagate": True},
131
+ "uvicorn": {"level": "INFO", "handlers": [], "propagate": True},
132
+ "uvicorn.error": {"level": "INFO", "handlers": [], "propagate": True},
133
+ "uvicorn.access":{"level": "INFO", "handlers": [], "propagate": True},
134
+ },
120
135
  }
121
136
  ```
122
137
 
138
+ **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):
139
+
140
+ ```python
141
+ "root": {
142
+ "level": "INFO",
143
+ "handlers": ["console"],
144
+ }
145
+ ```
146
+
147
+ ---
148
+
149
+ ### When to add a logger entry
150
+
151
+ Add an explicit logger entry when you need **any** of the following:
152
+
153
+ | Situation | What to set |
154
+ |-----------|-------------|
155
+ | Route to structured JSON (`console_struct`) | `handlers: ["console_struct"], propagate: False` |
156
+ | Suppress a noisy third-party library | `level: "WARNING", handlers: [], propagate: True` |
157
+ | Prevent double output for a structlog-owned logger | `handlers: ["console_struct"], propagate: False` |
158
+ | Change the log level for a specific namespace | Set `level` explicitly |
159
+
160
+ **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.
161
+
162
+ ### Silencing audit loggers (route to root instead of structlog)
163
+
164
+ 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.
165
+
166
+ To stop structlog from handling them and fall back to the plain-text root logger instead, set `handlers: []` and `propagate: True`:
167
+
168
+ ```python
169
+ "loggers": {
170
+ "audit.model": {"handlers": [], "propagate": True},
171
+ "audit.request": {"handlers": [], "propagate": True},
172
+ "audit.login": {"handlers": [], "propagate": True},
173
+ }
174
+ ```
175
+
176
+ 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.
177
+
123
178
  5. Configure the service name in `settings.py` (optional, defaults to `"default"`):
124
179
  ```python
125
180
  AUDIT_SERVICE_NAME = "my_service"
@@ -170,6 +225,7 @@ INFO 2025-04-30 08:51:10,403 /app/patients/api/utils.py utils create_patient_wit
170
225
  "level": "AUDIT",
171
226
  "name": "audit.model",
172
227
  "message": "CREATE event by User (id: 6f77b814-f9c1-4cab-a737-6677734bc303)",
228
+ "request_id": "f3c9a1b2-0001-4abc-beef-deadbeef0001",
173
229
  "model": "User",
174
230
  "event_type": "CREATE",
175
231
  "instance_id": "6f77b814-f9c1-4cab-a737-6677734bc303",
@@ -200,6 +256,7 @@ INFO 2025-04-30 08:51:10,403 /app/patients/api/utils.py utils create_patient_wit
200
256
  "level": "API",
201
257
  "name": "audit.request",
202
258
  "message": "Audit Internal Request",
259
+ "request_id": "f3c9a1b2-0001-4abc-beef-deadbeef0001",
203
260
  "service_name": "my_service",
204
261
  "request_type": "internal",
205
262
  "protocol": "http",
@@ -246,6 +303,7 @@ INFO 2025-04-30 08:51:10,403 /app/patients/api/utils.py utils create_patient_wit
246
303
  "level": "API",
247
304
  "name": "audit.request",
248
305
  "message": "Audit External Service",
306
+ "request_id": "f3c9a1b2-0001-4abc-beef-deadbeef0001",
249
307
  "service_name": "apollo",
250
308
  "request_type": "external",
251
309
  "protocol": "http",
@@ -19,6 +19,8 @@ A Django package that extends the default logging mechanism to track CRUD operat
19
19
  pip install django-activity-audit
20
20
  ```
21
21
 
22
+ This also installs [`structlog`](https://www.structlog.org/) and [`orjson`](https://github.com/ijl/orjson) as required dependencies.
23
+
22
24
  2. Add 'activity_audit' to your INSTALLED_APPS in settings.py:
23
25
  ```python
24
26
  INSTALLED_APPS = [
@@ -36,47 +38,101 @@ MIDDLEWARE = [
36
38
  ```
37
39
 
38
40
  4. Configure logging in settings.py:
41
+
42
+ Import the formatter helpers from `activity_audit.config`:
43
+
39
44
  ```python
40
- from activity_audit import *
45
+ from activity_audit.config import get_plain_formatter, get_stdlib_formatter
46
+ ```
47
+
48
+ - `get_stdlib_formatter()` — structlog JSON renderer. Use in staging/production where logs are ingested by a pipeline (Vector, CloudWatch, etc.).
49
+ - `get_plain_formatter()` — structlog plain-text renderer. Use locally for human-readable console output.
50
+
51
+ **Local development** (plain text output):
52
+
53
+ ```python
54
+ from activity_audit.config import get_plain_formatter, get_stdlib_formatter
41
55
 
42
56
  LOGGING = {
43
57
  "version": 1,
44
58
  "disable_existing_loggers": False,
45
59
  "formatters": {
46
- "json": get_json_formatter(),
47
- "verbose": get_console_formatter(),
60
+ "structlog": get_stdlib_formatter(),
61
+ "default": get_plain_formatter(),
48
62
  },
49
63
  "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(),
64
+ "console": {"class": "logging.StreamHandler", "formatter": "default"},
65
+ "console_struct": {"class": "logging.StreamHandler", "formatter": "structlog"},
66
+ },
67
+ "root": {
68
+ "level": "INFO",
69
+ "handlers": ["console"], # plain text fallback for all loggers
58
70
  },
59
- "root": {"level": "DEBUG", "handlers": ["console", "file"]},
60
71
  "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
- }
72
+ # Structlog owns these — explicit handler, no propagation to avoid double output
73
+ "audit.model": {"handlers": ["console_struct"], "propagate": False},
74
+ "audit.request": {"handlers": ["console_struct"], "propagate": False},
75
+ "audit.login": {"handlers": ["console_struct"], "propagate": False},
76
+
77
+ # Celery — structlog formats log_type correctly
78
+ "celery": {"level": "INFO", "handlers": ["console_struct"], "propagate": False},
79
+ "celery.task": {"level": "INFO", "handlers": ["console_struct"], "propagate": False},
80
+ "celery.beat": {"level": "INFO", "handlers": ["console_struct"], "propagate": False},
81
+
82
+ # Third-party noise control — WARNING only, routed to root
83
+ "django.db.backends": {"level": "WARNING", "handlers": [], "propagate": True},
84
+ "boto3": {"level": "WARNING", "handlers": [], "propagate": True},
85
+ "botocore": {"level": "WARNING", "handlers": [], "propagate": True},
86
+
87
+ # Framework loggers
88
+ "django": {"level": "INFO", "handlers": [], "propagate": True},
89
+ "uvicorn": {"level": "INFO", "handlers": [], "propagate": True},
90
+ "uvicorn.error": {"level": "INFO", "handlers": [], "propagate": True},
91
+ "uvicorn.access":{"level": "INFO", "handlers": [], "propagate": True},
92
+ },
77
93
  }
78
94
  ```
79
95
 
96
+ **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):
97
+
98
+ ```python
99
+ "root": {
100
+ "level": "INFO",
101
+ "handlers": ["console"],
102
+ }
103
+ ```
104
+
105
+ ---
106
+
107
+ ### When to add a logger entry
108
+
109
+ Add an explicit logger entry when you need **any** of the following:
110
+
111
+ | Situation | What to set |
112
+ |-----------|-------------|
113
+ | Route to structured JSON (`console_struct`) | `handlers: ["console_struct"], propagate: False` |
114
+ | Suppress a noisy third-party library | `level: "WARNING", handlers: [], propagate: True` |
115
+ | Prevent double output for a structlog-owned logger | `handlers: ["console_struct"], propagate: False` |
116
+ | Change the log level for a specific namespace | Set `level` explicitly |
117
+
118
+ **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.
119
+
120
+ ### Silencing audit loggers (route to root instead of structlog)
121
+
122
+ 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.
123
+
124
+ To stop structlog from handling them and fall back to the plain-text root logger instead, set `handlers: []` and `propagate: True`:
125
+
126
+ ```python
127
+ "loggers": {
128
+ "audit.model": {"handlers": [], "propagate": True},
129
+ "audit.request": {"handlers": [], "propagate": True},
130
+ "audit.login": {"handlers": [], "propagate": True},
131
+ }
132
+ ```
133
+
134
+ 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.
135
+
80
136
  5. Configure the service name in `settings.py` (optional, defaults to `"default"`):
81
137
  ```python
82
138
  AUDIT_SERVICE_NAME = "my_service"
@@ -127,6 +183,7 @@ INFO 2025-04-30 08:51:10,403 /app/patients/api/utils.py utils create_patient_wit
127
183
  "level": "AUDIT",
128
184
  "name": "audit.model",
129
185
  "message": "CREATE event by User (id: 6f77b814-f9c1-4cab-a737-6677734bc303)",
186
+ "request_id": "f3c9a1b2-0001-4abc-beef-deadbeef0001",
130
187
  "model": "User",
131
188
  "event_type": "CREATE",
132
189
  "instance_id": "6f77b814-f9c1-4cab-a737-6677734bc303",
@@ -157,6 +214,7 @@ INFO 2025-04-30 08:51:10,403 /app/patients/api/utils.py utils create_patient_wit
157
214
  "level": "API",
158
215
  "name": "audit.request",
159
216
  "message": "Audit Internal Request",
217
+ "request_id": "f3c9a1b2-0001-4abc-beef-deadbeef0001",
160
218
  "service_name": "my_service",
161
219
  "request_type": "internal",
162
220
  "protocol": "http",
@@ -203,6 +261,7 @@ INFO 2025-04-30 08:51:10,403 /app/patients/api/utils.py utils create_patient_wit
203
261
  "level": "API",
204
262
  "name": "audit.request",
205
263
  "message": "Audit External Service",
264
+ "request_id": "f3c9a1b2-0001-4abc-beef-deadbeef0001",
206
265
  "service_name": "apollo",
207
266
  "request_type": "external",
208
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.
@@ -20,6 +20,22 @@ class AuditBoundLogger(structlog.stdlib.BoundLogger):
20
20
  return self._proxy_to_logger("login", event, *args, **kw)
21
21
 
22
22
 
23
+ def get_plain_formatter() -> dict:
24
+ """
25
+ LOGGING formatter dict that routes stdlib loggers through the structlog
26
+ processor chain but renders as plain text instead of JSON. Use for local
27
+ development when human-readable output is preferred over structured JSON.
28
+ """
29
+ return {
30
+ "()": structlog.stdlib.ProcessorFormatter,
31
+ "processors": [
32
+ structlog.stdlib.ProcessorFormatter.remove_processors_meta,
33
+ structlog.dev.ConsoleRenderer(colors=False),
34
+ ],
35
+ "foreign_pre_chain": shared_processors,
36
+ }
37
+
38
+
23
39
  def get_stdlib_formatter() -> dict:
24
40
  """
25
41
  LOGGING formatter dict that routes stdlib loggers through the structlog
@@ -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:
@@ -1,16 +1,15 @@
1
- import contextlib
2
1
  import json
3
2
  import re
4
3
  import time
5
4
  import uuid
6
5
 
6
+ from contextvars import ContextVar
7
+
7
8
  import structlog.contextvars as ctx
8
9
 
9
- from asgiref.local import Local
10
10
  from asgiref.sync import (
11
11
  iscoroutinefunction,
12
12
  markcoroutinefunction,
13
- sync_to_async,
14
13
  )
15
14
  from django.http import HttpResponse
16
15
  from django.utils.deprecation import MiddlewareMixin
@@ -21,7 +20,7 @@ from .settings import REGISTERED_URLS, SERVICE_NAME, UNREGISTERED_URLS
21
20
 
22
21
  _log = get_logger("audit.request")
23
22
 
24
- _thread_locals = Local()
23
+ _request_var: ContextVar = ContextVar("current_request", default=None)
25
24
 
26
25
 
27
26
  class MockRequest:
@@ -32,11 +31,11 @@ class MockRequest:
32
31
 
33
32
 
34
33
  def get_current_request():
35
- return getattr(_thread_locals, "request", None)
34
+ return _request_var.get()
36
35
 
37
36
 
38
37
  def set_current_request(request):
39
- _thread_locals.request = request
38
+ _request_var.set(request)
40
39
 
41
40
 
42
41
  def get_current_user():
@@ -47,11 +46,11 @@ def get_current_user():
47
46
 
48
47
 
49
48
  def set_current_user(user):
50
- try:
51
- _thread_locals.request.user = user
52
- except AttributeError:
53
- request = MockRequest(user=user)
54
- _thread_locals.request = request
49
+ request = _request_var.get()
50
+ if request is not None:
51
+ request.user = user
52
+ else:
53
+ _request_var.set(MockRequest(user=user))
55
54
 
56
55
 
57
56
  def get_user_details():
@@ -73,8 +72,7 @@ def get_user_details():
73
72
 
74
73
 
75
74
  def clear_request():
76
- with contextlib.suppress(AttributeError):
77
- del _thread_locals.request
75
+ _request_var.set(None)
78
76
 
79
77
 
80
78
  def should_log_url(url):
@@ -120,6 +118,18 @@ class AuditLoggingMiddleware(MiddlewareMixin):
120
118
  }
121
119
  """
122
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
+
123
133
  def __init__(self, get_response):
124
134
  self.get_response = get_response
125
135
 
@@ -137,15 +147,7 @@ class AuditLoggingMiddleware(MiddlewareMixin):
137
147
  if not should_log_url(request.path):
138
148
  return self.get_response(request)
139
149
 
140
- log_data = {
141
- "service_name": SERVICE_NAME,
142
- "request_type": REQUEST_TYPES[0],
143
- "protocol": None,
144
- "request_repr": {},
145
- "response_repr": {},
146
- "error_message": None,
147
- "execution_time": 0,
148
- }
150
+ log_data = self._init_log_data()
149
151
  start_time = time.time()
150
152
 
151
153
  # Log request
@@ -211,15 +213,7 @@ class AuditLoggingMiddleware(MiddlewareMixin):
211
213
  if not should_log_url(request.path):
212
214
  return await self.get_response(request)
213
215
 
214
- log_data = {
215
- "service_name": SERVICE_NAME,
216
- "request_type": REQUEST_TYPES[0],
217
- "protocol": None,
218
- "request_repr": {},
219
- "response_repr": {},
220
- "error_message": None,
221
- "execution_time": 0,
222
- }
216
+ log_data = self._init_log_data()
223
217
  start_time = time.time()
224
218
 
225
219
  # Log request
@@ -242,9 +236,7 @@ class AuditLoggingMiddleware(MiddlewareMixin):
242
236
  end_time = time.time()
243
237
 
244
238
  # Capture user details AFTER authentication has happened
245
- user_id, user_info = await sync_to_async(
246
- get_user_details, thread_sensitive=True
247
- )()
239
+ user_id, user_info = get_user_details()
248
240
  ctx.bind_contextvars(user_id=user_id, user_info=user_info)
249
241
 
250
242
  # TODO: Find way to add status code to response_data
@@ -96,6 +96,26 @@ _STANDARD_KEYS = frozenset(
96
96
  "request_id",
97
97
  "_record",
98
98
  "_from_structlog",
99
+ # audit.model fields
100
+ "model",
101
+ "event_type",
102
+ "instance_id",
103
+ "instance_repr",
104
+ "user_id",
105
+ "user_info",
106
+ "extra",
107
+ # audit.request fields
108
+ "service_name",
109
+ "request_type",
110
+ "protocol",
111
+ "request_repr",
112
+ "response_repr",
113
+ "error_message",
114
+ "execution_time",
115
+ # audit.login fields
116
+ "event",
117
+ "success",
118
+ "error",
99
119
  }
100
120
  )
101
121
 
@@ -145,7 +145,7 @@ def push_usage_log(
145
145
  """
146
146
  import logging
147
147
 
148
- from .signals import get_user_details
148
+ from .middleware import get_user_details
149
149
 
150
150
  logger = logging.getLogger("audit.login")
151
151
  user_id, user_info = get_user_details()
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "django-activity-audit"
3
- version = "1.3.0.dev19"
3
+ version = "1.3.0.dev21"
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" }
@@ -31,6 +31,8 @@ classifiers = [
31
31
  requires-python = ">=3.8"
32
32
  dependencies = [
33
33
  "django>=4.2,<6.0",
34
+ "structlog>=24.0",
35
+ "orjson>=3.0",
34
36
  ]
35
37
 
36
38
  [project.urls]
@@ -50,9 +52,7 @@ test = [
50
52
  "pytest-django>=4.0.0",
51
53
  "pytest-cov>=4.0.0",
52
54
  "djangorestframework>=3.14.0",
53
- "structlog>=24.0",
54
55
  ]
55
- structlog = ["structlog>=24.0"]
56
56
 
57
57
  [build-system]
58
58
  requires = ["hatchling"]
@@ -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.