import datetime as dt
import json
import logging
from decimal import Decimal
from uuid import UUID
from django.conf import settings
from django.db import models
from django.utils.translation import gettext_lazy as _
from logmancer.levels import LogLevel
logger = logging.getLogger("logmancer.models")
[docs]
class SafeJSONField(models.JSONField):
"""JSONField that automatically sanitizes data"""
[docs]
def get_prep_value(self, value):
"""Prepare value for database - sanitize before saving"""
if value is None:
return value
value = self.make_json_safe(value)
return super().get_prep_value(value)
[docs]
@staticmethod
def make_json_safe(data):
"""Convert data to JSON-serializable format"""
def default_serializer(obj):
if isinstance(obj, (dt.datetime, dt.date)):
return obj.isoformat()
elif isinstance(obj, Decimal):
return float(obj)
elif isinstance(obj, UUID):
return str(obj)
elif hasattr(obj, "__dict__"):
return str(obj)
return str(obj)
try:
json_str = json.dumps(data, default=default_serializer)
return json.loads(json_str)
except Exception as e:
logger.error(f"make_json_safe error: {e}")
return {}
[docs]
class LogEntry(models.Model):
timestamp = models.DateTimeField(auto_now_add=True)
level = models.CharField(
max_length=10, choices=LogLevel.get_choices(), default=LogLevel.INFO.name, db_index=True
)
message = models.TextField(blank=True, null=True)
path = models.CharField(max_length=500, blank=True, null=True)
method = models.CharField(max_length=10, blank=True, null=True)
status_code = models.PositiveSmallIntegerField(blank=True, null=True)
user = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name="log_entries",
)
source = models.CharField(
max_length=50,
blank=True,
null=True,
help_text="Source of the log: 'middleware', 'signal', etc.",
)
actor_type = models.CharField(
max_length=20,
choices=[("user", _("User")), ("system", _("System"))],
default="user",
help_text="Type of source triggering the event",
)
meta = SafeJSONField(
blank=True,
null=True,
help_text="Additional metadata for the log entry in JSON format",
)
class Meta:
ordering = ["-timestamp"]
verbose_name = _("Log Entry")
verbose_name_plural = _("Log Entries")
[docs]
def get_level_info(self):
"""Get full level information"""
return LogLevel.from_name(self.level).value
[docs]
def get_emoji(self):
"""Get emoji for this log level"""
return self.get_level_info().emoji
[docs]
def get_color(self):
"""Get color for this log level"""
return self.get_level_info().color
[docs]
def __str__(self):
return f"[{self.timestamp:%Y-%m-%d %H:%M:%S}] {self.level}"