import json import logging from django.utils import timezone from django.core.serializers.json import DjangoJSONEncoder from . import utils logger = logging.getLogger(__name__) def _json_safe(value): """ Convierte cualquier valor a un tipo seguro para JSONField. Usa DjangoJSONEncoder para manejar date, datetime, Decimal, UUID, etc. """ if value is None or value == '': return value try: return json.loads(json.dumps(value, cls=DjangoJSONEncoder)) except Exception: return str(value) class LogService: """ Servicio centralizado de gestión de logs. Única fuente de inserción en la tabla audit_logs. Uso en vistas: log_id = LogService.gestionar_log(self, request, path='/mi/path/') LogService.gestionar_log(self, request, log_id=log_id, body_request=data, status_code=200) Si la BD no está disponible, el log falla silenciosamente para no interrumpir la petición del usuario. """ def gestionar_log( self, request, log_id=None, path=None, user=None, body_request=None, body_response=None, status_code=None, ): try: return LogService._ejecutar_log( self, request, log_id, path, user, body_request, body_response, status_code ) except Exception as e: logger.warning('LogService.gestionar_log falló (log omitido): %s', str(e)) return log_id # Devuelve el log_id existente o None si era creación @staticmethod def _ejecutar_log(caller, request, log_id, path, user, body_request, body_response, status_code): # Importación diferida para evitar problemas de arranque si la BD no está lista from backend_admin.models import Log # Determinar la app llamante a partir del módulo de la vista modulo = caller.__class__.__module__ app_nombre = modulo.split('.')[0] tag_header = request.headers.get('tag') if tag_header: usuario_final = tag_header elif app_nombre: apps_automatizadas = ['automatizados'] if app_nombre.lower() in apps_automatizadas: usuario_final = 'Jenkins' else: usuario_final = app_nombre else: usuario_final = user if user else 'orquestador' if log_id is None: # --- CREACIÓN: primer registro del ciclo de vida de la petición --- path_final = path if path else request.path nuevo_log = Log.objects.create( user_id=0, user=usuario_final, app_id=0, remote_address=utils.get_client_ip(request), request='', response='', status_code=status_code if status_code else '0', path=path_final, method=request.method, createdAt=timezone.now(), ) return nuevo_log.pk else: # --- ACTUALIZACIÓN: enriquece el log con datos del procesamiento --- datos_a_actualizar = { 'updatedAt': timezone.now(), } if user: datos_a_actualizar['user'] = user if path: datos_a_actualizar['path'] = path if body_request is not None: # _json_safe convierte dates, Decimals, etc. a tipos JSON válidos datos_a_actualizar['request'] = _json_safe(body_request) if body_response is not None: datos_a_actualizar['response'] = _json_safe(body_response) if status_code is not None: datos_a_actualizar['status_code'] = str(status_code) Log.objects.filter(pk=log_id).update(**datos_a_actualizar) return log_id