mirror of
https://github.com/yusing/godoxy.git
synced 2026-03-21 00:29:03 +01:00
160 lines
4.1 KiB
Go
160 lines
4.1 KiB
Go
package config
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/rs/zerolog"
|
|
"github.com/rs/zerolog/log"
|
|
"github.com/yusing/godoxy/internal/common"
|
|
config "github.com/yusing/godoxy/internal/config/types"
|
|
"github.com/yusing/godoxy/internal/notif"
|
|
"github.com/yusing/godoxy/internal/watcher"
|
|
watcherEvents "github.com/yusing/godoxy/internal/watcher/events"
|
|
gperr "github.com/yusing/goutils/errs"
|
|
"github.com/yusing/goutils/eventqueue"
|
|
"github.com/yusing/goutils/events"
|
|
"github.com/yusing/goutils/strings/ansi"
|
|
"github.com/yusing/goutils/task"
|
|
)
|
|
|
|
var (
|
|
cfgWatcher watcher.Watcher
|
|
reloadMu sync.Mutex
|
|
)
|
|
|
|
const configEventFlushInterval = 500 * time.Millisecond
|
|
|
|
var (
|
|
errCfgRenameWarn = errors.New("config file renamed, not reloading; Make sure you rename it back before next time you start")
|
|
errCfgDeleteWarn = errors.New(`config file deleted, not reloading; You may run "ls-config" to show or dump the current config`)
|
|
)
|
|
|
|
func logNotifyError(action string, err error) {
|
|
log.Error().Err(err).Msg("config " + action + " error")
|
|
notif.Notify(¬if.LogMessage{
|
|
Level: zerolog.ErrorLevel,
|
|
Title: fmt.Sprintf("Config %s error", action),
|
|
Body: notif.ErrorBody(err),
|
|
})
|
|
events.Global.Add(events.NewEvent(events.LevelError, "config", action, err))
|
|
}
|
|
|
|
func logNotifyWarn(action string, err error) {
|
|
log.Warn().Err(err).Msg("config " + action + " warning")
|
|
notif.Notify(¬if.LogMessage{
|
|
Level: zerolog.WarnLevel,
|
|
Title: fmt.Sprintf("Config %s warning", action),
|
|
Body: notif.ErrorBody(err),
|
|
})
|
|
events.Global.Add(events.NewEvent(events.LevelWarn, "config", action, err))
|
|
}
|
|
|
|
func Load() error {
|
|
if HasState() {
|
|
panic(errors.New("config already loaded"))
|
|
}
|
|
state := NewState()
|
|
config.WorkingState.Store(state)
|
|
|
|
cfgWatcher = watcher.NewConfigFileWatcher(common.ConfigFileName)
|
|
|
|
initErr := state.InitFromFile(common.ConfigPath)
|
|
if initErr != nil {
|
|
// if error is critical, notify and return it without starting providers
|
|
if criticalErr, ok := errors.AsType[CriticalError](initErr); ok {
|
|
logNotifyError("init", criticalErr.err)
|
|
return criticalErr
|
|
}
|
|
}
|
|
|
|
// disable pool logging temporary since we already have pretty logging
|
|
state.Entrypoint().DisablePoolsLog(true)
|
|
defer func() {
|
|
state.Entrypoint().DisablePoolsLog(false)
|
|
}()
|
|
|
|
err := errors.Join(initErr, state.StartProviders())
|
|
if err != nil {
|
|
logNotifyError("init", err)
|
|
}
|
|
|
|
state.StartAPIServers()
|
|
state.StartMetrics()
|
|
|
|
SetState(state)
|
|
|
|
// flush temporary log
|
|
state.FlushTmpLog()
|
|
return nil
|
|
}
|
|
|
|
func Reload() error {
|
|
events.Global.Add(events.NewEvent(events.LevelInfo, "config", "reload", nil))
|
|
|
|
// avoid race between config change and API reload request
|
|
reloadMu.Lock()
|
|
defer reloadMu.Unlock()
|
|
|
|
newState := NewState()
|
|
config.WorkingState.Store(newState)
|
|
|
|
err := newState.InitFromFile(common.ConfigPath)
|
|
if err != nil {
|
|
newState.Task().FinishAndWait(err)
|
|
config.WorkingState.Store(GetState())
|
|
return gperr.Wrap(err, ansi.Warning("using last config"))
|
|
}
|
|
|
|
// flush temporary log
|
|
newState.FlushTmpLog()
|
|
|
|
// cancel all current subtasks -> wait
|
|
// -> replace config -> start new subtasks
|
|
GetState().Task().FinishAndWait(config.ErrConfigChanged)
|
|
SetState(newState)
|
|
|
|
if err := newState.StartProviders(); err != nil {
|
|
logNotifyError("start providers", err)
|
|
return nil // continue
|
|
}
|
|
|
|
newState.StartAPIServers()
|
|
newState.StartMetrics()
|
|
return nil
|
|
}
|
|
|
|
func WatchChanges() {
|
|
opts := eventqueue.Options[watcherEvents.Event]{
|
|
FlushInterval: configEventFlushInterval,
|
|
OnFlush: OnConfigChange,
|
|
OnError: func(err error) {
|
|
logNotifyError("reload", err)
|
|
},
|
|
Debug: common.IsDebug,
|
|
}
|
|
t := task.RootTask("config_watcher", true)
|
|
eventQueue := eventqueue.New(t, opts)
|
|
eventQueue.Start(cfgWatcher.Events(t.Context()))
|
|
}
|
|
|
|
func OnConfigChange(ev []watcherEvents.Event) {
|
|
// no matter how many events during the interval
|
|
// just reload once and check the last event
|
|
switch ev[len(ev)-1].Action {
|
|
case watcherEvents.ActionFileRenamed:
|
|
logNotifyWarn("rename", errCfgRenameWarn)
|
|
return
|
|
case watcherEvents.ActionFileDeleted:
|
|
logNotifyWarn("delete", errCfgDeleteWarn)
|
|
return
|
|
}
|
|
|
|
if err := Reload(); err != nil {
|
|
// recovered in event queue
|
|
panic(err)
|
|
}
|
|
}
|