mirror of
https://github.com/yusing/godoxy.git
synced 2026-04-25 02:09:01 +02:00
migrated from logrus to zerolog, improved error formatting, fixed concurrent map write, fixed crash on rapid page refresh for idle containers, fixed infinite recursion on gotfiy error, fixed websocket connection problem when using idlewatcher
This commit is contained in:
@@ -1,10 +1,10 @@
|
||||
package watcher
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
|
||||
"github.com/yusing/go-proxy/internal/common"
|
||||
"github.com/yusing/go-proxy/internal/task"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -17,7 +17,7 @@ func NewConfigFileWatcher(filename string) Watcher {
|
||||
configDirWatcherMu.Lock()
|
||||
defer configDirWatcherMu.Unlock()
|
||||
if configDirWatcher == nil {
|
||||
configDirWatcher = NewDirectoryWatcher(context.Background(), common.ConfigBasePath)
|
||||
configDirWatcher = NewDirectoryWatcher(task.GlobalTask("config watcher"), common.ConfigBasePath)
|
||||
}
|
||||
return configDirWatcher.Add(filename)
|
||||
}
|
||||
|
||||
@@ -7,13 +7,16 @@ import (
|
||||
"sync"
|
||||
|
||||
"github.com/fsnotify/fsnotify"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/rs/zerolog"
|
||||
E "github.com/yusing/go-proxy/internal/error"
|
||||
"github.com/yusing/go-proxy/internal/task"
|
||||
F "github.com/yusing/go-proxy/internal/utils/functional"
|
||||
"github.com/yusing/go-proxy/internal/watcher/events"
|
||||
)
|
||||
|
||||
type DirWatcher struct {
|
||||
zerolog.Logger
|
||||
|
||||
dir string
|
||||
w *fsnotify.Watcher
|
||||
|
||||
@@ -23,7 +26,7 @@ type DirWatcher struct {
|
||||
eventCh chan Event
|
||||
errCh chan E.Error
|
||||
|
||||
ctx context.Context
|
||||
task task.Task
|
||||
}
|
||||
|
||||
// NewDirectoryWatcher returns a DirWatcher instance.
|
||||
@@ -34,22 +37,26 @@ type DirWatcher struct {
|
||||
//
|
||||
// Note that the returned DirWatcher is not ready to use until the goroutine
|
||||
// started by NewDirectoryWatcher has finished.
|
||||
func NewDirectoryWatcher(ctx context.Context, dirPath string) *DirWatcher {
|
||||
func NewDirectoryWatcher(callerSubtask task.Task, dirPath string) *DirWatcher {
|
||||
//! subdirectories are not watched
|
||||
w, err := fsnotify.NewWatcher()
|
||||
if err != nil {
|
||||
logrus.Panicf("unable to create fs watcher: %s", err)
|
||||
logger.Panic().Err(err).Msg("unable to create fs watcher")
|
||||
}
|
||||
if err = w.Add(dirPath); err != nil {
|
||||
logrus.Panicf("unable to create fs watcher: %s", err)
|
||||
logger.Panic().Err(err).Msg("unable to create fs watcher")
|
||||
}
|
||||
helper := &DirWatcher{
|
||||
Logger: logger.With().
|
||||
Str("type", "dir").
|
||||
Str("path", dirPath).
|
||||
Logger(),
|
||||
dir: dirPath,
|
||||
w: w,
|
||||
fwMap: F.NewMapOf[string, *fileWatcher](),
|
||||
eventCh: make(chan Event),
|
||||
errCh: make(chan E.Error),
|
||||
ctx: ctx,
|
||||
task: callerSubtask,
|
||||
}
|
||||
go helper.start()
|
||||
return helper
|
||||
@@ -73,14 +80,10 @@ func (h *DirWatcher) Add(relPath string) Watcher {
|
||||
eventCh: make(chan Event),
|
||||
errCh: make(chan E.Error),
|
||||
}
|
||||
go func() {
|
||||
defer func() {
|
||||
close(s.eventCh)
|
||||
close(s.errCh)
|
||||
}()
|
||||
<-h.ctx.Done()
|
||||
logrus.Debugf("file watcher %s stopped", relPath)
|
||||
}()
|
||||
h.task.OnFinished("close file watcher for "+relPath, func() {
|
||||
close(s.eventCh)
|
||||
close(s.errCh)
|
||||
})
|
||||
h.fwMap.Store(relPath, s)
|
||||
return s
|
||||
}
|
||||
@@ -88,11 +91,10 @@ func (h *DirWatcher) Add(relPath string) Watcher {
|
||||
func (h *DirWatcher) start() {
|
||||
defer close(h.eventCh)
|
||||
defer h.w.Close()
|
||||
defer logrus.Debugf("directory watcher %s stopped", h.dir)
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-h.ctx.Done():
|
||||
case <-h.task.Context().Done():
|
||||
return
|
||||
case fsEvent, ok := <-h.w.Events:
|
||||
if !ok {
|
||||
@@ -122,9 +124,9 @@ func (h *DirWatcher) start() {
|
||||
// send event to directory watcher
|
||||
select {
|
||||
case h.eventCh <- msg:
|
||||
logrus.Debugf("sent event to directory watcher %s", h.dir)
|
||||
h.Debug().Msg("sent event to directory watcher")
|
||||
default:
|
||||
logrus.Debugf("failed to send event to directory watcher %s", h.dir)
|
||||
h.Debug().Msg("failed to send event to directory watcher")
|
||||
}
|
||||
|
||||
// send event to file watcher too
|
||||
@@ -132,12 +134,12 @@ func (h *DirWatcher) start() {
|
||||
if ok {
|
||||
select {
|
||||
case w.eventCh <- msg:
|
||||
logrus.Debugf("sent event to file watcher %s", relPath)
|
||||
h.Debug().Msg("sent event to file watcher " + relPath)
|
||||
default:
|
||||
logrus.Debugf("failed to send event to file watcher %s", relPath)
|
||||
h.Debug().Msg("failed to send event to file watcher " + relPath)
|
||||
}
|
||||
} else {
|
||||
logrus.Debugf("file watcher not found: %s", relPath)
|
||||
h.Debug().Msg("file watcher not found: " + relPath)
|
||||
}
|
||||
case err := <-h.w.Errors:
|
||||
if errors.Is(err, fsnotify.ErrClosed) {
|
||||
|
||||
@@ -2,12 +2,11 @@ package watcher
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
docker_events "github.com/docker/docker/api/types/events"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/rs/zerolog"
|
||||
D "github.com/yusing/go-proxy/internal/docker"
|
||||
E "github.com/yusing/go-proxy/internal/error"
|
||||
"github.com/yusing/go-proxy/internal/watcher/events"
|
||||
@@ -15,10 +14,11 @@ import (
|
||||
|
||||
type (
|
||||
DockerWatcher struct {
|
||||
zerolog.Logger
|
||||
|
||||
host string
|
||||
client D.Client
|
||||
clientOwned bool
|
||||
logrus.FieldLogger
|
||||
}
|
||||
DockerListOptions = docker_events.ListOptions
|
||||
)
|
||||
@@ -47,7 +47,7 @@ var (
|
||||
dockerWatcherRetryInterval = 3 * time.Second
|
||||
)
|
||||
|
||||
func DockerrFilterContainer(nameOrID string) filters.KeyValuePair {
|
||||
func DockerFilterContainerNameID(nameOrID string) filters.KeyValuePair {
|
||||
return filters.Arg("container", nameOrID)
|
||||
}
|
||||
|
||||
@@ -55,18 +55,21 @@ func NewDockerWatcher(host string) DockerWatcher {
|
||||
return DockerWatcher{
|
||||
host: host,
|
||||
clientOwned: true,
|
||||
FieldLogger: (logrus.
|
||||
WithField("module", "docker_watcher").
|
||||
WithField("host", host)),
|
||||
Logger: logger.With().
|
||||
Str("type", "docker").
|
||||
Str("host", host).
|
||||
Logger(),
|
||||
}
|
||||
}
|
||||
|
||||
func NewDockerWatcherWithClient(client D.Client) DockerWatcher {
|
||||
return DockerWatcher{
|
||||
client: client,
|
||||
FieldLogger: (logrus.
|
||||
WithField("module", "docker_watcher").
|
||||
WithField("host", client.DaemonHost()))}
|
||||
Logger: logger.With().
|
||||
Str("type", "docker").
|
||||
Str("host", client.DaemonHost()).
|
||||
Logger(),
|
||||
}
|
||||
}
|
||||
|
||||
func (w DockerWatcher) Events(ctx context.Context) (<-chan Event, <-chan E.Error) {
|
||||
@@ -88,7 +91,7 @@ func (w DockerWatcher) EventsWithOptions(ctx context.Context, options DockerList
|
||||
}()
|
||||
|
||||
if !w.client.Connected() {
|
||||
var err E.Error
|
||||
var err error
|
||||
attempts := 0
|
||||
for {
|
||||
w.client, err = D.ConnectClient(w.host)
|
||||
@@ -96,7 +99,7 @@ func (w DockerWatcher) EventsWithOptions(ctx context.Context, options DockerList
|
||||
break
|
||||
}
|
||||
attempts++
|
||||
errCh <- E.FailWith(fmt.Sprintf("docker connection attempt #%d", attempts), err)
|
||||
errCh <- E.Errorf("docker connection attempt #%d: %w", attempts, err)
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
@@ -113,14 +116,14 @@ func (w DockerWatcher) EventsWithOptions(ctx context.Context, options DockerList
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
if err := E.From(ctx.Err()); err != nil && err.IsNot(context.Canceled) {
|
||||
if err := E.From(ctx.Err()); err != nil && !err.Is(context.Canceled) {
|
||||
errCh <- err
|
||||
}
|
||||
return
|
||||
case msg := <-cEventCh:
|
||||
action, ok := events.DockerEventMap[msg.Action]
|
||||
if !ok {
|
||||
w.Debugf("ignored unknown docker event: %s for container %s", msg.Action, msg.Actor.Attributes["name"])
|
||||
w.Debug().Msgf("ignored unknown docker event: %s for container %s", msg.Action, msg.Actor.Attributes["name"])
|
||||
continue
|
||||
}
|
||||
event := Event{
|
||||
|
||||
@@ -65,7 +65,7 @@ func (e *EventQueue) Start(eventCh <-chan Event, errCh <-chan E.Error) {
|
||||
go func() {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
e.onError(E.PanicRecv("onFlush: %s", err).Subject(e.task.Parent().Name()))
|
||||
e.onError(E.Errorf("recovered panic in onFlush: %v", err).Subject(e.task.Parent().String()))
|
||||
}
|
||||
}()
|
||||
e.onFlush(flushTask, queue)
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/yusing/go-proxy/internal/net/types"
|
||||
U "github.com/yusing/go-proxy/internal/utils"
|
||||
"github.com/yusing/go-proxy/internal/utils/strutils"
|
||||
)
|
||||
|
||||
type JSONRepresentation struct {
|
||||
@@ -27,10 +27,10 @@ func (jsonRepr *JSONRepresentation) MarshalJSON() ([]byte, error) {
|
||||
"name": jsonRepr.Name,
|
||||
"config": jsonRepr.Config,
|
||||
"started": jsonRepr.Started.Unix(),
|
||||
"startedStr": U.FormatTime(jsonRepr.Started),
|
||||
"startedStr": strutils.FormatTime(jsonRepr.Started),
|
||||
"status": jsonRepr.Status.String(),
|
||||
"uptime": jsonRepr.Uptime.Seconds(),
|
||||
"uptimeStr": U.FormatDuration(jsonRepr.Uptime),
|
||||
"uptimeStr": strutils.FormatDuration(jsonRepr.Uptime),
|
||||
"url": url,
|
||||
"extra": jsonRepr.Extra,
|
||||
})
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package health
|
||||
|
||||
import "github.com/sirupsen/logrus"
|
||||
import (
|
||||
"github.com/yusing/go-proxy/internal/logging"
|
||||
)
|
||||
|
||||
var logger = logrus.WithField("module", "health_mon")
|
||||
var logger = logging.With().Str("module", "health_mon").Logger()
|
||||
|
||||
@@ -3,10 +3,14 @@ package health
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
E "github.com/yusing/go-proxy/internal/error"
|
||||
"github.com/yusing/go-proxy/internal/logging"
|
||||
"github.com/yusing/go-proxy/internal/net/types"
|
||||
"github.com/yusing/go-proxy/internal/notif"
|
||||
"github.com/yusing/go-proxy/internal/task"
|
||||
U "github.com/yusing/go-proxy/internal/utils"
|
||||
F "github.com/yusing/go-proxy/internal/utils/functional"
|
||||
@@ -29,6 +33,10 @@ type (
|
||||
|
||||
var monMap = F.NewMapOf[string, HealthMonitor]()
|
||||
|
||||
var (
|
||||
ErrNegativeInterval = errors.New("negative interval")
|
||||
)
|
||||
|
||||
func newMonitor(url types.URL, config *HealthCheckConfig, healthCheckFunc HealthCheckFunc) *monitor {
|
||||
mon := &monitor{
|
||||
config: config,
|
||||
@@ -59,10 +67,12 @@ func (mon *monitor) Start(routeSubtask task.Task) E.Error {
|
||||
mon.task = routeSubtask
|
||||
|
||||
if mon.config.Interval <= 0 {
|
||||
return E.Invalid("interval", mon.config.Interval)
|
||||
return E.From(ErrNegativeInterval)
|
||||
}
|
||||
|
||||
go func() {
|
||||
logger := logging.With().Str("name", mon.service).Logger()
|
||||
|
||||
defer func() {
|
||||
if mon.status.Load() != StatusError {
|
||||
mon.status.Store(StatusUnknown)
|
||||
@@ -70,7 +80,7 @@ func (mon *monitor) Start(routeSubtask task.Task) E.Error {
|
||||
}()
|
||||
|
||||
if err := mon.checkUpdateHealth(); err != nil {
|
||||
logger.Errorf("healthchecker %s failure: %s", mon.service, err)
|
||||
logger.Err(err).Msg("healthchecker failure")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -87,7 +97,7 @@ func (mon *monitor) Start(routeSubtask task.Task) E.Error {
|
||||
case <-ticker.C:
|
||||
err := mon.checkUpdateHealth()
|
||||
if err != nil {
|
||||
logger.Errorf("healthchecker %s failure: %s", mon.service, err)
|
||||
logger.Err(err).Msg("healthchecker failure")
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -128,10 +138,8 @@ func (mon *monitor) Uptime() time.Duration {
|
||||
|
||||
// Name implements HealthMonitor.
|
||||
func (mon *monitor) Name() string {
|
||||
if mon.task == nil {
|
||||
return ""
|
||||
}
|
||||
return mon.task.Name()
|
||||
parts := strings.Split(mon.service, "/")
|
||||
return parts[len(parts)-1]
|
||||
}
|
||||
|
||||
// String implements fmt.Stringer of HealthMonitor.
|
||||
@@ -151,13 +159,14 @@ func (mon *monitor) MarshalJSON() ([]byte, error) {
|
||||
}).MarshalJSON()
|
||||
}
|
||||
|
||||
func (mon *monitor) checkUpdateHealth() E.Error {
|
||||
func (mon *monitor) checkUpdateHealth() error {
|
||||
logger := logging.With().Str("name", mon.Name()).Logger()
|
||||
healthy, detail, err := mon.checkHealth()
|
||||
if err != nil {
|
||||
defer mon.task.Finish(err)
|
||||
mon.status.Store(StatusError)
|
||||
if !errors.Is(err, context.Canceled) {
|
||||
return E.Failure("check health").With(err)
|
||||
return fmt.Errorf("check health: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -169,9 +178,12 @@ func (mon *monitor) checkUpdateHealth() E.Error {
|
||||
}
|
||||
if healthy != (mon.status.Swap(status) == StatusHealthy) {
|
||||
if healthy {
|
||||
logger.Infof("%s is up", mon.service)
|
||||
logger.Info().Msg("server is up")
|
||||
notif.Notify(mon.service, "server is up")
|
||||
} else {
|
||||
logger.Warnf("%s is down: %s", mon.service, detail)
|
||||
logger.Warn().Msg("server is down")
|
||||
logger.Debug().Msg(detail)
|
||||
notif.Notify(mon.service, "server is down")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
5
internal/watcher/logger.go
Normal file
5
internal/watcher/logger.go
Normal file
@@ -0,0 +1,5 @@
|
||||
package watcher
|
||||
|
||||
import "github.com/yusing/go-proxy/internal/logging"
|
||||
|
||||
var logger = logging.With().Str("module", "watcher").Logger()
|
||||
Reference in New Issue
Block a user