Files
logwisp/src/internal/config/loader.go

204 lines
5.2 KiB
Go

// FILE: src/internal/config/loader.go
package config
import (
"fmt"
"os"
"path/filepath"
"strings"
lconfig "github.com/lixenwraith/config"
)
// LoadContext holds all configuration sources
type LoadContext struct {
FlagConfig any // Parsed command-line flags from main
}
func defaults() *Config {
return &Config{
// Top-level flag defaults
UseRouter: false,
Background: false,
ShowVersion: false,
Quiet: false,
// Runtime behavior defaults
DisableStatusReporter: false,
// Child process indicator
BackgroundDaemon: false,
// Existing defaults
Logging: DefaultLogConfig(),
Pipelines: []PipelineConfig{
{
Name: "default",
Sources: []SourceConfig{
{
Type: "directory",
Options: map[string]any{
"path": "./",
"pattern": "*.log",
"check_interval_ms": 100,
},
},
},
Sinks: []SinkConfig{
{
Type: "http",
Options: map[string]any{
"port": 8080,
"buffer_size": 1000,
"stream_path": "/stream",
"status_path": "/status",
"heartbeat": map[string]any{
"enabled": true,
"interval_seconds": 30,
"include_timestamp": true,
"include_stats": false,
"format": "comment",
},
},
},
},
},
},
}
}
// Load is the single entry point for loading all configuration
func Load(args []string) (*Config, error) {
configPath, isExplicit := resolveConfigPath(args)
// Build configuration with all sources
cfg, err := lconfig.NewBuilder().
WithDefaults(defaults()).
WithEnvPrefix("LOGWISP_").
WithFile(configPath).
WithArgs(args).
WithEnvTransform(customEnvTransform).
WithSources(
lconfig.SourceCLI,
lconfig.SourceEnv,
lconfig.SourceFile,
lconfig.SourceDefault,
).
Build()
if err != nil {
// Config file load errors
if strings.Contains(err.Error(), "not found") {
if isExplicit {
return nil, fmt.Errorf("config file not found: %s", configPath)
}
// If the default config file is not found, it's not an error.
} else {
return nil, fmt.Errorf("failed to load config: %w", err)
}
}
// Scan into final config struct
finalConfig := &Config{}
if err := cfg.Scan("", finalConfig); err != nil {
return nil, fmt.Errorf("failed to scan config: %w", err)
}
if _, err := os.Stat(configPath); err == nil {
finalConfig.ConfigFile = configPath
}
// Ensure critical fields are not nil
if finalConfig.Logging == nil {
finalConfig.Logging = DefaultLogConfig()
}
// Apply console target overrides if needed
if err := applyConsoleTargetOverrides(finalConfig); err != nil {
return nil, fmt.Errorf("failed to apply console target overrides: %w", err)
}
// Validate configuration
return finalConfig, finalConfig.validate()
}
// resolveConfigPath returns the configuration file path
func resolveConfigPath(args []string) (path string, isExplicit bool) {
// 1. Check for --config flag in command-line arguments (highest precedence)
for i, arg := range args {
if (arg == "--config" || arg == "-c") && i+1 < len(args) {
return args[i+1], true
}
if strings.HasPrefix(arg, "--config=") {
return strings.TrimPrefix(arg, "--config="), true
}
}
// 2. Check environment variables
if configFile := os.Getenv("LOGWISP_CONFIG_FILE"); configFile != "" {
path = configFile
if configDir := os.Getenv("LOGWISP_CONFIG_DIR"); configDir != "" {
path = filepath.Join(configDir, configFile)
}
return path, true
}
if configDir := os.Getenv("LOGWISP_CONFIG_DIR"); configDir != "" {
return filepath.Join(configDir, "logwisp.toml"), true
}
// 3. Check default user config location
if homeDir, err := os.UserHomeDir(); err == nil {
configPath := filepath.Join(homeDir, ".config", "logwisp", "logwisp.toml")
if _, err := os.Stat(configPath); err == nil {
return configPath, false // Found a default, but not explicitly set by user
}
}
// 4. Fallback to default in current directory
return "logwisp.toml", false
}
func customEnvTransform(path string) string {
env := strings.ReplaceAll(path, ".", "_")
env = strings.ToUpper(env)
env = "LOGWISP_" + env
return env
}
// applyConsoleTargetOverrides centralizes console target configuration
func applyConsoleTargetOverrides(cfg *Config) error {
// Check environment variable for console target override
consoleTarget := os.Getenv("LOGWISP_CONSOLE_TARGET")
if consoleTarget == "" {
return nil
}
// Validate console target value
validTargets := map[string]bool{
"stdout": true,
"stderr": true,
"split": true,
}
if !validTargets[consoleTarget] {
return fmt.Errorf("invalid LOGWISP_CONSOLE_TARGET value: %s", consoleTarget)
}
// Apply to all console sinks
for i, pipeline := range cfg.Pipelines {
for j, sink := range pipeline.Sinks {
if sink.Type == "stdout" || sink.Type == "stderr" {
if sink.Options == nil {
cfg.Pipelines[i].Sinks[j].Options = make(map[string]any)
}
// Set target for split mode handling
cfg.Pipelines[i].Sinks[j].Options["target"] = consoleTarget
}
}
}
// Also update logging console target if applicable
if cfg.Logging.Console != nil && consoleTarget == "split" {
cfg.Logging.Console.Target = "split"
}
return nil
}