package config import ( "errors" "fmt" "os" "path" "strings" "github.com/mitchellh/mapstructure" "github.com/rs/zerolog" "github.com/spf13/pflag" "github.com/spf13/viper" ) type LoggerConfig struct { Verbose bool `mapstructure:"verbose"` Format string `mapstructure:"format"` } func (c *LoggerConfig) MarshalZerologObject(e *zerolog.Event) { e. Bool("verbose", c.Verbose). Str("format", c.Format) } type SomethingClientConfig struct { Value int `mapstructure:"value"` } func (c *SomethingClientConfig) MarshalZerologObject(event *zerolog.Event) { event. Int("value", c.Value) } type BusinessLogicConfig struct { Threshold int `mapstructure:"threshold"` } func (c *BusinessLogicConfig) MarshalZerologObject(event *zerolog.Event) { event. Int("threshold", c.Threshold) } type MainConfig struct { Something SomethingClientConfig `mapstructure:"something"` BusinessLogic BusinessLogicConfig `mapstructure:"bl"` Logger LoggerConfig `mapstructure:"logger"` configFile string } func (c *MainConfig) MarshalZerologObject(event *zerolog.Event) { event. Str("config-file", c.configFile). Object("logger", &c.Logger). Object("something", &c.Something). Object("bl", &c.BusinessLogic) } func addLoggerOptions() { pflag.Bool("verbose", false, "log at debug level") viper.BindPFlag("logger.verbose", pflag.Lookup("verbose")) //nolint:errcheck pflag.String("log-format", "json", "logging format (json, console)") viper.BindPFlag("logger.format", pflag.Lookup("log-format")) //nolint:errcheck } func addSomethingOptions() { pflag.Int("something-value", 123, "value of something") viper.BindPFlag("something.value", pflag.Lookup("something-value")) //nolint:errcheck viper.BindEnv("something.value", "SOMETHING_VALUE") //nolint:errcheck } func addBLOptions() { pflag.Int("bl-threshold", 100, "threshold for a business logic action") viper.BindPFlag("bl.threshold", pflag.Lookup("bl-threshold")) //nolint:errcheck viper.BindEnv("bl.threshold", "BUSINESS_THRESHOLD") //nolint:errcheck } func addConfigFromFile(configRelPath string) error { mode := os.Getenv("RUN_MODE") if mode == "" { mode = "development" } viper.SetConfigName("example." + mode) myName, err := os.Executable() if err == nil && !strings.HasPrefix(configRelPath, "/") { myDir := path.Dir(myName) viper.AddConfigPath(path.Join(myDir, configRelPath)) } else { viper.AddConfigPath(configRelPath) } err = viper.ReadInConfig() if err != nil { var notFound viper.ConfigFileNotFoundError if !errors.As(err, ¬Found) { return fmt.Errorf("failed to read config file at %s: %w", viper.ConfigFileUsed(), err) } } return nil } func loadConfigFromFile() (MainConfig, error) { path := os.Getenv("EXAMPLE_CONFIG") if path == "" { path = "." } err := addConfigFromFile(path) if err != nil { return MainConfig{}, err } var config MainConfig err = viper.Unmarshal(&config, viper.DecodeHook( mapstructure.ComposeDecodeHookFunc( mapstructure.StringToTimeDurationHookFunc(), mapstructure.StringToIPHookFunc(), ), )) if err != nil { return MainConfig{}, fmt.Errorf("failed to parse options: %w", err) } config.configFile = viper.ConfigFileUsed() return config, nil } func GetMainConfig() (MainConfig, error) { addLoggerOptions() addSomethingOptions() addBLOptions() pflag.Parse() viper.BindPFlags(pflag.CommandLine) //nolint:errcheck return loadConfigFromFile() }