django-activity-audit 1.3.0.dev8__tar.gz → 1.3.0.dev10__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.dev8 → django_activity_audit-1.3.0.dev10}/PKG-INFO +1 -1
- {django_activity_audit-1.3.0.dev8 → django_activity_audit-1.3.0.dev10}/activity_audit/__init__.py +8 -0
- django_activity_audit-1.3.0.dev10/activity_audit/handlers.py +166 -0
- {django_activity_audit-1.3.0.dev8 → django_activity_audit-1.3.0.dev10}/activity_audit/utils.py +52 -0
- django_activity_audit-1.3.0.dev10/django-activity-audit.md +625 -0
- {django_activity_audit-1.3.0.dev8 → django_activity_audit-1.3.0.dev10}/pyproject.toml +1 -1
- django_activity_audit-1.3.0.dev10/user-activity-feed-plan.md +416 -0
- django_activity_audit-1.3.0.dev8/activity_audit/handlers.py +0 -74
- {django_activity_audit-1.3.0.dev8 → django_activity_audit-1.3.0.dev10}/.gitignore +0 -0
- {django_activity_audit-1.3.0.dev8 → django_activity_audit-1.3.0.dev10}/.pre-commit-config.yaml +0 -0
- {django_activity_audit-1.3.0.dev8 → django_activity_audit-1.3.0.dev10}/LICENSE +0 -0
- {django_activity_audit-1.3.0.dev8 → django_activity_audit-1.3.0.dev10}/MANIFEST.in +0 -0
- {django_activity_audit-1.3.0.dev8 → django_activity_audit-1.3.0.dev10}/README.md +0 -0
- {django_activity_audit-1.3.0.dev8 → django_activity_audit-1.3.0.dev10}/README.rst +0 -0
- {django_activity_audit-1.3.0.dev8 → django_activity_audit-1.3.0.dev10}/activity_audit/apps.py +0 -0
- {django_activity_audit-1.3.0.dev8 → django_activity_audit-1.3.0.dev10}/activity_audit/constants.py +0 -0
- {django_activity_audit-1.3.0.dev8 → django_activity_audit-1.3.0.dev10}/activity_audit/formatters.py +0 -0
- {django_activity_audit-1.3.0.dev8 → django_activity_audit-1.3.0.dev10}/activity_audit/logger_levels.py +0 -0
- {django_activity_audit-1.3.0.dev8 → django_activity_audit-1.3.0.dev10}/activity_audit/middleware.py +0 -0
- {django_activity_audit-1.3.0.dev8 → django_activity_audit-1.3.0.dev10}/activity_audit/protocols.py +0 -0
- {django_activity_audit-1.3.0.dev8 → django_activity_audit-1.3.0.dev10}/activity_audit/settings.py +0 -0
- {django_activity_audit-1.3.0.dev8 → django_activity_audit-1.3.0.dev10}/activity_audit/signals.py +0 -0
- {django_activity_audit-1.3.0.dev8 → django_activity_audit-1.3.0.dev10}/hatch +0 -0
- {django_activity_audit-1.3.0.dev8 → django_activity_audit-1.3.0.dev10}/pytest.ini +0 -0
- {django_activity_audit-1.3.0.dev8 → django_activity_audit-1.3.0.dev10}/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.
|
|
3
|
+
Version: 1.3.0.dev10
|
|
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
|
{django_activity_audit-1.3.0.dev8 → django_activity_audit-1.3.0.dev10}/activity_audit/__init__.py
RENAMED
|
@@ -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
|
]
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import queue
|
|
3
|
+
|
|
4
|
+
from logging.handlers import QueueHandler, QueueListener, RotatingFileHandler
|
|
5
|
+
|
|
6
|
+
from .formatters import APIFormatter, AuditFormatter, JsonFormatter, LoginFormatter
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class BaseAuditHandler(RotatingFileHandler):
|
|
10
|
+
"""Base handler for all audit logs with common emit logic."""
|
|
11
|
+
|
|
12
|
+
def emit(self, record):
|
|
13
|
+
"""
|
|
14
|
+
Emit a record with additional values for audit-specific fields.
|
|
15
|
+
"""
|
|
16
|
+
try:
|
|
17
|
+
# Handle extra if present
|
|
18
|
+
if hasattr(record, "extra"):
|
|
19
|
+
for key, value in record.extra.items():
|
|
20
|
+
setattr(record, key, value)
|
|
21
|
+
|
|
22
|
+
super().emit(record)
|
|
23
|
+
|
|
24
|
+
except Exception as e:
|
|
25
|
+
self.handleError(record)
|
|
26
|
+
# Log the error to the root logger
|
|
27
|
+
logging.getLogger().error(f"Error in AuditLogHandler: {str(e)}")
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class APILogHandler(BaseAuditHandler):
|
|
31
|
+
"""Handler for API audit logs with default APIFormatter."""
|
|
32
|
+
|
|
33
|
+
def __init__(
|
|
34
|
+
self,
|
|
35
|
+
filename,
|
|
36
|
+
mode="a",
|
|
37
|
+
maxBytes=0,
|
|
38
|
+
backupCount=0,
|
|
39
|
+
encoding=None,
|
|
40
|
+
delay=False,
|
|
41
|
+
):
|
|
42
|
+
super().__init__(filename, mode, maxBytes, backupCount, encoding, delay)
|
|
43
|
+
self.setFormatter(APIFormatter())
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class AuditLogHandler(BaseAuditHandler):
|
|
47
|
+
"""Handler for model audit logs with default AuditFormatter."""
|
|
48
|
+
|
|
49
|
+
def __init__(
|
|
50
|
+
self,
|
|
51
|
+
filename,
|
|
52
|
+
mode="a",
|
|
53
|
+
maxBytes=0,
|
|
54
|
+
backupCount=0,
|
|
55
|
+
encoding=None,
|
|
56
|
+
delay=False,
|
|
57
|
+
):
|
|
58
|
+
super().__init__(filename, mode, maxBytes, backupCount, encoding, delay)
|
|
59
|
+
self.setFormatter(AuditFormatter())
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class LoginLogHandler(BaseAuditHandler):
|
|
63
|
+
"""Handler for login audit logs with default LoginFormatter."""
|
|
64
|
+
|
|
65
|
+
def __init__(
|
|
66
|
+
self,
|
|
67
|
+
filename,
|
|
68
|
+
mode="a",
|
|
69
|
+
maxBytes=0,
|
|
70
|
+
backupCount=0,
|
|
71
|
+
encoding=None,
|
|
72
|
+
delay=False,
|
|
73
|
+
):
|
|
74
|
+
super().__init__(filename, mode, maxBytes, backupCount, encoding, delay)
|
|
75
|
+
self.setFormatter(LoginFormatter())
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
# ASYNC ------------------------------------------------------
|
|
79
|
+
class AsyncBaseAuditHandler(QueueHandler):
|
|
80
|
+
"""
|
|
81
|
+
Base async handler. Enqueues records on the calling thread; a background
|
|
82
|
+
QueueListener thread does the actual file write via the wrapped sync handler.
|
|
83
|
+
|
|
84
|
+
Subclasses pass the concrete sync handler class and formatter via
|
|
85
|
+
_sync_handler_class and _formatter_class.
|
|
86
|
+
"""
|
|
87
|
+
|
|
88
|
+
_sync_handler_class = None
|
|
89
|
+
_formatter_class = None
|
|
90
|
+
|
|
91
|
+
def __init__(
|
|
92
|
+
self,
|
|
93
|
+
filename,
|
|
94
|
+
mode="a",
|
|
95
|
+
maxBytes=0,
|
|
96
|
+
backupCount=0,
|
|
97
|
+
encoding=None,
|
|
98
|
+
delay=False,
|
|
99
|
+
):
|
|
100
|
+
log_queue = queue.Queue(-1)
|
|
101
|
+
super().__init__(log_queue)
|
|
102
|
+
|
|
103
|
+
sync_handler = self._sync_handler_class(
|
|
104
|
+
filename, mode, maxBytes, backupCount, encoding, delay
|
|
105
|
+
)
|
|
106
|
+
self._listener = QueueListener(
|
|
107
|
+
log_queue, sync_handler, respect_handler_level=True
|
|
108
|
+
)
|
|
109
|
+
self._listener.start()
|
|
110
|
+
|
|
111
|
+
def close(self):
|
|
112
|
+
self._listener.stop()
|
|
113
|
+
super().close()
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
class AsyncAPILogHandler(AsyncBaseAuditHandler):
|
|
117
|
+
"""Non-blocking handler for API audit logs."""
|
|
118
|
+
|
|
119
|
+
_sync_handler_class = APILogHandler
|
|
120
|
+
_formatter_class = APIFormatter
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
class AsyncAuditLogHandler(AsyncBaseAuditHandler):
|
|
124
|
+
"""Non-blocking handler for model audit logs."""
|
|
125
|
+
|
|
126
|
+
_sync_handler_class = AuditLogHandler
|
|
127
|
+
_formatter_class = AuditFormatter
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
class AsyncLoginLogHandler(AsyncBaseAuditHandler):
|
|
131
|
+
"""Non-blocking handler for login audit logs."""
|
|
132
|
+
|
|
133
|
+
_sync_handler_class = LoginLogHandler
|
|
134
|
+
_formatter_class = LoginFormatter
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
class AsyncJsonHandler(QueueHandler):
|
|
138
|
+
"""
|
|
139
|
+
Non-blocking handler for general JSON logs. Wraps a RotatingFileHandler
|
|
140
|
+
with JsonFormatter on the background thread.
|
|
141
|
+
"""
|
|
142
|
+
|
|
143
|
+
def __init__(
|
|
144
|
+
self,
|
|
145
|
+
filename,
|
|
146
|
+
mode="a",
|
|
147
|
+
maxBytes=0,
|
|
148
|
+
backupCount=0,
|
|
149
|
+
encoding=None,
|
|
150
|
+
delay=False,
|
|
151
|
+
):
|
|
152
|
+
log_queue = queue.Queue(-1)
|
|
153
|
+
super().__init__(log_queue)
|
|
154
|
+
|
|
155
|
+
sync_handler = RotatingFileHandler(
|
|
156
|
+
filename, mode, maxBytes, backupCount, encoding, delay
|
|
157
|
+
)
|
|
158
|
+
sync_handler.setFormatter(JsonFormatter())
|
|
159
|
+
self._listener = QueueListener(
|
|
160
|
+
log_queue, sync_handler, respect_handler_level=True
|
|
161
|
+
)
|
|
162
|
+
self._listener.start()
|
|
163
|
+
|
|
164
|
+
def close(self):
|
|
165
|
+
self._listener.stop()
|
|
166
|
+
super().close()
|
{django_activity_audit-1.3.0.dev8 → django_activity_audit-1.3.0.dev10}/activity_audit/utils.py
RENAMED
|
@@ -63,6 +63,58 @@ def get_login_handler(
|
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
|
|
66
|
+
# ASYNC ------------------------------------------------------
|
|
67
|
+
def get_async_json_handler(
|
|
68
|
+
level: str = "DEBUG",
|
|
69
|
+
filename: str = "audit_logs/app.log",
|
|
70
|
+
max_bytes: int = 1024 * 1024 * 10,
|
|
71
|
+
backup_count: int = 5,
|
|
72
|
+
) -> dict:
|
|
73
|
+
return {
|
|
74
|
+
"level": level,
|
|
75
|
+
"class": "activity_audit.handlers.AsyncJsonHandler",
|
|
76
|
+
"filename": filename,
|
|
77
|
+
"maxBytes": max_bytes,
|
|
78
|
+
"backupCount": backup_count,
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def get_async_api_handler(
|
|
83
|
+
filename: str = "audit_logs/api.log",
|
|
84
|
+
) -> dict:
|
|
85
|
+
return {
|
|
86
|
+
"class": "activity_audit.handlers.AsyncAPILogHandler",
|
|
87
|
+
"filename": filename,
|
|
88
|
+
"maxBytes": 1024 * 1024 * 10, # 10MB
|
|
89
|
+
"backupCount": 5,
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def get_async_audit_handler(
|
|
94
|
+
filename: str = "audit_logs/audit.log",
|
|
95
|
+
) -> dict:
|
|
96
|
+
return {
|
|
97
|
+
"class": "activity_audit.handlers.AsyncAuditLogHandler",
|
|
98
|
+
"filename": filename,
|
|
99
|
+
"maxBytes": 1024 * 1024 * 10, # 10MB
|
|
100
|
+
"backupCount": 5,
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def get_async_login_handler(
|
|
105
|
+
filename: str = "audit_logs/login.log",
|
|
106
|
+
) -> dict:
|
|
107
|
+
return {
|
|
108
|
+
"class": "activity_audit.handlers.AsyncLoginLogHandler",
|
|
109
|
+
"filename": filename,
|
|
110
|
+
"maxBytes": 1024 * 1024 * 5, # 5MB
|
|
111
|
+
"backupCount": 5,
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
# ----------------------------------------------------------------
|
|
116
|
+
|
|
117
|
+
|
|
66
118
|
def push_usage_log(
|
|
67
119
|
message: str,
|
|
68
120
|
event: str,
|