- Add scripts/build.sh to compile binaries into bin/ directory - Move all zerolog setup logic from cmd/server/main.go to pkg/config - Add log level configuration support (trace, debug, info, warn, error, fatal, panic) - Simplify cmd/server/main.go from 57 to 27 lines (53% reduction) - Update .gitignore to use bin/ directory instead of individual files - Document build process and bin directory in AGENTS.md - Maintain backward compatibility with all existing functionality
220 lines
6.7 KiB
Go
220 lines
6.7 KiB
Go
package config
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/rs/zerolog"
|
|
"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"`
|
|
Level string `mapstructure:"level"`
|
|
}
|
|
|
|
// 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 up initial console logging for config loading messages
|
|
consoleWriter := zerolog.ConsoleWriter{Out: os.Stderr}
|
|
log.Logger = log.Output(consoleWriter)
|
|
|
|
// 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)
|
|
v.SetDefault("logging.level", "trace")
|
|
|
|
// 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")
|
|
v.BindEnv("logging.level", "DLC_LOGGING_LEVEL")
|
|
|
|
// 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)
|
|
}
|
|
|
|
// Configure log output format (JSON or console) first
|
|
if config.Logging.JSON {
|
|
log.Logger = log.Output(os.Stderr)
|
|
} else {
|
|
consoleWriter := zerolog.ConsoleWriter{Out: os.Stderr}
|
|
log.Logger = log.Output(consoleWriter)
|
|
}
|
|
|
|
// Setup logging based on configuration
|
|
config.SetupLogging()
|
|
|
|
log.Info().
|
|
Str("host", config.Server.Host).
|
|
Int("port", config.Server.Port).
|
|
Dur("shutdown_timeout", config.Shutdown.Timeout).
|
|
Bool("logging_json", config.Logging.JSON).
|
|
Str("logging_level", config.Logging.Level).
|
|
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
|
|
}
|
|
|
|
// GetLogLevel returns the logging level
|
|
func (c *Config) GetLogLevel() string {
|
|
return c.Logging.Level
|
|
}
|
|
|
|
// SetupLogging configures zerolog based on the configuration
|
|
func (c *Config) SetupLogging() {
|
|
// Parse log level
|
|
level := parseLogLevel(c.GetLogLevel())
|
|
zerolog.SetGlobalLevel(level)
|
|
|
|
zerolog.TimeFieldFormat = zerolog.TimeFormatUnix
|
|
}
|
|
|
|
// parseLogLevel converts a string log level to zerolog.Level
|
|
func parseLogLevel(level string) zerolog.Level {
|
|
switch strings.ToLower(level) {
|
|
case "trace":
|
|
return zerolog.TraceLevel
|
|
case "debug":
|
|
return zerolog.DebugLevel
|
|
case "info":
|
|
return zerolog.InfoLevel
|
|
case "warn", "warning":
|
|
return zerolog.WarnLevel
|
|
case "error":
|
|
return zerolog.ErrorLevel
|
|
case "fatal":
|
|
return zerolog.FatalLevel
|
|
case "panic":
|
|
return zerolog.PanicLevel
|
|
default:
|
|
log.Warn().Str("level", level).Msg("Unknown log level, defaulting to trace")
|
|
return zerolog.TraceLevel
|
|
}
|
|
}
|