package main import ( "fmt" "log" "os" "strings" "github.com/joho/godotenv" ) type Environment string const ( Development Environment = "development" Staging Environment = "staging" Production Environment = "production" ) type Config struct { Environment Environment DatabaseURL string JWTSecret string Port string MetricsPort string GinMode string LogLevel string Debug bool LogDir string LogVolume string } // LoadConfig loads configuration with proper environment file precedence func LoadConfig() *Config { // Load environment files in priority order loadEnvironmentFiles() // Get environment env := getEnvironment() // Load configuration config := &Config{ Environment: env, DatabaseURL: getEnv("DATABASE_URL", getDefaultDatabaseURL(env)), JWTSecret: getRequiredEnv("JWT_SECRET"), Port: getEnv("PORT", "8080"), MetricsPort: getEnv("METRICS_PORT", "9090"), GinMode: getGinMode(env), LogLevel: getLogLevel(env), Debug: env == Development, LogDir: getEnv("LOG_DIR", "/app/logs"), LogVolume: getEnv("LOG_VOLUME", "counter_logs"), } // Log configuration (without sensitive data) logConfig(config) return config } // loadEnvironmentFiles loads environment files in priority order func loadEnvironmentFiles() { // Get environment first (from system env or default) env := getEnvironmentFromSystem() log.Printf("🔍 Detected environment: %s", env) // Define file loading order (later files override earlier ones) files := []string{ ".env", // Base configuration fmt.Sprintf(".env.%s", env), // Environment-specific } // Load files in order for _, file := range files { if _, err := os.Stat(file); err == nil { if err := godotenv.Load(file); err != nil { log.Printf("⚠️ Warning: Could not load %s: %v", file, err) } else { log.Printf("✅ Loaded: %s", file) } } else { log.Printf("❌ Not found: %s", file) } } } // getEnvironmentFromSystem gets environment from system variables only func getEnvironmentFromSystem() string { // Check if ENVIRONMENT is already set if env := os.Getenv("ENVIRONMENT"); env != "" { return strings.ToLower(env) } // Fallback detection if ginMode := os.Getenv("GIN_MODE"); ginMode == "release" { return "production" } return "development" } // getEnvironment gets the current environment func getEnvironment() Environment { env := strings.ToLower(getEnv("ENVIRONMENT", "development")) switch env { case "development", "dev": return Development case "staging", "stage": return Staging case "production", "prod": return Production default: log.Printf("⚠️ Unknown environment '%s', defaulting to development", env) return Development } } // getGinMode returns the appropriate Gin mode for the environment func getGinMode(env Environment) string { switch env { case Production, Staging: return "release" case Development: return "debug" default: return "debug" } } // getLogLevel returns the appropriate log level for the environment func getLogLevel(env Environment) string { switch env { case Production: return "warn" case Staging: return "info" case Development: return "debug" default: return "debug" } } // getDefaultDatabaseURL returns default database URL for environment func getDefaultDatabaseURL(env Environment) string { switch env { case Development: return "postgres://postgres:password@localhost:5432/counter_db?sslmode=disable" case Staging: return "postgres://postgres:password@postgres:5432/counter_db?sslmode=disable" case Production: return "postgres://postgres:password@postgres:5432/counter_db?sslmode=require" default: return "postgres://postgres:password@localhost:5432/counter_db?sslmode=disable" } } // getEnv gets environment variable with default value func getEnv(key, defaultValue string) string { if value := os.Getenv(key); value != "" { return value } return defaultValue } // getRequiredEnv gets required environment variable func getRequiredEnv(key string) string { value := os.Getenv(key) if value == "" { log.Fatalf("❌ Required environment variable %s is not set", key) } return value } // logConfig logs configuration (without sensitive data) func logConfig(config *Config) { // Environment banner log.Printf("") log.Printf("╔══════════════════════════════════════════════════════════════╗") log.Printf("║ COUNTER APPLICATION ║") log.Printf("║ ║") log.Printf("║ 🌍 ENVIRONMENT: %-15s ║", strings.ToUpper(string(config.Environment))) log.Printf("║ 🚀 MODE: %-20s ║", config.GinMode) log.Printf("║ 🔧 DEBUG: %-20s ║", fmt.Sprintf("%t", config.Debug)) log.Printf("║ 📊 LOG LEVEL: %-15s ║", config.LogLevel) log.Printf("║ 🌐 PORT: %-20s ║", config.Port) log.Printf("║ 📈 METRICS PORT: %-15s ║", config.MetricsPort) log.Printf("║ 📝 LOG DIR: %-20s ║", config.LogDir) log.Printf("║ 📦 LOG VOLUME: %-18s ║", config.LogVolume) log.Printf("║ ║") log.Printf("║ 📁 Configuration Files Loaded: ║") log.Printf("║ • .env (base configuration) ║") log.Printf("║ • .env.%s (environment-specific) ║", config.Environment) log.Printf("║ ║") log.Printf("║ 🔐 Security: ║") log.Printf("║ • Database: %s ║", maskDatabaseURL(config.DatabaseURL)) log.Printf("║ • JWT Secret: %s ║", maskSecret(config.JWTSecret)) log.Printf("║ ║") log.Printf("╚══════════════════════════════════════════════════════════════╝") log.Printf("") } // maskDatabaseURL masks sensitive parts of database URL func maskDatabaseURL(url string) string { // Simple masking - replace password with *** if strings.Contains(url, "://") { parts := strings.Split(url, "://") if len(parts) == 2 { // Replace password in connection string masked := strings.Replace(parts[1], ":", ":***@", 1) return parts[0] + "://" + masked } } return "***" } // maskSecret masks JWT secret for logging func maskSecret(secret string) string { if len(secret) <= 8 { return "***" } return secret[:4] + "***" + secret[len(secret)-4:] }