Confy supports watching configuration sources for changes and automatically reloading values. File sources use fsnotify for filesystem events, while other sources poll at configurable intervals.
Quick Start01
// Enable watching on the file source
source, _ := sources.NewFileSource("config.yaml", sources.FileSourceOptions{
WatchEnabled: true,
})
cfg.LoadFrom(source)
// Register a change callback
cfg.WatchChanges(func(change confy.ConfigChange) {
log.Printf("config changed: %s = %v (was %v)",
change.Key, change.NewValue, change.OldValue)
})
// Start watching (blocks until context is cancelled)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
cfg.Watch(ctx)Change Callbacks02
Watch All Changes
WatchChanges receives every configuration change from any source:
cfg.WatchChanges(func(change confy.ConfigChange) {
switch change.Type {
case confy.ChangeTypeSet:
log.Printf("new key: %s = %v", change.Key, change.NewValue)
case confy.ChangeTypeUpdate:
log.Printf("updated: %s = %v → %v", change.Key, change.OldValue, change.NewValue)
case confy.ChangeTypeDelete:
log.Printf("deleted: %s (was %v)", change.Key, change.OldValue)
case confy.ChangeTypeReload:
log.Printf("full reload triggered")
}
})Watch Specific Keys
WatchWithCallback monitors a specific key:
cfg.WatchWithCallback("log.level", func(key string, value any) {
newLevel := value.(string)
logger.SetLevel(newLevel)
log.Printf("Log level changed to: %s", newLevel)
})
cfg.WatchWithCallback("database.host", func(key string, value any) {
newHost := value.(string)
reconnectDatabase(newHost)
})Change Types03
| Type | Constant | Description |
| Set | ChangeTypeSet | New key added |
| Update | ChangeTypeUpdate | Existing key changed |
| Delete | ChangeTypeDelete | Key removed |
| Reload | ChangeTypeReload | Full configuration reloaded |
ConfigChange Structure04
type ConfigChange struct {
Key string // dot-separated key path
OldValue any // previous value (nil for new keys)
NewValue any // new value (nil for deleted keys)
Type ChangeType // set, update, delete, or reload
}Watcher Configuration05
For advanced control, create a standalone watcher:
watcher := confy.NewWatcher(confy.WatcherConfig{
Interval: 5 * time.Second, // polling interval
BufferSize: 100, // event buffer size
MaxRetries: 3, // retry attempts on error
RetryInterval: 5 * time.Second, // delay between retries
EnableDebounce: true, // debounce rapid changes
DebounceTime: 500 * time.Millisecond,
})Watcher Configuration Options
| Option | Type | Default | Description |
Interval | time.Duration | 5s | Polling interval |
BufferSize | int | 100 | Event buffer size |
MaxRetries | int | 3 | Retry limit for errors |
RetryInterval | time.Duration | 5s | Delay between retries |
EnableDebounce | bool | false | Debounce rapid changes |
DebounceTime | time.Duration | 500ms | Debounce window |
Watch Events06
The standalone watcher emits WatchEvent structs with metadata:
type WatchEvent struct {
SourceName string
EventType WatchEventType
Data map[string]any
Timestamp time.Time
Checksum string
Error error
}Event Types
| Type | Description |
WatchEventTypeChange | Source data changed |
WatchEventTypeCreate | Source created |
WatchEventTypeDelete | Source deleted |
WatchEventTypeError | Error occurred |
WatchEventTypeReload | Full reload |
Standalone Watcher07
Use the standalone watcher for direct source management:
watcher := confy.NewWatcher(confy.WatcherConfig{
Interval: 10 * time.Second,
})
// Watch a specific source
err := watcher.WatchSource(ctx, fileSource, func(name string, data map[string]any) {
log.Printf("Source %s changed", name)
// Handle new data
})
// Get watched source names
sources := watcher.GetWatchedSources()
// Get statistics
stats := watcher.GetAllStats()
for name, stat := range stats {
log.Printf("Source %s: %d changes, last at %v", name, stat.ChangeCount, stat.LastChange)
}
// Stop watching a specific source
watcher.StopWatching("config.yaml")
// Stop all watches
watcher.StopAll()Automatic Reload08
Enable automatic reload when any source changes:
cfg := confy.New(
confy.WithReloadOnChange(true),
confy.WithWatchInterval(10 * time.Second),
)With ReloadOnChange enabled, the configuration is automatically merged when any source emits a change event. You can still register WatchChanges callbacks to react to changes.
Dynamic Reconfiguration Pattern09
A common pattern for hot-reloading production applications:
func main() {
cfg := confy.New(
confy.WithReloadOnChange(true),
)
source, _ := sources.NewFileSource("config.yaml", sources.FileSourceOptions{
WatchEnabled: true,
})
cfg.LoadFrom(source)
// React to specific changes
cfg.WatchWithCallback("log.level", func(key string, value any) {
setLogLevel(value.(string))
})
cfg.WatchWithCallback("rate_limit.rps", func(key string, value any) {
updateRateLimiter(cfg.GetInt("rate_limit.rps", 100))
})
// Start watching in background
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
go cfg.Watch(ctx)
// Application runs with hot-reloadable config
startServer(cfg)
}When using ReloadOnChange, ensure your application code is safe for concurrent config reads. All confy getter methods are thread-safe.