django-activity-audit 1.3.0.dev9__tar.gz → 1.3.0.dev11__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 (24) hide show
  1. {django_activity_audit-1.3.0.dev9 → django_activity_audit-1.3.0.dev11}/PKG-INFO +1 -1
  2. {django_activity_audit-1.3.0.dev9 → django_activity_audit-1.3.0.dev11}/activity_audit/__init__.py +8 -0
  3. {django_activity_audit-1.3.0.dev9 → django_activity_audit-1.3.0.dev11}/activity_audit/formatters.py +5 -0
  4. {django_activity_audit-1.3.0.dev9 → django_activity_audit-1.3.0.dev11}/activity_audit/middleware.py +18 -0
  5. {django_activity_audit-1.3.0.dev9 → django_activity_audit-1.3.0.dev11}/activity_audit/signals.py +2 -1
  6. {django_activity_audit-1.3.0.dev9 → django_activity_audit-1.3.0.dev11/docs}/django-activity-audit.md +83 -2
  7. {django_activity_audit-1.3.0.dev9 → django_activity_audit-1.3.0.dev11}/pyproject.toml +1 -1
  8. {django_activity_audit-1.3.0.dev9 → django_activity_audit-1.3.0.dev11}/.gitignore +0 -0
  9. {django_activity_audit-1.3.0.dev9 → django_activity_audit-1.3.0.dev11}/.pre-commit-config.yaml +0 -0
  10. {django_activity_audit-1.3.0.dev9 → django_activity_audit-1.3.0.dev11}/LICENSE +0 -0
  11. {django_activity_audit-1.3.0.dev9 → django_activity_audit-1.3.0.dev11}/MANIFEST.in +0 -0
  12. {django_activity_audit-1.3.0.dev9 → django_activity_audit-1.3.0.dev11}/README.md +0 -0
  13. {django_activity_audit-1.3.0.dev9 → django_activity_audit-1.3.0.dev11}/README.rst +0 -0
  14. {django_activity_audit-1.3.0.dev9 → django_activity_audit-1.3.0.dev11}/activity_audit/apps.py +0 -0
  15. {django_activity_audit-1.3.0.dev9 → django_activity_audit-1.3.0.dev11}/activity_audit/constants.py +0 -0
  16. {django_activity_audit-1.3.0.dev9 → django_activity_audit-1.3.0.dev11}/activity_audit/handlers.py +0 -0
  17. {django_activity_audit-1.3.0.dev9 → django_activity_audit-1.3.0.dev11}/activity_audit/logger_levels.py +0 -0
  18. {django_activity_audit-1.3.0.dev9 → django_activity_audit-1.3.0.dev11}/activity_audit/protocols.py +0 -0
  19. {django_activity_audit-1.3.0.dev9 → django_activity_audit-1.3.0.dev11}/activity_audit/settings.py +0 -0
  20. {django_activity_audit-1.3.0.dev9 → django_activity_audit-1.3.0.dev11}/activity_audit/utils.py +0 -0
  21. {django_activity_audit-1.3.0.dev9 → django_activity_audit-1.3.0.dev11/docs}/user-activity-feed-plan.md +0 -0
  22. {django_activity_audit-1.3.0.dev9 → django_activity_audit-1.3.0.dev11}/hatch +0 -0
  23. {django_activity_audit-1.3.0.dev9 → django_activity_audit-1.3.0.dev11}/pytest.ini +0 -0
  24. {django_activity_audit-1.3.0.dev9 → django_activity_audit-1.3.0.dev11}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: django-activity-audit
3
- Version: 1.3.0.dev9
3
+ Version: 1.3.0.dev11
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
@@ -2,6 +2,10 @@ default_app_config = "activity_audit.apps.AuditLoggingConfig"
2
2
 
