package logging import ( "io" "os" "path/filepath" "time" "counter/internal/infrastructure/config" "github.com/gin-gonic/gin" "github.com/sirupsen/logrus" ) // Logger interface defines the contract for logging type Logger interface { Info(args ...interface{}) Infof(format string, args ...interface{}) Warn(args ...interface{}) Warnf(format string, args ...interface{}) Error(args ...interface{}) Errorf(format string, args ...interface{}) Fatal(args ...interface{}) Fatalf(format string, args ...interface{}) WithFields(fields logrus.Fields) *logrus.Entry WithError(err error) *logrus.Entry } // LogrusLogger implements the Logger interface using logrus type LogrusLogger struct { *logrus.Logger } // InitLogger initializes the structured logger with file output func InitLogger(cfg *config.Config) (Logger, error) { logger := logrus.New() // Set log level based on configuration level, err := logrus.ParseLevel(cfg.LogLevel) if err != nil { level = logrus.InfoLevel } logger.SetLevel(level) // Set JSON formatter for structured logging logger.SetFormatter(&logrus.JSONFormatter{ TimestampFormat: time.RFC3339, FieldMap: logrus.FieldMap{ logrus.FieldKeyTime: "timestamp", logrus.FieldKeyLevel: "level", logrus.FieldKeyMsg: "message", }, }) // Create log directory if it doesn't exist if err := os.MkdirAll(cfg.LogDir, 0755); err != nil { return nil, err } // Create log file with timestamp logFile := filepath.Join(cfg.LogDir, "app.log") file, err := os.OpenFile(logFile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666) if err != nil { return nil, err } // Set output to both file and stdout multiWriter := io.MultiWriter(os.Stdout, file) logger.SetOutput(multiWriter) // Log initialization with default fields logger.WithFields(logrus.Fields{ "service": "counter-app", "environment": string(cfg.Environment), "version": "1.0.0", }).Info("Logger initialized successfully") return &LogrusLogger{Logger: logger}, nil } // LoggingMiddleware creates a Gin middleware for HTTP request logging func LoggingMiddleware(logger Logger, cfg *config.Config) gin.HandlerFunc { return gin.LoggerWithFormatter(func(param gin.LogFormatterParams) string { // Create structured log entry with default fields entry := logger.WithFields(logrus.Fields{ "service": "counter-app", "environment": string(cfg.Environment), "version": "1.0.0", "method": param.Method, "path": param.Path, "status": param.StatusCode, "latency": param.Latency.String(), "client_ip": param.ClientIP, "user_agent": param.Request.UserAgent(), "timestamp": param.TimeStamp.Format(time.RFC3339), }) // Set log level based on status code switch { case param.StatusCode >= 500: entry.Error("HTTP Request") case param.StatusCode >= 400: entry.Warn("HTTP Request") default: entry.Info("HTTP Request") } // Return empty string since we're handling logging ourselves return "" }) }