204 lines
5.2 KiB
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
|
|
} |