mirror of
https://github.com/yusing/godoxy.git
synced 2026-04-19 06:59:50 +02:00
This is a large-scale refactoring across the codebase that replaces the custom `gperr.Error` type with Go's standard `error` interface. The changes include: - Replacing `gperr.Error` return types with `error` in function signatures - Using `errors.New()` and `fmt.Errorf()` instead of `gperr.New()` and `gperr.Errorf()` - Using `%w` format verb for error wrapping instead of `.With()` method - Replacing `gperr.Subject()` calls with `gperr.PrependSubject()` - Converting error logging from `gperr.Log*()` functions to zerolog's `.Err().Msg()` pattern - Update NewLogger to handle multiline error message - Updating `goutils` submodule to latest commit This refactoring aligns with Go idioms and removes the dependency on custom error handling abstractions in favor of standard library patterns.
156 lines
3.7 KiB
Go
156 lines
3.7 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"
|
|
"github.com/yusing/godoxy/internal/watcher/events"
|
|
gperr "github.com/yusing/goutils/errs"
|
|
"github.com/yusing/goutils/strings/ansi"
|
|
"github.com/yusing/goutils/task"
|
|
)
|
|
|
|
var (
|
|
cfgWatcher watcher.Watcher
|
|
reloadMu sync.Mutex
|
|
)
|
|
|
|
const configEventFlushInterval = 500 * time.Millisecond
|
|
|
|
const (
|
|
cfgRenameWarn = `Config file renamed, not reloading.
|
|
Make sure you rename it back before next time you start.`
|
|
cfgDeleteWarn = `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),
|
|
})
|
|
}
|
|
|
|
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),
|
|
})
|
|
}
|
|
|
|
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
|
|
var criticalErr CriticalError
|
|
if errors.As(initErr, &criticalErr) {
|
|
logNotifyError("init", criticalErr.err)
|
|
return criticalErr.err
|
|
}
|
|
}
|
|
|
|
// 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 {
|
|
// 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() {
|
|
t := task.RootTask("config_watcher", true)
|
|
eventQueue := events.NewEventQueue(
|
|
t,
|
|
configEventFlushInterval,
|
|
OnConfigChange,
|
|
func(err error) {
|
|
logNotifyError("reload", err)
|
|
},
|
|
)
|
|
eventQueue.Start(cfgWatcher.Events(t.Context()))
|
|
}
|
|
|
|
func OnConfigChange(ev []events.Event) {
|
|
// no matter how many events during the interval
|
|
// just reload once and check the last event
|
|
switch ev[len(ev)-1].Action {
|
|
case events.ActionFileRenamed:
|
|
logNotifyWarn("rename", errors.New(cfgRenameWarn))
|
|
return
|
|
case events.ActionFileDeleted:
|
|
logNotifyWarn("delete", errors.New(cfgDeleteWarn))
|
|
return
|
|
}
|
|
|
|
if err := Reload(); err != nil {
|
|
// recovered in event queue
|
|
panic(err)
|
|
}
|
|
}
|