3
3
  from activity_audit.utils import (
4
4
  get_api_handler,
5
+ get_async_api_handler,
6
+ get_async_audit_handler,
7
+ get_async_json_handler,
8
+ get_async_login_handler,
5
9
  get_audit_handler,
6
10
  get_console_formatter,
7
11
  get_json_formatter,
@@ -18,4 +22,8 @@ __all__ = [
18
22
  "get_api_handler",
19
23
  "get_audit_handler",
20
24
  "get_login_handler",
25
+ "get_async_json_handler",
26
+ "get_async_api_handler",
27
+ "get_async_audit_handler",
28
+ "get_async_login_handler",
21
29
  ]
@@ -4,6 +4,8 @@ import json
4
4
  import logging
5
5
  import uuid
6
6
 
7
+ from activity_audit.middleware import get_request_id
8
+
7
9
 
8
10
  def _json_default(obj):
9
11
  """
@@ -47,6 +49,7 @@ class JsonFormatter(logging.Formatter):
47
49
  "path": record.pathname,
48
50
  "module": record.module,
49
51
  "function": record.funcName,
52
+ "request_id": get_request_id() or "",
50
53
  "message": record.getMessage(),
51
54
  "exception": "",
52
55
  # "extra": {},
@@ -86,6 +89,7 @@ class APIFormatter(logging.Formatter):
86
89
  "service_name",
87
90
  "request_type",
88
91
  "protocol",
92
+ "request_id",
89
93
  "user_id",
90
94
  "user_info",
91
95
  "request_repr",
@@ -117,6 +121,7 @@ class AuditFormatter(logging.Formatter):
117
121
  audit_fields = [
118
122
  "model",
119
123
  "event_type",
124
+ "request_id",
120
125
  "instance_id",
121
126
  "instance_repr",
122
127
  "user_id",
@@ -3,6 +3,7 @@ import json
3
3
  import logging
4
4
  import re
5
5
  import time
6
+ import uuid
6
7
 
7
8
  from asgiref.local import Local
8
9
  from asgiref.sync import iscoroutinefunction, markcoroutinefunction, sync_to_async
@@ -32,6 +33,14 @@ def set_current_request(request):
32
33
  _thread_locals.request = request
33
34
 
34
35
 
36
+ def get_request_id():
37
+ return getattr(_thread_locals, "request_id", None)
38
+
39
+
40
+ def set_request_id(request_id):
41
+ _thread_locals.request_id = request_id
42
+
43
+
35
44
  def get_current_user():
36
45
  request = get_current_request()
37
46
  if request:
@@ -68,6 +77,8 @@ def get_user_details():
68
77
  def clear_request():
69
78
  with contextlib.suppress(AttributeError):
70
79
  del _thread_locals.request
80
+ with contextlib.suppress(AttributeError):
81
+ del _thread_locals.request_id
71
82
 
72
83
 
73
84
  def should_log_url(url):
@@ -119,6 +130,7 @@ class AuditLoggingMiddleware(MiddlewareMixin):
119
130
  "service_name": SERVICE_NAME,
120
131
  "request_type": REQUEST_TYPES[0],
121
132
  "protocol": None,
133
+ "request_id": "",
122
134
  "user_id": "",
123
135
  "user_info": {},
124
136
  "request_repr": {},
@@ -134,6 +146,8 @@ class AuditLoggingMiddleware(MiddlewareMixin):
134
146
  if iscoroutinefunction(self):
135
147
  return self.__acall__(request)
136
148
  set_current_request(request)
149
+ request_id = str(uuid.uuid4())
150
+ set_request_id(request_id)
137
151
 
138
152
  if not should_log_url(request.path):
139
153
  return self.get_response(request)
@@ -184,6 +198,7 @@ class AuditLoggingMiddleware(MiddlewareMixin):
184
198
 
185
199
  self.log_data["execution_time"] = end_time - start_time
186
200
  self.log_data["protocol"] = "https" if request.is_secure() else "http"
201
+ self.log_data["request_id"] = request_id
187
202
  self.log_data["request_repr"] = request_data
188
203
  self.log_data["response_repr"] = response_data
189
204
 
@@ -195,6 +210,8 @@ class AuditLoggingMiddleware(MiddlewareMixin):
195
210
 
196
211
  async def __acall__(self, request):
197
212
  set_current_request(request)
213
+ request_id = str(uuid.uuid4())
214
+ set_request_id(request_id)
198
215
 
199
216
  if not should_log_url(request.path):
200
217
  return await self.get_response(request)
@@ -247,6 +264,7 @@ class AuditLoggingMiddleware(MiddlewareMixin):
247
264
 
248
265
  self.log_data["execution_time"] = end_time - start_time
249
266
  self.log_data["protocol"] = "https" if request.is_secure() else "http"
267
+ self.log_data["request_id"] = request_id
250
268
  self.log_data["request_repr"] = request_data
251
269
  self.log_data["response_repr"] = response_data
252
270
 
@@ -15,7 +15,7 @@ from django.db.models.signals import (
15
15
  from django.dispatch import receiver
16
16
  from django.forms.models import model_to_dict
17
17
 
18
- from activity_audit.middleware import get_user_details
18
+ from activity_audit.middleware import get_request_id, get_user_details
19
19
  from activity_audit.settings import UNREGISTERED_CLASSES
20
20
 
21
21
  logger = logging.getLogger("audit.model")
@@ -96,6 +96,7 @@ def push_log(
96
96
  "model": model,
97
97
  "instance_id": str(instance_id),
98
98
  "event_type": event_type,
99
+ "request_id": get_request_id() or "",
99
100
  "user_id": user_id,
100
101
  "user_info": user_info,
101
102
  "instance_repr": instance_repr,
@@ -620,6 +620,87 @@ INSERT INTO rbp_stag_logs.audit FORMAT JSONEachRow
620
620
 
621
621
  ---
622
622
 
623
- ## Future Scope
623
+ ## Part E: Async Handlers
624
624
 
625
- - Async implementation using **`QueueListener`** and **`QueueHandler`**.
625
+ All four handler types have non-blocking async variants. Each async handler subclasses `QueueHandler` — the calling (request) thread only enqueues the log record, and a dedicated background thread owned by a `QueueListener` performs the actual file I/O.
626
+
627
+ ### How It Works
628
+
629
+ ```
630
+ Request thread Background thread
631
+ ────────────── ─────────────────
632
+ emit(record)
633
+ └─ queue.put() ───► QueueListener.dequeue()
634
+ (returns immediately) └─ RotatingFileHandler.emit()
635
+ └─ write to file + rotate if needed
636
+ ```
637
+
638
+ ### Async Handler Classes
639
+
640
+ | Async Handler | Wraps | Formatter |
641
+ |---|---|---|
642
+ | `AsyncAPILogHandler` | `APILogHandler` | `APIFormatter` |
643
+ | `AsyncAuditLogHandler` | `AuditLogHandler` | `AuditFormatter` |
644
+ | `AsyncLoginLogHandler` | `LoginLogHandler` | `LoginFormatter` |
645
+ | `AsyncJsonHandler` | `RotatingFileHandler` | `JsonFormatter` |
646
+
647
+ Each handler starts its `QueueListener` thread on `__init__` and stops it cleanly on `close()` (called automatically by Django on shutdown).
648
+
649
+ ### Utility Functions
650
+
651
+ Async counterparts match the signatures of their sync equivalents:
652
+
653
+ ```python
654
+ from activity_audit import (
655
+ get_async_json_handler,
656
+ get_async_api_handler,
657
+ get_async_audit_handler,
658
+ get_async_login_handler,
659
+ )
660
+ ```
661
+
662
+ #### `get_async_json_handler`
663
+
664
+ ```python
665
+ get_async_json_handler(
666
+ level="DEBUG",
667
+ filename="audit_logs/app.log",
668
+ max_bytes=1024 * 1024 * 10, # 10MB
669
+ backup_count=5,
670
+ )
671
+ ```
672
+
673
+ > Note: unlike `get_json_handler`, there is no `formatter` parameter — `JsonFormatter` is embedded directly on the inner handler so it applies on the background thread.
674
+
675
+ #### `get_async_api_handler`
676
+
677
+ ```python
678
+ get_async_api_handler(filename="audit_logs/api.log")
679
+ ```
680
+
681
+ #### `get_async_audit_handler`
682
+
683
+ ```python
684
+ get_async_audit_handler(filename="audit_logs/audit.log")
685
+ ```
686
+
687
+ #### `get_async_login_handler`
688
+
689
+ ```python
690
+ get_async_login_handler(filename="audit_logs/login.log")
691
+ ```
692
+
693
+ ### Example Django LOGGING Configuration
694
+
695
+ ```python
696
+ LOGGING = {
697
+ "version": 1,
698
+ "handlers": {
699
+ "app_file": get_async_json_handler(level="INFO", filename="audit_logs/app.log"),
700
+ "api_file": get_async_api_handler(filename="audit_logs/api.log"),
701
+ "audit_file": get_async_audit_handler(filename="audit_logs/audit.log"),
702
+ "login_file": get_async_login_handler(filename="audit_logs/login.log"),
703
+ },
704
+ ...
705
+ }
706
+ ```
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "django-activity-audit"
3
- version = "1.3.0.dev9"
3
+ version = "1.3.0.dev11"
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" }