loko/loko/analytics/middleware.py

196 lines
7.3 KiB
Python

"""
Middleware pour le tracking des visites de pages et des sessions utilisateurs.
Ce middleware enregistre automatiquement les visites de pages et met à jour
les informations de session pour le monitoring de l'application.
"""
import time
import logging
from django.utils import timezone
from django.contrib.auth.signals import user_logged_in, user_logged_out
# Import tardif pour éviter les dépendances circulaires
# Ces imports sont effectués au niveau du module mais après la configuration Django
from analytics.models import PageView, UserSession
class AnalyticsMiddleware:
"""
Middleware qui capture les visites de pages pour le monitoring.
Pour chaque requête, enregistre:
- La page visitée
- L'utilisateur (si authentifié)
- La session
- Le timestamp
- Les métadonnées (IP, user agent, etc.)
"""
# URLs à ignorer pour ne pas polluer les statistiques
IGNORED_PATHS = [
'/static/',
'/media/',
'/private-media/',
'/favicon.ico',
'/__debug__/',
'/admin/jsi18n/', # Traductions JS de l'admin
]
# Méthodes HTTP à tracker (ignorer les HEAD, OPTIONS, etc.)
TRACKED_METHODS = ['GET', 'POST']
def __init__(self, get_response):
self.get_response = get_response
# Connecter les signaux pour tracker les connexions/déconnexions
user_logged_in.connect(self._on_user_login)
user_logged_out.connect(self._on_user_logout)
def __call__(self, request):
# Vérifier si cette requête doit être trackée
if not self._should_track(request):
return self.get_response(request)
# Enregistrer le temps de début
start_time = time.time()
# Traiter la requête
response = self.get_response(request)
# Calculer le temps de réponse
response_time_ms = int((time.time() - start_time) * 1000)
# Enregistrer la visite de page (de manière asynchrone pour ne pas ralentir la réponse)
try:
self._record_page_view(request, response_time_ms)
except Exception as e:
# En cas d'erreur, ne pas faire planter la requête
logger = logging.getLogger('analytics')
logger.error(f"Erreur lors de l'enregistrement de la page view: {e}")
return response
def _should_track(self, request):
"""Détermine si la requête doit être trackée."""
# Ignorer certains chemins
for path in self.IGNORED_PATHS:
if request.path.startswith(path):
return False
# Ignorer certaines méthodes HTTP
if request.method not in self.TRACKED_METHODS:
return False
# Ignorer les requêtes AJAX de type polling/refresh
if request.META.get('HTTP_X_REQUESTED_WITH') == 'XMLHttpRequest':
# On peut choisir de tracker ou non les requêtes AJAX
# Pour l'instant, on les ignore pour ne pas polluer les stats
return False
return True
def _get_client_ip(self, request):
"""Récupère l'adresse IP du client."""
x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
if x_forwarded_for:
ip = x_forwarded_for.split(',')[0].strip()
else:
ip = request.META.get('REMOTE_ADDR')
return ip
def _get_user_agent(self, request):
"""Récupère le user agent en le tronquant si nécessaire."""
user_agent = request.META.get('HTTP_USER_AGENT')
return user_agent[:2000] if user_agent else None
def _get_referer(self, request):
"""Récupère le referer en le tronquant si nécessaire."""
referer = request.META.get('HTTP_REFERER')
return referer[:2048] if referer else None
def _record_page_view(self, request, response_time_ms):
"""Enregistre la visite de page dans la base de données."""
# Créer l'enregistrement de page view
page_view = PageView.objects.create(
user=request.user if request.user.is_authenticated else None,
session_key=request.session.session_key or '',
path=request.path,
full_url=request.build_absolute_uri(),
method=request.method,
ip_address=self._get_client_ip(request),
user_agent=self._get_user_agent(request),
referer=self._get_referer(request),
response_time_ms=response_time_ms,
)
# Mettre à jour la session utilisateur si l'utilisateur est authentifié
if request.user.is_authenticated and request.session.session_key:
self._update_user_session(request)
def _update_user_session(self, request):
"""Met à jour les informations de session utilisateur."""
session_key = request.session.session_key
if not session_key:
return
try:
# Récupérer ou créer la session
user_session, created = UserSession.objects.get_or_create(
session_key=session_key,
defaults={
'user': request.user,
'login_time': timezone.now(),
'last_activity': timezone.now(),
'ip_address': self._get_client_ip(request),
'user_agent': self._get_user_agent(request),
}
)
# Mettre à jour l'activité et le compteur de pages vues
if not created:
user_session.last_activity = timezone.now()
user_session.page_views_count += 1
user_session.save(update_fields=['last_activity', 'page_views_count'])
except Exception as e:
# En cas d'erreur (par exemple, clé de session dupliquée), ignorer
logger = logging.getLogger('analytics')
logger.warning(f"Erreur lors de la mise à jour de la session: {e}")
def _on_user_login(self, sender, request, user, **kwargs):
"""
Signal handler appelé quand un utilisateur se connecte.
Crée une nouvelle session utilisateur.
"""
# S'assurer que la session a une clé
if not request.session.session_key:
request.session.create()
session_key = request.session.session_key
# Créer ou mettre à jour la session
UserSession.objects.update_or_create(
session_key=session_key,
defaults={
'user': user,
'login_time': timezone.now(),
'last_activity': timezone.now(),
'ip_address': self._get_client_ip(request),
'user_agent': self._get_user_agent(request),
'is_active': True,
}
)
def _on_user_logout(self, sender, request, user, **kwargs):
"""
Signal handler appelé quand un utilisateur se déconnecte.
Marque la session comme inactive.
"""
if request.session.session_key:
try:
user_session = UserSession.objects.get(
session_key=request.session.session_key
)
user_session.mark_inactive()
except UserSession.DoesNotExist:
pass