Descripción
Helper genérico para registrar entradas de auditoría (SystemLog) desde cualquier endpoint o servicio, con garantía de nunca romper la request (best-effort).
Ubicación: core.utils.audit.log_action()
Disponible en: from core.utils import log_action
Requisitos previos: SystemLog (modelo) ya debe existir.
Firma
def log_action(
request,
category: str,
action: str,
details: str = "",
level: str = "INFO"
) -> None:
Parámetros
| Parámetro | Tipo | Descripción | Ejemplo |
|---|---|---|---|
request | HttpRequest | Contexto HTTP para resolver user + org. Obligatorio. | Django request object |
category | str | Categoría de auditoría (constante en SystemLog.CATEGORY_CHOICES). | "ITSM", "OBSERVATORY", "AUTH" |
action | str | Identificador corto machine-friendly de la acción. | "channel.create", "alert.delete", "sla.update" |
details | str | Payload libre (JSON, texto plano o URL-encoded). Opcional, por defecto "". | f"id={c.id} name={c.name}", json.dumps({"old": old_val, "new": new_val}) |
level | str | Severidad del log ("INFO", "WARN", "ERROR"). Por defecto "INFO". | "WARN" para acciones arriesgadas |
Retorno
Nonesiempre. La función nunca lanza excepciones — todas capturadas y logeadas internamente.
Comportamiento
Resolución de contexto
- Usuario:
request.usersi autenticado;Nonesi anónimo o token sin identidad - Organización: Resuelto via
get_current_org(request)(respeta RLS) - IP/User-Agent: No se capturan en
SystemLog(se podrían añadir víarequest.METAsi se requiere)
Best-effort (tolerancia a fallos)
try:
# 1. Import SystemLog + get_current_org
# 2. Resolve user & org
# 3. Create entry
SystemLog.objects.create(...)
except Exception:
logger.exception("log_action failed (category=%s action=%s)", category, action)
# NCA: nunca re-raise, request continúa
Implicación: Si la BD está lenta, la migración falló, o hay constraint violation, el endpoint sigue adelante. El fallo se registra en logging.getLogger("core") para debugging posterior.
Casos especiales
- Usuario anónimo (request.user.is_anonymous): Se registra como
user=None - JWT agent / Token sin identidad: Se registra como
user=None - Org no resuelta: Si
get_current_org()falla, la excepción se captura; entry se intenta crear conorganization=None(puede fallar por constraint si no null) - Request sin user attribute: Se asume anónimo
Uso típico
En un endpoint de mutación
from django.shortcuts import get_object_or_404
from ninja import Router
from core.utils import log_action, require_perm
router = Router()
@router.delete("/channels/{channel_id}")
def delete_channel(request, channel_id: int):
require_perm(request, "itsm", "edit")
org = require_org(request)
c = get_object_or_404(NotificationChannel, id=channel_id, organization=org)
# Audit ANTES de borrar (por si quieres capturar datos del objeto)
log_action(
request,
category="ITSM",
action="channel.delete",
details=f"id={channel_id} name={c.name}"
)
c.delete()
return {"deleted": True}
Con JSON detallado
import json
@router.post("/alerts/{alert_id}/threshold")
def update_threshold(request, alert_id: int, data: ThresholdIn):
old_val = alert.threshold_value
alert.threshold_value = data.threshold_value
alert.save()
log_action(
request,
category="OBSERVATORY",
action="alert.threshold",
details=json.dumps({
"alert_id": alert_id,
"old_value": old_val,
"new_value": data.threshold_value
})
)
return {"ok": True}
Campos generados automáticamente
Cada entrada SystemLog incluye:
| Campo | Fuente | Ejemplo |
|---|---|---|
timestamp | timezone.now() | 2026-06-07 10:55:52+00:00 |
user | request.user si autenticado | User(id=42, username="operator") |
organization | get_current_org(request) | Organization(id=5, slug="acme") |
level | Parámetro (default "INFO") | "INFO" |
category | Parámetro | "ITSM" |
action | Parámetro | "channel.create" |
details | Parámetro | "id=123 name=Webhook" |
Integración con RLS
get_current_org(request)respeta las validaciones de RLS de la app (no crear entradas para orgs no autorizadas)- Si RLS falla, la excepción es capturada →
organization=None - Implicación: Un operador no puede auditar mutaciones ajenas (RLS preventivo)
Índices recomendados
Para queries rápidas de auditoría:
CREATE INDEX idx_systemlog_org_category_action_ts ON core_systemlog(
organization_id,
category,
action,
timestamp DESC
);
CREATE INDEX idx_systemlog_org_user_ts ON core_systemlog(
organization_id,
user_id,
timestamp DESC
);
Logging de fallos
Cualquier fallo en log_action() se emite a logging.getLogger("core") con nivel ERROR:
logger.exception("log_action failed (category=%s action=%s)", category, action)
Monitoreo recomendado: Alertar si tasa de “log_action failed” > 0.1% de requests de mutación.
Performance
- Escritura: 1 INSERT/exec + resuelución de org (1 query si caché, fallback a BD)
- No bloqueante: La función es síncrona pero rápida; si es cuello de botella, considerar celery task
- TTL: Logs se guardan indefinidamente (considerar archivado anual)
Versión
- Introducido: 2026-06-07 (PR #71, commit 83866d7)
- Estado: Estable
Véase también
- [[feature—monitoring—audit-logs-mutaciones-sa4-sa5]] — Feature general que usa este helper
- [[entity—core—model—systemlog]] — Modelo de datos que almacena las entradas
- [[decision—20260607—audit-logs-best-effort]] — ADR sobre la estrategia best-effort