package config import ( "fmt" "os" "time" "github.com/rs/zerolog/log" "github.com/spf13/viper" ) // Config represents the application configuration type Config struct { Server ServerConfig `mapstructure:"server"` Shutdown ShutdownConfig `mapstructure:"shutdown"` Logging LoggingConfig `mapstructure:"logging"` Telemetry TelemetryConfig `mapstructure:"telemetry"` } // ServerConfig holds server-related configuration type ServerConfig struct { Host string `mapstructure:"host"` Port int `mapstructure:"port"` } // ShutdownConfig holds shutdown-related configuration type ShutdownConfig struct { Timeout time.Duration `mapstructure:"timeout"` } // LoggingConfig holds logging-related configuration type LoggingConfig struct { JSON bool `mapstructure:"json"` } // TelemetryConfig holds OpenTelemetry-related configuration type TelemetryConfig struct { Enabled bool `mapstructure:"enabled"` OTLPEndpoint string `mapstructure:"otlp_endpoint"` ServiceName string `mapstructure:"service_name"` Insecure bool `mapstructure:"insecure"` Sampler SamplerConfig `mapstructure:"sampler"` } // SamplerConfig holds tracing sampler configuration type SamplerConfig struct { Type string `mapstructure:"type"` Ratio float64 `mapstructure:"ratio"` } // LoadConfig loads configuration from file, environment variables, and defaults // Configuration priority: file > environment variables > defaults // To specify a custom config file path, set DLC_CONFIG_FILE environment variable func LoadConfig() (*Config, error) { v := viper.New() // Set default values v.SetDefault("server.host", "0.0.0.0") v.SetDefault("server.port", 8080) v.SetDefault("shutdown.timeout", 30*time.Second) v.SetDefault("logging.json", false) // Telemetry defaults v.SetDefault("telemetry.enabled", false) v.SetDefault("telemetry.otlp_endpoint", "localhost:4317") v.SetDefault("telemetry.service_name", "DanceLessonsCoach") v.SetDefault("telemetry.insecure", true) v.SetDefault("telemetry.sampler.type", "parentbased_always_on") v.SetDefault("telemetry.sampler.ratio", 1.0) // Check for custom config file path via environment variable if configFile := os.Getenv("DLC_CONFIG_FILE"); configFile != "" { v.SetConfigFile(configFile) log.Info().Str("config_file", configFile).Msg("Using custom config file path") } else { // Default: look for config.yaml in current directory v.SetConfigName("config") v.SetConfigType("yaml") v.AddConfigPath(".") } // Read config file if it exists if err := v.ReadInConfig(); err != nil { if _, ok := err.(viper.ConfigFileNotFoundError); !ok { // Config file was found but there was an error reading it log.Warn().Err(err).Msg("Error reading config file, using defaults") } // Config file not found, continue with environment variables and defaults } else { log.Info().Str("config_file", v.ConfigFileUsed()).Msg("Config file loaded") } // Bind environment variables v.AutomaticEnv() v.SetEnvPrefix("DLC") // DanceLessonsCoach prefix v.BindEnv("server.host", "DLC_SERVER_HOST") v.BindEnv("server.port", "DLC_SERVER_PORT") v.BindEnv("shutdown.timeout", "DLC_SHUTDOWN_TIMEOUT") v.BindEnv("logging.json", "DLC_LOGGING_JSON") // Telemetry environment variables v.BindEnv("telemetry.enabled", "DLC_TELEMETRY_ENABLED") v.BindEnv("telemetry.otlp_endpoint", "DLC_TELEMETRY_OTLP_ENDPOINT") v.BindEnv("telemetry.service_name", "DLC_TELEMETRY_SERVICE_NAME") v.BindEnv("telemetry.insecure", "DLC_TELEMETRY_INSECURE") v.BindEnv("telemetry.sampler.type", "DLC_TELEMETRY_SAMPLER_TYPE") v.BindEnv("telemetry.sampler.ratio", "DLC_TELEMETRY_SAMPLER_RATIO") // Unmarshal into Config struct var config Config if err := v.Unmarshal(&config); err != nil { log.Error().Err(err).Msg("Failed to unmarshal config") return nil, fmt.Errorf("config unmarshal error: %w", err) } log.Info(). Str("host", config.Server.Host). Int("port", config.Server.Port). Dur("shutdown_timeout", config.Shutdown.Timeout). Bool("logging_json", config.Logging.JSON). Bool("telemetry_enabled", config.Telemetry.Enabled). Str("telemetry_service", config.Telemetry.ServiceName). Msg("Configuration loaded") return &config, nil } // GetServerAddress returns the formatted server address (host:port) func (c *Config) GetServerAddress() string { return fmt.Sprintf("%s:%d", c.Server.Host, c.Server.Port) } // GetTelemetryEnabled returns whether telemetry is enabled func (c *Config) GetTelemetryEnabled() bool { return c.Telemetry.Enabled } // GetOTLPEndpoint returns the OTLP endpoint for telemetry func (c *Config) GetOTLPEndpoint() string { return c.Telemetry.OTLPEndpoint } // GetServiceName returns the service name for telemetry func (c *Config) GetServiceName() string { return c.Telemetry.ServiceName } // GetTelemetryInsecure returns whether to use insecure connection func (c *Config) GetTelemetryInsecure() bool { return c.Telemetry.Insecure } // GetSamplerType returns the sampler type func (c *Config) GetSamplerType() string { return c.Telemetry.Sampler.Type } // GetSamplerRatio returns the sampler ratio func (c *Config) GetSamplerRatio() float64 { return c.Telemetry.Sampler.Ratio }