- Configuration complète Stripe pour les 3 environnements (DEV/REC/PROD) * DEV: Clés TEST Pierre (mode test) * REC: Clés TEST Client (mode test) * PROD: Clés LIVE Client (mode live) - Ajout de la gestion des bases de données immeubles/bâtiments * Configuration buildings_database pour DEV/REC/PROD * Service BuildingService pour enrichissement des adresses - Optimisations pages et améliorations ergonomie - Mises à jour des dépendances Composer - Nettoyage des fichiers obsolètes 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
16 KiB
Système de logs d'événements JSONL
📋 Vue d'ensemble
Système de traçabilité des événements métier pour statistiques et audit, stocké en fichiers JSONL (JSON Lines) sans impact sur la base de données principale.
Créé le : 26 Octobre 2025 Rétention : 15 mois Format : JSONL (une ligne = un événement JSON)
🎯 Objectifs
Événements tracés
Authentification
- Connexions réussies (user_id, entity_id, plateforme, IP)
- Tentatives échouées (username, raison, IP, nb tentatives)
CRUD métier
- Passages : création, modification, suppression
- Secteurs : création, modification, suppression
- Membres : création, modification, suppression
- Entités : création, modification, suppression
Cas d'usage
1. Admin entité
- Stats de son entité : connexions, passages, secteurs sur 1 jour/semaine/mois
- Activité des membres de l'entité
2. Super-admin
- Stats globales : tous les passages modifiés sur 2 semaines
- Événements toutes entités sur période donnée
- Détection d'anomalies
📁 Architecture de stockage
Structure des répertoires
/logs/events/
├── 2025-10-26.jsonl # Fichier du jour (écriture append)
├── 2025-10-25.jsonl
├── 2025-10-24.jsonl
├── 2025-09-30.jsonl
├── 2025-09-29.jsonl.gz # Compression auto après 30 jours
└── archive/
├── 2025-09.jsonl.gz # Archive mensuelle
├── 2025-08.jsonl.gz
└── 2024-07.jsonl.gz # Supprimé auto après 15 mois
Cycle de vie des fichiers
| Âge | État | Taille estimée | Accès |
|---|---|---|---|
| 0-30 jours | .jsonl non compressé |
1-10 MB/jour | Lecture directe rapide |
| 30 jours-15 mois | .jsonl.gz compressé |
~100 KB/jour | Décompression à la volée |
| > 15 mois | Supprimé automatiquement | - | - |
Rotation et rétention
CRON mensuel : scripts/cron/rotate_event_logs.php
- Fréquence : 1er du mois à 3h00
- Actions :
- Compresser les fichiers
.jsonlde plus de 30 jours en.jsonl.gz - Supprimer les fichiers
.jsonl.gzde plus de 15 mois - Logger le résumé de rotation
- Compresser les fichiers
Commande manuelle :
php scripts/cron/rotate_event_logs.php
📊 Format des événements
Structure commune
Tous les événements partagent ces champs :
{
"timestamp": "2025-10-26T14:32:15Z", // ISO 8601 UTC
"event": "nom_evenement", // Type d'événement
"user_id": 123, // ID utilisateur (si authentifié)
"entity_id": 5, // ID entité (si applicable)
"ip": "192.168.1.100", // IP client
"platform": "ios|android|web", // Plateforme
"app_version": "3.3.6" // Version app (mobile uniquement)
}
Événements d'authentification
Login réussi
{"timestamp":"2025-10-26T14:32:15Z","event":"login_success","user_id":123,"entity_id":5,"platform":"ios","app_version":"3.3.6","ip":"192.168.1.100","username":"user123"}
Login échoué
{"timestamp":"2025-10-26T14:35:22Z","event":"login_failed","username":"test","reason":"invalid_password","ip":"192.168.1.101","attempt":3,"platform":"web"}
Raisons possibles : invalid_password, user_not_found, account_inactive, blocked_ip
Logout
{"timestamp":"2025-10-26T16:45:00Z","event":"logout","user_id":123,"entity_id":5,"platform":"android","session_duration":7800}
Événements Passages
Création
{"timestamp":"2025-10-26T14:40:10Z","event":"passage_created","passage_id":45678,"user_id":123,"entity_id":5,"operation_id":789,"sector_id":12,"amount":50.00,"payment_type":"cash","platform":"android"}
Modification
{"timestamp":"2025-10-26T14:42:05Z","event":"passage_updated","passage_id":45678,"user_id":123,"entity_id":5,"changes":{"amount":{"old":50.00,"new":75.00},"payment_type":{"old":"cash","new":"stripe"}},"platform":"ios"}
Suppression
{"timestamp":"2025-10-26T14:45:30Z","event":"passage_deleted","passage_id":45678,"user_id":123,"entity_id":5,"operation_id":789,"deleted_by":123,"soft_delete":true,"platform":"web"}
Événements Secteurs
Création
{"timestamp":"2025-10-26T15:10:00Z","event":"sector_created","sector_id":456,"operation_id":789,"entity_id":5,"user_id":123,"sector_name":"Secteur A","platform":"web"}
Modification
{"timestamp":"2025-10-26T15:12:00Z","event":"sector_updated","sector_id":456,"operation_id":789,"entity_id":5,"user_id":123,"changes":{"sector_name":{"old":"Secteur A","new":"Secteur Alpha"}},"platform":"web"}
Suppression
{"timestamp":"2025-10-26T15:15:00Z","event":"sector_deleted","sector_id":456,"operation_id":789,"entity_id":5,"user_id":123,"deleted_by":123,"soft_delete":true,"platform":"web"}
Événements Membres (Users)
Création
{"timestamp":"2025-10-26T15:20:00Z","event":"user_created","new_user_id":789,"entity_id":5,"created_by":123,"role_id":1,"username":"newuser","platform":"web"}
Modification
{"timestamp":"2025-10-26T15:25:00Z","event":"user_updated","user_id":789,"entity_id":5,"updated_by":123,"changes":{"role_id":{"old":1,"new":2},"encrypted_phone":true},"platform":"web"}
Note : Les champs chiffrés sont indiqués par un booléen true sans exposer les valeurs
Suppression
{"timestamp":"2025-10-26T15:30:00Z","event":"user_deleted","user_id":789,"entity_id":5,"deleted_by":123,"soft_delete":true,"platform":"web"}
Événements Entités
Création
{"timestamp":"2025-10-26T15:35:00Z","event":"entity_created","entity_id":25,"created_by":1,"entity_type_id":1,"postal_code":"75001","platform":"web"}
Modification
{"timestamp":"2025-10-26T15:40:00Z","event":"entity_updated","entity_id":25,"user_id":123,"updated_by":123,"changes":{"encrypted_name":true,"encrypted_email":true,"chk_stripe":{"old":0,"new":1}},"platform":"web"}
Suppression (rare)
{"timestamp":"2025-10-26T15:45:00Z","event":"entity_deleted","entity_id":25,"deleted_by":1,"soft_delete":true,"reason":"duplicate","platform":"web"}
Événements Opérations
Création
{"timestamp":"2025-10-26T16:00:00Z","event":"operation_created","operation_id":999,"entity_id":5,"created_by":123,"date_start":"2025-11-01","date_end":"2025-11-30","platform":"web"}
Modification
{"timestamp":"2025-10-26T16:05:00Z","event":"operation_updated","operation_id":999,"entity_id":5,"updated_by":123,"changes":{"date_end":{"old":"2025-11-30","new":"2025-12-15"},"chk_active":{"old":0,"new":1}},"platform":"web"}
Suppression
{"timestamp":"2025-10-26T16:10:00Z","event":"operation_deleted","operation_id":999,"entity_id":5,"deleted_by":123,"soft_delete":true,"platform":"web"}
🛠️ Implémentation
Service EventLogService.php
Emplacement : src/Services/EventLogService.php
Méthodes publiques :
EventLogService::logLoginSuccess($userId, $entityId, $username)
EventLogService::logLoginFailed($username, $reason, $attempt)
EventLogService::logLogout($userId, $entityId, $sessionDuration)
EventLogService::logPassageCreated($passageId, $operationId, $sectorId, $amount, $paymentType)
EventLogService::logPassageUpdated($passageId, $changes)
EventLogService::logPassageDeleted($passageId, $operationId, $softDelete)
EventLogService::logSectorCreated($sectorId, $operationId, $sectorName)
EventLogService::logSectorUpdated($sectorId, $operationId, $changes)
EventLogService::logSectorDeleted($sectorId, $operationId, $softDelete)
EventLogService::logUserCreated($newUserId, $entityId, $roleId, $username)
EventLogService::logUserUpdated($userId, $changes)
EventLogService::logUserDeleted($userId, $softDelete)
EventLogService::logEntityCreated($entityId, $entityTypeId, $postalCode)
EventLogService::logEntityUpdated($entityId, $changes)
EventLogService::logEntityDeleted($entityId, $reason)
EventLogService::logOperationCreated($operationId, $dateStart, $dateEnd)
EventLogService::logOperationUpdated($operationId, $changes)
EventLogService::logOperationDeleted($operationId, $softDelete)
Enrichissement automatique :
timestamp: Généré automatiquement (UTC)user_id,entity_id: Récupérés depuisSessionip: Récupérée viaClientDetectorplatform: Détecté viaClientDetector(ios/android/web)app_version: Extrait du User-Agent pour mobile
Intégration dans les Controllers
Exemple dans PassageController :
public function createPassage(Request $request, Response $response): void {
// ... validation et création ...
$passageId = $db->lastInsertId();
// Log de l'événement
EventLogService::logPassageCreated(
$passageId,
$data['fk_operation'],
$data['fk_sector'],
$data['montant'],
$data['fk_type_reglement']
);
// ... suite du code ...
}
Scripts d'analyse
1. Stats entité
Fichier : scripts/stats/entity_stats.php
Usage :
# Stats entité 5 sur 7 derniers jours
php scripts/stats/entity_stats.php --entity-id=5 --days=7
# Stats entité 5 entre deux dates
php scripts/stats/entity_stats.php --entity-id=5 --from=2025-10-01 --to=2025-10-26
# Résultat JSON
{
"entity_id": 5,
"period": {"from": "2025-10-20", "to": "2025-10-26"},
"stats": {
"logins": {"success": 45, "failed": 2},
"passages": {"created": 120, "updated": 15, "deleted": 3},
"sectors": {"created": 2, "updated": 8, "deleted": 0},
"users": {"created": 1, "updated": 5, "deleted": 0}
},
"top_users": [
{"user_id": 123, "actions": 85},
{"user_id": 456, "actions": 42}
]
}
2. Stats globales super-admin
Fichier : scripts/stats/global_stats.php
Usage :
# Tous les passages modifiés sur 2 semaines
php scripts/stats/global_stats.php --event=passage_updated --days=14
# Toutes les connexions échouées du mois
php scripts/stats/global_stats.php --event=login_failed --month=2025-10
# Résultat JSON
{
"event": "passage_updated",
"period": {"from": "2025-10-13", "to": "2025-10-26"},
"total_events": 342,
"by_entity": [
{"entity_id": 5, "count": 120},
{"entity_id": 12, "count": 85},
{"entity_id": 18, "count": 67}
],
"by_day": {
"2025-10-26": 45,
"2025-10-25": 38,
"2025-10-24": 52
}
}
3. Export CSV pour analyse externe
Fichier : scripts/stats/export_events_csv.php
Usage :
# Exporter toutes les connexions du mois en CSV
php scripts/stats/export_events_csv.php \
--event=login_success \
--month=2025-10 \
--output=/tmp/logins_october.csv
CRON de rotation
Fichier : scripts/cron/rotate_event_logs.php
Configuration crontab :
# Rotation des logs d'événements - 1er du mois à 3h
0 3 1 * * cd /var/www/geosector/api && php scripts/cron/rotate_event_logs.php
Actions :
- Compresser fichiers > 30 jours :
gzip logs/events/2025-09-*.jsonl - Supprimer archives > 15 mois :
rm logs/events/*-2024-06-*.jsonl.gz - Logger résumé dans
logs/rotation.log
📈 Performances et volumétrie
Estimations
Volume quotidien moyen (pour 50 entités actives) :
- 500 connexions/jour = 500 lignes
- 2000 passages créés/modifiés = 2000 lignes
- 100 autres événements = 100 lignes
- Total : ~2600 événements/jour
Taille fichier :
- 1 événement ≈ 200-400 bytes JSON
- 2600 événements ≈ 0.8-1 MB/jour non compressé
- Compression gzip : ratio ~10:1 → ~100 KB/jour compressé
Rétention 15 mois :
- Non compressé (30 jours) : 30 MB
- Compressé (14.5 mois) : 45 MB
- Total stockage : ~75 MB pour 15 mois
Optimisation lecture
Lecture mono-fichier : < 50ms pour analyser 1 jour (2600 événements)
Lecture période 7 jours :
- 7 fichiers × 1 MB = 7 MB à lire
- Filtrage
jqou PHP : ~200-300ms
Lecture période 2 semaines (super-admin) :
- 14 fichiers × 1 MB = 14 MB à lire
- Filtrage sur type événement : ~500ms
Lecture archive compressée :
- Décompression à la volée : +100-200ms
- Total : ~700-800ms pour 1 mois compressé
🔒 Sécurité et confidentialité
Données sensibles
❌ Jamais loggé en clair :
- Mots de passe
- Contenu chiffré (noms, emails, téléphones, IBAN)
- Tokens d'authentification
✅ Loggé :
- IDs (user_id, entity_id, passage_id, etc.)
- Montants financiers
- Dates et timestamps
- Types de modifications (indicateur booléen pour champs chiffrés)
Exemple champ chiffré
{
"event": "user_updated",
"changes": {
"encrypted_name": true, // Indique modification sans valeur
"encrypted_email": true,
"role_id": {"old": 1, "new": 2} // Champ non sensible = valeurs OK
}
}
Permissions d'accès
Fichiers logs :
- Propriétaire :
nginx:nginx - Permissions :
0640(lecture nginx, écriture nginx, aucun autre) - Dossier
/logs/events/:0750
Scripts d'analyse :
- Exécution : root ou nginx uniquement
- Pas d'accès direct via endpoints API (pour l'instant)
🚀 Roadmap et évolutions futures
Phase 1 - MVP (actuel) ✅
- Architecture JSONL quotidienne
- Service EventLogService.php
- Intégration dans controllers (LoginController, PassageController, UserController, SectorController, OperationController, EntiteController)
- CRON de rotation 15 mois
- Scripts d'analyse de base
Phase 2 - Dashboards (Q1 2026)
- Endpoints API :
GET /api/stats/entity/{id},GET /api/stats/global - Interface web admin : graphiques connexions, passages
- Filtres avancés (période, plateforme, utilisateur)
Phase 3 - Alertes (Q2 2026)
- Détection anomalies (pics de connexions échouées)
- Alertes email super-admins
- Seuils configurables par entité
Phase 4 - Migration TimescaleDB (si besoin)
- Évaluation volume : si > 50k événements/jour
- Import JSONL → TimescaleDB
- Rétention hybride : 90j TimescaleDB, archives JSONL
📝 Statut implémentation
Date : 28 Octobre 2025
✅ Terminé
- Service
EventLogService.phpcréé avec toutes les méthodes de logging - Intégration complète dans les 6 controllers principaux :
- LoginController : login réussi/échoué, logout
- PassageController : création, modification, suppression passages
- UserController : création, modification, suppression utilisateurs
- SectorController : création, modification, suppression secteurs
- OperationController : création, modification, suppression opérations
- EntiteController : création, modification entités
- Enrichissement automatique : timestamp UTC, user_id, entity_id, IP, platform, app_version
- Sécurité : champs sensibles loggés en booléen uniquement (pas de valeurs chiffrées)
- Script de déploiement
deploy-api.shcrée automatiquement/logs/events/avec permissions 0750
🔄 En attente
- Scripts d'analyse (
entity_stats.php,global_stats.php,export_events_csv.php) - CRON de rotation 15 mois (
rotate_event_logs.php) - Tests en environnement DEV
📝 Checklist déploiement
Environnement DEV (dva-geo)
- Créer dossier
/logs/events/(permissions 0750) - Intégré dans deploy-api.sh - Déployer
EventLogService.php - Déployer scripts stats et rotation
- Configurer CRON rotation
- Tests : générer événements manuellement
- Valider format JSONL et rotation
Environnement RECETTE (rca-geo)
- Déployer depuis DEV validé
- Tests de charge : 10k événements/jour
- Valider performances scripts d'analyse
- Valider compression et suppression auto
Environnement PRODUCTION (pra-geo)
- Déployer depuis RECETTE validée
- Monitoring volumétrie
- Backups quotidiens
/logs/events/(via CRON général)
Dernière mise à jour : 28 Octobre 2025 Version : 1.1 Statut : ✅ Service implémenté et intégré - Scripts d'analyse à développer