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), } if requestID != "" { fields = append(fields, zap.String("request_id", requestID)) } 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)) } } } 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 // Action 1.3.2.1: Use wrapped format helper RespondSuccess(c, http.StatusOK, gin.H{ "received": true, "level": level, }) }