veza/docs/archive/root-md/LOGGING_REFONTE_PLAN.md

660 lines
19 KiB
Markdown
Raw Normal View History

# 🏗️ PLAN DE REFONTE DU SYSTÈME DE LOGS VEZA
**Date**: 2025-01-27
**Version**: 1.0
**Statut**: 📋 Plan d'Action
---
## 📋 TABLE DES MATIÈRES
1. [Architecture Cible](#1-architecture-cible)
2. [Plan d'Implémentation](#2-plan-dimplémentation)
3. [Standards de Logging](#3-standards-de-logging)
4. [Configuration Unifiée](#4-configuration-unifiée)
5. [Corrélation Distribuée](#5-corrélation-distribuée)
6. [Checklist d'Implémentation](#6-checklist-dimplémentation)
---
## 1. ARCHITECTURE CIBLE
### 1.1 Vue d'Ensemble
```
┌─────────────────────────────────────────────────────────────────┐
│ SYSTÈME DE LOGS UNIFIÉ │
└─────────────────────────────────────────────────────────────────┘
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ Backend Go │ │ Chat Server │ │Stream Server │
│ (zap) │ │ (tracing) │ │ (tracing) │
└──────┬────────┘ └──────┬──────┘ └──────┬──────┘
│ │ │
│ Request ID │ Request ID │ Request ID
│ Trace ID │ Trace ID │ Trace ID
│ │ │
└──────────────────┴─────────────────┘
┌──────────────────────┐
│ Log Aggregator │
│ (Loki) │
└──────────┬───────────┘
┌──────────────────────┐
│ Grafana Dashboards │
│ (Visualisation) │
└──────────────────────┘
```
### 1.2 Composants
#### 1.2.1 Backend Go (veza-backend-api)
- **Logger** : `zap` (structuré, performant)
- **Format** : JSON en production, console en développement
- **Niveaux** : DEBUG, INFO, WARN, ERROR, FATAL
- **Corrélation** : Request ID, Trace ID, Span ID
- **Agrégation** : Loki (activé par défaut en prod)
#### 1.2.2 Services Rust (chat-server, stream-server)
- **Logger** : `tracing` (framework moderne)
- **Format** : JSON en production, texte en développement
- **Niveaux** : trace, debug, info, warn, error
- **Corrélation** : Request ID, Trace ID via spans
- **Agrégation** : Loki (via HTTP ou gRPC)
#### 1.2.3 Frontend React (apps/web)
- **Logger** : Logger structuré custom (JSON)
- **Format** : JSON (toujours)
- **Niveaux** : debug, info, warn, error
- **Corrélation** : Request ID (extrait des réponses API)
- **Agrégation** : Endpoint backend `/api/v1/logs` (optionnel)
### 1.3 Flux de Logs
```
1. Requête HTTP arrive au Backend Go
└─> Génère Request ID (UUID v4)
└─> Génère Trace ID (si OpenTelemetry activé)
└─> Log de début de requête
2. Backend Go appelle Chat Server
└─> Propage Request ID via header X-Request-ID
└─> Chat Server extrait Request ID et l'utilise dans les spans
3. Backend Go appelle Stream Server
└─> Propage Request ID via header X-Request-ID
└─> Stream Server extrait Request ID et l'utilise dans les spans
4. Frontend fait une requête API
└─> Backend répond avec X-Request-ID
└─> Frontend extrait Request ID et l'inclut dans tous les logs
5. Tous les logs sont envoyés vers Loki
└─> Labels: service, env, request_id, trace_id
└─> Recherche possible par Request ID pour tracer une requête complète
```
---
## 2. PLAN D'IMPLÉMENTATION
### Phase 1 : Corrections Critiques (Semaine 1-2)
#### 1.1 Corriger la Configuration du Logger Go
**Fichier** : `veza-backend-api/internal/config/config.go`
**Actions** :
- [ ] Lire `LOG_LEVEL` AVANT d'initialiser le logger
- [ ] Supprimer l'initialisation dupliquée dans `main.go`
- [ ] Utiliser `NewLoggerWithAggregation` directement si agrégation activée
- [ ] Garantir le flush au shutdown via `ShutdownManager`
**Code cible** :
```go
// Lire LOG_LEVEL en premier
logLevel := getEnv("LOG_LEVEL", "INFO")
// Initialiser le logger directement avec la bonne config
var logger *zap.Logger
if config.LogAggregationEnabled && config.LogAggregationEndpoint != "" {
aggLogger, err := logging.NewLoggerWithAggregation(env, logLevel, aggConfig)
if err != nil {
// Fallback vers logger standard
logger = createStandardLogger(env, logLevel)
} else {
logger = aggLogger.GetZapLogger()
}
} else {
logger = createStandardLogger(env, logLevel)
}
```
#### 1.2 Implémenter le Filtre de Secrets
**Fichier** : `veza-backend-api/internal/logging/secret_filter.go` (nouveau)
**Actions** :
- [ ] Créer un `zapcore.Core` qui filtre les secrets
- [ ] Liste de patterns à filtrer : `password`, `secret`, `token`, `key`, `credential`
- [ ] Remplacer les valeurs par `[REDACTED]`
- [ ] Intégrer dans la chaîne de cores zap
**Code cible** :
```go
type SecretFilterCore struct {
core zapcore.Core
}
func (f *SecretFilterCore) Write(entry zapcore.Entry, fields []zapcore.Field) error {
// Filtrer les champs sensibles
filteredFields := make([]zapcore.Field, 0, len(fields))
for _, field := range fields {
if isSecretField(field.Key) {
filteredFields = append(filteredFields, zap.String(field.Key, "[REDACTED]"))
} else {
filteredFields = append(filteredFields, field)
}
}
return f.core.Write(entry, filteredFields)
}
```
#### 1.3 Remplacer fmt.Println par Logs Structurés
**Fichiers** : 12 fichiers identifiés
**Actions** :
- [ ] Remplacer tous les `fmt.Print*` par `logger.Debug/Info/Warn/Error`
- [ ] Supprimer les logs de debug excessifs (`=== DEBUG ===`)
- [ ] Utiliser `logger.Debug()` pour les logs de debug (désactivés en prod)
**Exemple** :
```go
// AVANT
fmt.Printf(">>> ERROR STRING: %s\n", err.Error())
// APRÈS
logger.Error("Registration failed",
zap.Error(err),
zap.String("request_id", requestID),
)
```
#### 1.4 Propager Request ID vers Services Rust
**Fichiers** :
- `veza-backend-api/internal/services/chat_service.go`
- `veza-backend-api/internal/services/stream_service.go`
- `veza-chat-server/src/main.rs`
- `veza-stream-server/src/main.rs`
**Actions** :
- [ ] Extraire `request_id` du contexte Gin
- [ ] Ajouter header `X-Request-ID` dans les appels HTTP vers Rust
- [ ] Extraire `X-Request-ID` dans les services Rust
- [ ] Utiliser `request_id` dans les spans tracing
**Code Go** :
```go
// Dans chat_service.go
requestID := c.GetString("request_id")
req.Header.Set("X-Request-ID", requestID)
```
**Code Rust** :
```rust
// Dans main.rs ou middleware
let request_id = headers.get("x-request-id")
.and_then(|h| h.to_str().ok())
.map(|s| s.to_string());
if let Some(rid) = request_id {
let span = tracing::span!(tracing::Level::INFO, "request", request_id = %rid);
let _guard = span.enter();
// ... traitement
}
```
### Phase 2 : Standardisation (Semaine 3-4)
#### 2.1 Unifier la Configuration Rust
**Fichiers** :
- `veza-common/src/logging.rs` (améliorer)
- `veza-chat-server/src/main.rs`
- `veza-stream-server/src/main.rs`
**Actions** :
- [ ] Refactoriser pour utiliser `veza-common::logging` partout
- [ ] Supprimer la configuration dupliquée
- [ ] Ajouter support de la rotation de logs
- [ ] Standardiser sur `LOG_LEVEL` (au lieu de `RUST_LOG`)
**Code cible** :
```rust
// Dans main.rs des services Rust
use veza_common::logging;
fn main() -> Result<()> {
// Lire LOG_LEVEL (standardisé)
let log_level = std::env::var("LOG_LEVEL")
.unwrap_or_else(|_| "info".to_string());
let config = logging::LoggingConfig {
level: log_level,
format: if is_prod { "json" } else { "text" }.to_string(),
file: Some("/var/log/veza/chat.log".to_string()),
max_size: 100 * 1024 * 1024, // 100MB
max_files: 5,
compress: true,
};
logging::init_with_config(config)?;
// ...
}
```
#### 2.2 Logger Structuré Frontend
**Fichier** : `apps/web/src/utils/logger.ts` (refactoriser)
**Actions** :
- [ ] Créer un logger structuré avec format JSON
- [ ] Extraire `X-Request-ID` des réponses API
- [ ] Inclure `request_id` dans tous les logs
- [ ] Option d'envoyer les logs vers un endpoint backend
- [ ] Remplacer tous les `console.log` par le nouveau logger
**Code cible** :
```typescript
interface LogEntry {
level: 'debug' | 'info' | 'warn' | 'error';
message: string;
timestamp: string;
request_id?: string;
user_id?: string;
context?: Record<string, unknown>;
}
class StructuredLogger {
private requestId?: string;
setRequestId(id: string) {
this.requestId = id;
}
private log(level: LogEntry['level'], message: string, context?: Record<string, unknown>) {
const entry: LogEntry = {
level,
message,
timestamp: new Date().toISOString(),
request_id: this.requestId,
context,
};
if (import.meta.env.PROD) {
// En production, envoyer vers endpoint backend
this.sendToBackend(entry);
} else {
// En développement, afficher dans la console
console[level](JSON.stringify(entry, null, 2));
}
}
info(message: string, context?: Record<string, unknown>) {
this.log('info', message, context);
}
// ... autres méthodes
}
```
#### 2.3 Activer l'Agrégation par Défaut
**Fichier** : `veza-backend-api/internal/config/config.go`
**Actions** :
- [ ] Activer `LOG_AGGREGATION_ENABLED=true` par défaut en production
- [ ] Configurer l'endpoint Loki par défaut
- [ ] Documenter la configuration
**Configuration** :
```env
# En production, activer par défaut
LOG_AGGREGATION_ENABLED=true
LOG_AGGREGATION_ENDPOINT=http://loki:3100/loki/api/v1/push
LOG_AGGREGATION_BATCH_SIZE=100
LOG_AGGREGATION_FLUSH_INTERVAL=5s
```
### Phase 3 : Améliorations (Semaine 5-6)
#### 3.1 Implémenter le Sampling
**Fichier** : `veza-backend-api/internal/logging/sampler.go` (nouveau)
**Actions** :
- [ ] Créer un `zapcore.Sampler` pour les logs DEBUG/INFO
- [ ] Configurer le taux de sampling (ex: 10% pour DEBUG, 50% pour INFO)
- [ ] Toujours logger les WARN/ERROR (100%)
**Code cible** :
```go
func NewSamplingCore(core zapcore.Core, config SamplingConfig) zapcore.Core {
return zapcore.NewSamplerWithOptions(
core,
time.Second,
config.DebugSampleRate, // Ex: 10
config.InfoSampleRate, // Ex: 50
)
}
```
#### 3.2 Ajouter Error Tracking Frontend
**Fichier** : `apps/web/src/utils/errorTracking.ts` (nouveau)
**Actions** :
- [ ] Intégrer Sentry pour le frontend
- [ ] Capturer les erreurs non gérées
- [ ] Capturer les erreurs React (ErrorBoundary)
- [ ] Inclure le `request_id` dans les contextes Sentry
#### 3.3 Implémenter la Rotation des Logs (Rust)
**Fichier** : `veza-common/src/logging.rs`
**Actions** :
- [ ] Utiliser `tracing-appender` pour la rotation
- [ ] Configurer la taille max et le nombre de fichiers
- [ ] Compression des anciens logs
**Code cible** :
```rust
use tracing_appender::{rolling, non_blocking};
let file_appender = rolling::daily("/var/log/veza", "chat.log");
let (non_blocking_appender, _guard) = non_blocking(file_appender);
let file_layer = fmt::layer()
.with_writer(non_blocking_appender)
.json()
.boxed();
```
#### 3.4 Logging Asynchrone
**Fichier** : `veza-backend-api/internal/logging/async_logger.go` (nouveau)
**Actions** :
- [ ] Créer un logger asynchrone pour les logs non critiques
- [ ] Utiliser un buffer avec goroutine
- [ ] Flush périodique et au shutdown
---
## 3. STANDARDS DE LOGGING
### 3.1 Format Standardisé
#### Backend Go (zap)
```json
{
"timestamp": "2025-01-27T10:30:45.123Z",
"level": "info",
"message": "Request completed",
"service": "veza-api",
"env": "production",
"request_id": "550e8400-e29b-41d4-a716-446655440000",
"trace_id": "1234567890abcdef",
"span_id": "abcdef1234567890",
"method": "POST",
"path": "/api/v1/tracks",
"status": 200,
"latency_ms": 45,
"user_id": "user-123",
"ip": "192.168.1.1"
}
```
#### Services Rust (tracing)
```json
{
"timestamp": "2025-01-27T10:30:45.123Z",
"level": "info",
"message": "Message sent",
"service": "veza-chat",
"env": "production",
"request_id": "550e8400-e29b-41d4-a716-446655440000",
"trace_id": "1234567890abcdef",
"span_id": "abcdef1234567890",
"conversation_id": "conv-123",
"user_id": "user-456"
}
```
#### Frontend (JSON)
```json
{
"timestamp": "2025-01-27T10:30:45.123Z",
"level": "info",
"message": "Track uploaded successfully",
"service": "veza-web",
"env": "production",
"request_id": "550e8400-e29b-41d4-a716-446655440000",
"user_id": "user-123",
"track_id": "track-456",
"url": "/tracks/456"
}
```
### 3.2 Niveaux de Log
| Niveau | Usage | Exemples |
|--------|-------|----------|
| **DEBUG** | Informations détaillées pour le debugging | Variables, états intermédiaires |
| **INFO** | Informations générales sur le fonctionnement | Démarrage, requêtes réussies |
| **WARN** | Situations anormales mais non bloquantes | Requêtes lentes, retries |
| **ERROR** | Erreurs qui nécessitent une attention | Échecs de requêtes, exceptions |
| **FATAL** | Erreurs critiques qui arrêtent le service | Paniques, erreurs de configuration |
### 3.3 Champs Obligatoires
Tous les logs doivent inclure :
- `timestamp` : ISO 8601
- `level` : DEBUG, INFO, WARN, ERROR, FATAL
- `message` : Message lisible par un humain
- `service` : Nom du service (veza-api, veza-chat, veza-stream, veza-web)
- `env` : Environnement (development, staging, production)
### 3.4 Champs de Corrélation
Pour tracer une requête :
- `request_id` : UUID v4 unique par requête
- `trace_id` : ID de trace distribué (si OpenTelemetry)
- `span_id` : ID de span (si OpenTelemetry)
### 3.5 Champs Contextuels
Selon le contexte :
- `user_id` : ID de l'utilisateur (si authentifié)
- `method`, `path`, `status` : Pour les requêtes HTTP
- `latency_ms` : Durée de la requête
- `ip`, `user_agent` : Informations client
---
## 4. CONFIGURATION UNIFIÉE
### 4.1 Variables d'Environnement
```env
# =============================================================================
# LOGGING - Configuration Unifiée
# =============================================================================
# Niveau de log (standardisé pour tous les services)
# Valeurs: DEBUG, INFO, WARN, ERROR (Go) ou debug, info, warn, error (Rust)
LOG_LEVEL=info
# Format des logs
# Valeurs: json, text, console
LOG_FORMAT=json
# Agrégation de logs (Backend Go uniquement)
LOG_AGGREGATION_ENABLED=true
LOG_AGGREGATION_ENDPOINT=http://loki:3100/loki/api/v1/push
LOG_AGGREGATION_BATCH_SIZE=100
LOG_AGGREGATION_FLUSH_INTERVAL=5s
LOG_AGGREGATION_TIMEOUT=10s
LOG_AGGREGATION_LABELS=service=veza-api,env=production
# Rotation des logs (Services Rust uniquement)
LOG_FILE=/var/log/veza/service.log
LOG_MAX_SIZE=100MB
LOG_MAX_FILES=5
LOG_COMPRESS=true
# Sampling (optionnel)
LOG_SAMPLING_ENABLED=true
LOG_SAMPLING_DEBUG_RATE=10 # 10% des logs DEBUG
LOG_SAMPLING_INFO_RATE=50 # 50% des logs INFO
```
### 4.2 Configuration par Environnement
#### Development
```env
LOG_LEVEL=DEBUG
LOG_FORMAT=console
LOG_AGGREGATION_ENABLED=false
```
#### Staging
```env
LOG_LEVEL=INFO
LOG_FORMAT=json
LOG_AGGREGATION_ENABLED=true
```
#### Production
```env
LOG_LEVEL=INFO
LOG_FORMAT=json
LOG_AGGREGATION_ENABLED=true
LOG_SAMPLING_ENABLED=true
```
---
## 5. CORRÉLATION DISTRIBUÉE
### 5.1 Propagation du Request ID
#### Backend Go → Services Rust (HTTP)
```go
// Dans les appels HTTP vers Rust
req.Header.Set("X-Request-ID", requestID)
req.Header.Set("X-Trace-ID", traceID)
```
#### Services Rust (Réception)
```rust
// Middleware pour extraire les headers
let request_id = headers.get("x-request-id")
.and_then(|h| h.to_str().ok())
.map(|s| s.to_string());
let trace_id = headers.get("x-trace-id")
.and_then(|h| h.to_str().ok())
.map(|s| s.to_string());
// Utiliser dans les spans
let span = tracing::span!(
tracing::Level::INFO,
"request",
request_id = %request_id.unwrap_or_default(),
trace_id = %trace_id.unwrap_or_default(),
);
```
#### Backend → Frontend (Réponses HTTP)
```go
// Dans les réponses API
c.Header("X-Request-ID", requestID)
```
#### Frontend (Extraction)
```typescript
// Dans le client API
const response = await fetch(url, options);
const requestId = response.headers.get('X-Request-ID');
if (requestId) {
logger.setRequestId(requestId);
}
```
### 5.2 Propagation via RabbitMQ
```go
// Dans les messages RabbitMQ
message.Headers["X-Request-ID"] = requestID
message.Headers["X-Trace-ID"] = traceID
```
```rust
// Dans les consumers Rust
let request_id = headers.get("x-request-id")
.and_then(|h| h.as_str())
.map(|s| s.to_string());
```
---
## 6. CHECKLIST D'IMPLÉMENTATION
### Phase 1 : Corrections Critiques
- [ ] Corriger la configuration du logger Go (LOG_LEVEL)
- [ ] Supprimer l'initialisation dupliquée du logger
- [ ] Implémenter le filtre de secrets
- [ ] Remplacer tous les fmt.Println (12 fichiers)
- [ ] Supprimer les logs de debug excessifs
- [ ] Propager Request ID vers services Rust
- [ ] Tester la corrélation entre services
### Phase 2 : Standardisation
- [ ] Unifier la configuration Rust (utiliser veza-common)
- [ ] Implémenter le logger structuré frontend
- [ ] Remplacer tous les console.log (192 occurrences)
- [ ] Activer l'agrégation par défaut en production
- [ ] Standardiser les formats de logs (JSON en prod)
- [ ] Documenter les standards de logging
### Phase 3 : Améliorations
- [ ] Implémenter le sampling des logs
- [ ] Ajouter l'error tracking frontend (Sentry)
- [ ] Implémenter la rotation des logs (Rust)
- [ ] Implémenter le logging asynchrone
- [ ] Créer des dashboards Grafana
- [ ] Tests d'intégration de la corrélation
### Validation
- [ ] Tester la corrélation d'une requête complète (frontend → backend → chat → stream)
- [ ] Vérifier que tous les logs sont structurés (JSON)
- [ ] Vérifier que les secrets sont filtrés
- [ ] Vérifier que les logs sont agrégés dans Loki
- [ ] Vérifier les performances (pas de dégradation)
---
## 7. MÉTRIQUES DE SUCCÈS
### Avant la Refonte
- ❌ Temps pour tracer une requête : **IMPOSSIBLE**
- ❌ Logs structurés : **~60%** (Go seulement)
- ❌ Corrélation entre services : **0%**
- ❌ Agrégation activée : **Non**
### Après la Refonte
- ✅ Temps pour tracer une requête : **< 1 minute**
- ✅ Logs structurés : **100%**
- ✅ Corrélation entre services : **100%**
- ✅ Agrégation activée : **Oui (prod)**
---
**Fin du Plan de Refonte**