2025-12-27 15:02:02 +00:00
|
|
|
package handlers
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"fmt"
|
|
|
|
|
"net/http"
|
|
|
|
|
"os"
|
|
|
|
|
|
|
|
|
|
"github.com/gin-gonic/gin"
|
|
|
|
|
"go.uber.org/zap"
|
|
|
|
|
|
|
|
|
|
"veza-backend-api/internal/config"
|
|
|
|
|
"veza-backend-api/internal/logging"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// FrontendLogHandler gère la réception des logs du frontend
|
|
|
|
|
type FrontendLogHandler struct {
|
|
|
|
|
logger *zap.Logger
|
|
|
|
|
frontendLogger *logging.Logger
|
|
|
|
|
logDir string
|
|
|
|
|
commonHandler *CommonHandler
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// NewFrontendLogHandler crée un nouveau handler pour les logs frontend
|
|
|
|
|
func NewFrontendLogHandler(cfg *config.Config, logger *zap.Logger) (*FrontendLogHandler, error) {
|
|
|
|
|
logDir := cfg.LogDir
|
|
|
|
|
if logDir == "" {
|
|
|
|
|
logDir = "/var/log/veza"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Créer le répertoire de logs s'il n'existe pas
|
|
|
|
|
// En développement, utiliser un répertoire local si /var/log n'est pas accessible
|
|
|
|
|
if err := os.MkdirAll(logDir, 0755); err != nil {
|
|
|
|
|
// En développement, fallback vers un répertoire local
|
|
|
|
|
if cfg.Env == "development" || cfg.Env == "dev" {
|
|
|
|
|
fallbackDir := "./logs"
|
|
|
|
|
if err2 := os.MkdirAll(fallbackDir, 0755); err2 != nil {
|
|
|
|
|
return nil, fmt.Errorf("failed to create log directory %s (fallback %s also failed: %v): %w", logDir, fallbackDir, err2, err)
|
|
|
|
|
}
|
|
|
|
|
logDir = fallbackDir
|
|
|
|
|
} else {
|
|
|
|
|
return nil, fmt.Errorf("failed to create log directory %s: %w (hint: create it manually with 'sudo mkdir -p %s && sudo chown $USER:$USER %s')", logDir, err, logDir, logDir)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Créer un logger spécifique pour le frontend avec fichiers séparés
|
|
|
|
|
frontendLogger, err := logging.NewLoggerWithFileRotation(logDir, "frontend", cfg.Env, cfg.LogLevel)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, fmt.Errorf("failed to create frontend logger: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
handler := &FrontendLogHandler{
|
|
|
|
|
logger: logger,
|
|
|
|
|
frontendLogger: frontendLogger,
|
|
|
|
|
logDir: logDir,
|
|
|
|
|
commonHandler: NewCommonHandler(logger),
|
|
|
|
|
}
|
|
|
|
|
return handler, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// FrontendLogRequest représente une requête de log du frontend
|
|
|
|
|
type FrontendLogRequest struct {
|
|
|
|
|
Timestamp string `json:"timestamp"`
|
|
|
|
|
Level string `json:"level"`
|
|
|
|
|
Message string `json:"message"`
|
|
|
|
|
Context map[string]interface{} `json:"context,omitempty"`
|
|
|
|
|
Data interface{} `json:"data,omitempty"`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ReceiveLog gère la réception d'un log du frontend
|
|
|
|
|
// @Summary Receive frontend log
|
|
|
|
|
// @Description Receive and store a log entry from the frontend application
|
|
|
|
|
// @Tags Logging
|
|
|
|
|
// @Accept json
|
|
|
|
|
// @Produce json
|
|
|
|
|
// @Param log body FrontendLogRequest true "Frontend log entry"
|
|
|
|
|
// @Success 200 {object} handlers.APIResponse{data=object{received=bool}}
|
|
|
|
|
// @Failure 400 {object} handlers.APIResponse "Invalid log entry"
|
|
|
|
|
// @Failure 500 {object} handlers.APIResponse "Internal server error"
|
|
|
|
|
// @Router /api/v1/logs/frontend [post]
|
|
|
|
|
func (h *FrontendLogHandler) ReceiveLog(c *gin.Context) {
|
|
|
|
|
var logReq FrontendLogRequest
|
|
|
|
|
if err := c.ShouldBindJSON(&logReq); err != nil {
|
|
|
|
|
h.commonHandler.RespondWithError(c, http.StatusBadRequest, "Invalid log entry format", err)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Valider le niveau de log
|
|
|
|
|
level := logReq.Level
|
|
|
|
|
if level == "" {
|
|
|
|
|
level = "INFO"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Extraire le request_id du contexte si présent
|
|
|
|
|
requestID := ""
|
|
|
|
|
if logReq.Context != nil {
|
|
|
|
|
if rid, ok := logReq.Context["request_id"].(string); ok {
|
|
|
|
|
requestID = rid
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Construire les champs zap
|
|
|
|
|
fields := []zap.Field{
|
|
|
|
|
zap.String("source", "frontend"),
|
|
|
|
|
zap.String("level", level),
|
|
|
|
|
zap.String("message", logReq.Message),
|
|
|
|
|
}
|
2026-01-13 18:47:57 +00:00
|
|
|
|
2025-12-27 15:02:02 +00:00
|
|
|
if requestID != "" {
|
|
|
|
|
fields = append(fields, zap.String("request_id", requestID))
|
|
|
|
|
}
|
2026-01-13 18:47:57 +00:00
|
|
|
|
2025-12-27 15:02:02 +00:00
|
|
|
if logReq.Context != nil {
|
|
|
|
|
// Ajouter les champs du contexte (en évitant les doublons)
|
|
|
|
|
for k, v := range logReq.Context {
|
|
|
|
|
if k != "request_id" { // Déjà ajouté
|
|
|
|
|
fields = append(fields, zap.Any(k, v))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-01-13 18:47:57 +00:00
|
|
|
|
2025-12-27 15:02:02 +00:00
|
|
|
if logReq.Data != nil {
|
|
|
|
|
fields = append(fields, zap.Any("data", logReq.Data))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Logger selon le niveau
|
|
|
|
|
switch level {
|
|
|
|
|
case "DEBUG":
|
|
|
|
|
h.frontendLogger.Debug(logReq.Message, fields...)
|
|
|
|
|
case "INFO":
|
|
|
|
|
h.frontendLogger.Info(logReq.Message, fields...)
|
|
|
|
|
case "WARN":
|
|
|
|
|
h.frontendLogger.Warn(logReq.Message, fields...)
|
|
|
|
|
case "ERROR":
|
|
|
|
|
h.frontendLogger.Error(logReq.Message, fields...)
|
|
|
|
|
default:
|
|
|
|
|
h.frontendLogger.Info(logReq.Message, fields...)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Répondre avec succès
|
2026-01-15 16:32:02 +00:00
|
|
|
// Action 1.3.2.1: Use wrapped format helper
|
|
|
|
|
RespondSuccess(c, http.StatusOK, gin.H{
|
|
|
|
|
"received": true,
|
|
|
|
|
"level": level,
|
2025-12-27 15:02:02 +00:00
|
|
|
})
|
|
|
|
|
}
|