-
Notifications
You must be signed in to change notification settings - Fork 438
Description
Hello,
Thank you for providing this useful module.
Currently, we can use additional_data to store extra information when creating a log. However, sometimes it is necessary to filter logs based on this extra information. Since additional_data is stored as JSON, if the number of log records grows significantly, querying on this field becomes expensive and slows down database performance.
For example, our users belong to multiple groups that represent their roles. We need to store a user’s role at the time of logging so that logs can later be filtered by role.
Proposal:
Introduce support for replacing the default LogEntry model with a custom one. For instance, add a configuration option like AUDITLOG_CUSTOM_LOG_ENTRY_MODEL. If this setting is defined, the system should use the custom model for storing logs.
Modification in conf.py to add AUDITLOG_CUSTOM_LOG_ENTRY_MODEL configuration
settings.AUDITLOG_CUSTOM_LOG_ENTRY_MODEL = getattr(
settings, "AUDITLOG_CUSTOM_LOG_ENTRY_MODEL", None
)
Additionally, modifications in middleware.py can allow passing extra information during a request, for example:
def __call__(self, request):
user = self._get_actor(request)
set_cid(request)
context_data = {
"role": request.user.get_active_role(),
"remote_addr": self._get_remote_addr(request),
"remote_port": self._get_remote_port(request),
}
with set_extra_data(actor=user, context_data=context_data):
return self.get_response(request)
Also, changes in context.py would allow persisting additional information:
@contextlib.contextmanager
def set_extra_data(actor, context_data={}):
LogEntry = get_log_entry_model()
# Initialize thread local storage
context_data["signal_duid"] = ("set_extra_data", time.time())
auditlog_value.set(context_data)
# Connect signal for automatic logging
set_actor = partial(
_set_extra_data,
user=actor,
signal_duid=context_data["signal_duid"],
)
...
def _set_actor(user, sender, instance):
LogEntry = get_log_entry_model()
auth_user_model = get_user_model()
if (
sender == LogEntry
and isinstance(user, auth_user_model)
and instance.actor is None
):
instance.actor = user
instance.actor_email = getattr(user, "email", None)
def _set_extra_data(user, sender, instance, signal_duid, **kwargs):
try:
auditlog = auditlog_value.get()
except LookupError:
return
if signal_duid != auditlog["signal_duid"]:
return
_set_actor(user, sender, instance)
for key in auditlog:
if hasattr(instance, key):
setattr(instance, key, auditlog[key])
Define Custom LogEntry model in the main project models
class CustomLogEntry(LogEntry):
role = models.ForeignKey(Group, blank=True, null=True, on_delete=models.CASCADE, related_name="logs")
objects = LogEntryManager()
Set AUDITLOG_CUSTOM_LOG_ENTRY_MODEL in main project settings
AUDITLOG_CUSTOM_LOG_ENTRY_MODEL = "app.CustomLogEntry"