The Custom Event Logging system is designed to bolster observability, accountability, and security within your application. It offers a structured approach to log diverse user activities and system events. By leveraging this logging mechanism, you can systematically track and monitor user interactions, system operations, and security-related events in the database.
This system comprises models like EventLog and UserLog, alongside essential helper functions such as log_user. These components collectively enable the creation of detailed logs tailored to different system functionalities. The approach utilizes Django's robust framework to categorize and store logs efficiently, enhancing the application's visibility and traceability.
from datetime import datetime
from django.db import models
from django.contrib.contenttypes.models import ContentType
class EventLog(models.Model):
"""
This model is used to create log categories of the events that are happening in the system.
"""
name = models.CharField(max_length=255)
content_type = models.ForeignKey(
ContentType,
models.CASCADE,
verbose_name="content type",
)
codename = models.CharField(max_length=100)
class Meta:
verbose_name = "Event Log"
verbose_name_plural = "Event Logs"
def __str__(self):
return self.codename
@classmethod
def get_or_create_event_log(cls, name, content_type, codename):
"""
If there is no event log with the given name, content type and codename, it will create one.
"""
fetched_content_type = cls.get_content_type(content_type)
event_log = EventLog.objects.filter(
name=name, content_type=fetched_content_type, codename=codename
).first()
if event_log is None:
event_log = EventLog.objects.create(
name=name, content_type=fetched_content_type, codename=codename
)
return event_log
@classmethod
def get_content_type(cls, content_type):
"""
Get content type from a given string by using content_type map for models in the system.
"""
importer = DynamicImporter()
content_type_map = {
"user": importer.import_class("apps.management.models", "User"),
"notification": importer.import_class(
"apps.management.models", "Notification"
),
"permission": importer.import_class(
"django.contrib.auth.models", "Permission"
),
"event_log": importer.import_class("apps.management.models", "EventLog"),
"user_log": importer.import_class("apps.management.models", "UserLog"),
# your other models (entities)
}
if content_type not in content_type_map:
logger.error(
f"Content type {content_type} is not found in content type map!"
)
raise AttributeError(
f"Content type {content_type} is not found in content type map!"
)
return ContentType.objects.get_for_model(content_type_map[content_type])
class UserLog(models.Model):
"""
This model is used to create logs for the events that are happening in the system.
"""
created_at = models.DateTimeField(auto_now_add=True)
user = models.ForeignKey(
User, on_delete=models.SET_NULL, related_name="user_logs", null=True, blank=True
)
event_log = models.ForeignKey(
EventLog,
on_delete=models.SET_NULL,
related_name="user_logs",
null=True,
blank=True,
)
message = models.TextField(null=True, blank=True)
class Meta:
verbose_name = "User Log"
verbose_name_plural = "User Logs"
def __str__(self):
return self.message
@classmethod
def create_user_log(cls, user, message, content_type, event_log_codename):
"""
If there is no event log with the given name, content type and codename, it will create one.
"""
configured_message = cls.configure_message(message)
event_log = EventLog.get_or_create_event_log(
name=event_log_codename,
content_type=content_type,
codename=event_log_codename,
)
user_log = UserLog.objects.create(
user=user, message=configured_message, event_log=event_log
)
return user_log
@classmethod
def configure_message(cls, message, **kwargs):
"""
This method is used to add datetime and user information to the message.
"""
now_formatted = datetime.strftime(datetime.now(), "%Y-%m-%d %H:%M:%S")
message = f"{now_formatted} - {message}"
return message
log_user
helps us to create logs in DB.
from apps.management.models import UserLog
def log_user(user, message, content_type, event_log_codename):
"""This function will create a new UserLog object. A proxy function for UserLog.create_user_log()
If EventLog object does not exist, it will be created.
Arguments:
user (User): User object
message (str): Message to be logged
content_type (str): ContentType string. It can be found in apps.management.models.EventLog.get_content_type
event_log_codename (str): Codename of EventLog object. It can be anything, if it does not exist, it will be created.
Example:
log_user(user, "Page X permission given to User", 'management', "permission_updated")
"""
UserLog.create_user_log(user, message, content_type, event_log_codename)
DynamicImporter
helps us to import classes from modules by using strings. I also used this approach in my ajax_method_caller method.
import importlib
class DynamicImporter:
def import_module(self, module_name):
return importlib.import_module(module_name)
def import_class(self, module_name, class_name):
module = self.import_module(module_name)
return getattr(module, class_name)
# Usage
importer = DynamicImporter()
user_class = importer.import_class("apps.management.models", "User")