diff --git a/cmd/root.go b/cmd/root.go index 5898a79..c7705b3 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -56,12 +56,39 @@ func LoadConfig(cmd *cobra.Command) error { applyBoolFlag(cmd, "update-only", &Cfg.Sync.UpdateOnly) applyStringFlag(cmd, "cache-dir", &Cfg.Sync.CacheDir) + // Compute effective log settings: config file provides defaults, + // explicit CLI flags take precedence. Do not mutate the flag-backed globals. + effectiveDebug := debug + effectiveVerbose := verbose + effectiveLogFile := logFile + effectiveLogFormat := logFormat + + var unknownLogLevelMsg string + if Cfg.Log.Level != "" && !cmd.Flags().Changed("debug") && !cmd.Flags().Changed("verbose") { + switch strings.ToLower(Cfg.Log.Level) { + case "debug": + effectiveDebug = true + case "info": + effectiveVerbose = true + case "warn", "warning": + // default, no action needed + default: + unknownLogLevelMsg = fmt.Sprintf("Unknown log.level %q in config, using default (warn)", Cfg.Log.Level) + } + } + if Cfg.Log.File != "" && !cmd.Flags().Changed("log-file") { + effectiveLogFile = Cfg.Log.File + } + if Cfg.Log.Format != "" && !cmd.Flags().Changed("log-format") { + effectiveLogFormat = Cfg.Log.Format + } + // Configure log level var level logrus.Level switch { - case debug: + case effectiveDebug: level = logrus.DebugLevel - case verbose: + case effectiveVerbose: level = logrus.InfoLevel default: level = logrus.WarnLevel @@ -70,13 +97,13 @@ func LoadConfig(cmd *cobra.Command) error { // Configure formatter var formatter logrus.Formatter - switch strings.ToLower(logFormat) { + switch strings.ToLower(effectiveLogFormat) { case "json": formatter = &logrus.JSONFormatter{} case "text", "": formatter = &logrus.TextFormatter{FullTimestamp: true} default: - return fmt.Errorf("invalid --log-format %q: must be 'text' or 'json'", logFormat) + return fmt.Errorf("invalid log format %q: must be 'text' or 'json'", effectiveLogFormat) } setAllLogFormatters(formatter) @@ -86,8 +113,8 @@ func LoadConfig(cmd *cobra.Command) error { _ = logFileFD.Close() logFileFD = nil } - if logFile != "" { - f, err := os.OpenFile(logFile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0600) + if effectiveLogFile != "" { + f, err := os.OpenFile(effectiveLogFile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0600) if err != nil { return fmt.Errorf("opening log file: %w", err) } @@ -95,6 +122,11 @@ func LoadConfig(cmd *cobra.Command) error { setAllLogOutputs(io.MultiWriter(os.Stderr, f)) } + // Emit deferred warning after format/output are configured. + if unknownLogLevelMsg != "" { + log.Warn(unknownLogLevelMsg) + } + return nil } diff --git a/config/config.go b/config/config.go index c1ed043..33589bd 100644 --- a/config/config.go +++ b/config/config.go @@ -10,10 +10,19 @@ import ( // Config holds all configuration for axm2snipe. type Config struct { - ABM ABMConfig `yaml:"abm"` + ABM ABMConfig `yaml:"abm"` SnipeIT SnipeITConfig `yaml:"snipe_it"` - Sync SyncConfig `yaml:"sync"` - Slack SlackConfig `yaml:"slack"` + Sync SyncConfig `yaml:"sync"` + Slack SlackConfig `yaml:"slack"` + Log LogConfig `yaml:"log"` +} + +// LogConfig holds logging settings that can be set in the config file. +// CLI flags (--log-file, --log-format, -v, -d) take precedence. +type LogConfig struct { + File string `yaml:"file"` // path to log file (appended to stderr) + Format string `yaml:"format"` // "text" (default) or "json" + Level string `yaml:"level"` // "warn" (default), "info", or "debug" } // SlackConfig holds optional Slack webhook notification settings.