package config import ( "context" "errors" "reflect" "sync" "time" "git.yuntuo.hk/k2safe/go-omni/logx" ) // LoadFile loads and validates framework configuration using explicit file references. func LoadFile(path string) (Config, error) { return LoadProject(path, Default()) } // Reloader only accepts log.level changes. Static changes and invalid files // leave the current snapshot and logger untouched. Watch does not own a goroutine. type Reloader struct { mu sync.Mutex path string current Config logger *logx.Logger } func NewReloader(path string, logger *logx.Logger) (*Reloader, error) { cfg, err := LoadFile(path) if err != nil { return nil, err } if err = logger.SetLevel(cfg.Log.Level); err != nil { return nil, err } return &Reloader{path: path, current: cfg, logger: logger}, nil } func (r *Reloader) Reload() error { r.mu.Lock() defer r.mu.Unlock() next, err := LoadFile(r.path) if err != nil { return err } comparable := next comparable.Log.Level = r.current.Log.Level if !reflect.DeepEqual(comparable, r.current) { return errors.New("config: static fields changed; restart required") } if err = r.logger.SetLevel(next.Log.Level); err != nil { return err } r.current = next return nil } func (r *Reloader) Snapshot() Config { r.mu.Lock() defer r.mu.Unlock() cfg := r.current cfg.Response = r.current.Response.Clone() cfg.Errors = r.current.Errors.Clone() cfg.TrustedProxies = append([]string(nil), cfg.TrustedProxies...) cfg.Log.RedactKeys = append([]string(nil), cfg.Log.RedactKeys...) return cfg } func (r *Reloader) Watch(ctx context.Context, interval time.Duration, onError func(error)) error { if ctx == nil || interval < 100*time.Millisecond { return errors.New("config: context and interval >=100ms required") } ticker := time.NewTicker(interval) defer ticker.Stop() for { select { case <-ctx.Done(): return ctx.Err() case <-ticker.C: if err := r.Reload(); err != nil && onError != nil { onError(err) } } } }