# 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
