19 KiB
🏗️ PLAN DE REFONTE DU SYSTÈME DE LOGS VEZA
Date: 2025-01-27
Version: 1.0
Statut: 📋 Plan d'Action
📋 TABLE DES MATIÈRES
- Architecture Cible
- Plan d'Implémentation
- Standards de Logging
- Configuration Unifiée
- Corrélation Distribuée
- Checklist d'Implé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_LEVELAVANT d'initialiser le logger - Supprimer l'initialisation dupliquée dans
main.go - Utiliser
NewLoggerWithAggregationdirectement si agrégation activée - Garantir le flush au shutdown via
ShutdownManager
Code cible :
// 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.Corequi 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 :
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*parlogger.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 :
// 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.goveza-backend-api/internal/services/stream_service.goveza-chat-server/src/main.rsveza-stream-server/src/main.rs
Actions :
- Extraire
request_iddu contexte Gin - Ajouter header
X-Request-IDdans les appels HTTP vers Rust - Extraire
X-Request-IDdans les services Rust - Utiliser
request_iddans les spans tracing
Code Go :
// Dans chat_service.go
requestID := c.GetString("request_id")
req.Header.Set("X-Request-ID", requestID)
Code 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.rsveza-stream-server/src/main.rs
Actions :
- Refactoriser pour utiliser
veza-common::loggingpartout - Supprimer la configuration dupliquée
- Ajouter support de la rotation de logs
- Standardiser sur
LOG_LEVEL(au lieu deRUST_LOG)
Code cible :
// 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-IDdes réponses API - Inclure
request_iddans tous les logs - Option d'envoyer les logs vers un endpoint backend
- Remplacer tous les
console.logpar le nouveau logger
Code cible :
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=truepar défaut en production - Configurer l'endpoint Loki par défaut
- Documenter la configuration
Configuration :
# 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.Samplerpour les logs DEBUG/INFO - Configurer le taux de sampling (ex: 10% pour DEBUG, 50% pour INFO)
- Toujours logger les WARN/ERROR (100%)
Code cible :
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_iddans les contextes Sentry
3.3 Implémenter la Rotation des Logs (Rust)
Fichier : veza-common/src/logging.rs
Actions :
- Utiliser
tracing-appenderpour la rotation - Configurer la taille max et le nombre de fichiers
- Compression des anciens logs
Code cible :
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)
{
"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)
{
"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)
{
"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 8601level: DEBUG, INFO, WARN, ERROR, FATALmessage: Message lisible par un humainservice: 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êtetrace_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 HTTPlatency_ms: Durée de la requêteip,user_agent: Informations client
4. CONFIGURATION UNIFIÉE
4.1 Variables d'Environnement
# =============================================================================
# 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
LOG_LEVEL=DEBUG
LOG_FORMAT=console
LOG_AGGREGATION_ENABLED=false
Staging
LOG_LEVEL=INFO
LOG_FORMAT=json
LOG_AGGREGATION_ENABLED=true
Production
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)
// Dans les appels HTTP vers Rust
req.Header.Set("X-Request-ID", requestID)
req.Header.Set("X-Trace-ID", traceID)
Services Rust (Réception)
// 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)
// Dans les réponses API
c.Header("X-Request-ID", requestID)
Frontend (Extraction)
// 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
// Dans les messages RabbitMQ
message.Headers["X-Request-ID"] = requestID
message.Headers["X-Trace-ID"] = traceID
// 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