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

16 KiB
Raw Permalink Blame History

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 :

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 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 :

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 :

  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é

{
  "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.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)

  • 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