226 lines
7.0 KiB
Go
226 lines
7.0 KiB
Go
package config
|
|
|
|
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 "info" // Changed from "warn" to "info" to capture more logs
|
|
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) {
|
|
// Use standard log for configuration banner since logger might not be initialized yet
|
|
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:]
|
|
}
|