# 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** : 1. Compresser les fichiers `.jsonl` de plus de 30 jours en `.jsonl.gz` 2. Supprimer les fichiers `.jsonl.gz` de plus de 15 mois 3. Logger le résumé de rotation **Commande manuelle** : ```bash php scripts/cron/rotate_event_logs.php ``` ## 📊 Format des événements ### Structure commune Tous les événements partagent ces champs : ```json { "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 ```jsonl {"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é ```jsonl {"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 ```jsonl {"timestamp":"2025-10-26T16:45:00Z","event":"logout","user_id":123,"entity_id":5,"platform":"android","session_duration":7800} ``` ### Événements Passages #### Création ```jsonl {"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 ```jsonl {"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 ```jsonl {"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 ```jsonl {"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 ```jsonl {"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 ```jsonl {"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 ```jsonl {"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 ```jsonl {"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 ```jsonl {"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 ```jsonl {"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 ```jsonl {"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) ```jsonl {"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 ```jsonl {"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 ```jsonl {"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 ```jsonl {"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** : ```php 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 depuis `Session` - `ip` : Récupérée via `ClientDetector` - `platform` : Détecté via `ClientDetector` (ios/android/web) - `app_version` : Extrait du User-Agent pour mobile ### Intégration dans les Controllers **Exemple dans PassageController** : ```php 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** : ```bash # 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** : ```bash # 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** : ```bash # 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** : ```cron # 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** : 1. Compresser fichiers > 30 jours : `gzip logs/events/2025-09-*.jsonl` 2. Supprimer archives > 15 mois : `rm logs/events/*-2024-06-*.jsonl.gz` 3. 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 `jq` ou 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é ```json { "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) ✅ - [x] Architecture JSONL quotidienne - [x] Service EventLogService.php - [x] 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.php` créé 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.sh` cré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) - [x] Créer dossier `/logs/events/` (permissions 0750) - Intégré dans deploy-api.sh - [x] 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