Files
geo/api/docs/EVENTS-LOG.md
pierre 2f5946a184 feat: Version 3.5.2 - Configuration Stripe et gestion des immeubles
- 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>
2025-11-09 18:26:27 +01:00

496 lines
16 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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