Skip to content

Watcher ​

Provides file and Docker event watching capabilities for GoDoxy, enabling dynamic configuration updates.

Overview ​

The watcher package implements event watching systems for detecting changes in configuration files and Docker containers. It provides a unified interface for event streaming that allows GoDoxy to react to infrastructure changes in real-time.

Primary Consumers ​

  • internal/route - Route provider reloads configuration on container events
  • internal/config - Configuration file monitoring
  • internal/idlewatcher - Container idle state detection

Non-goals ​

  • Does not provide a general-purpose filesystem watcher beyond configuration files
  • Does not implement container orchestration or management operations
  • Does not persist events beyond in-memory buffering

Stability ​

Internal package. Public interfaces are stable, but implementation details may change.

Public API ​

Exported Types ​

Event Type ​

go
type Event = events.Event

Alias to events.Event for convenience.

Watcher Interface ​

go
type Watcher interface {
    // Events returns channels for receiving events and errors.
    // The channels are closed when the context is cancelled.
    Events(ctx context.Context) (<-chan Event, <-chan error)
}

Core interface that all watchers implement. Callers receive:

  • <-chan Event - Events as they occur
  • <-chan error - Errors during event watching

Docker Watcher ​

Constructor ​

go
func NewDockerWatcher(dockerCfg types.DockerProviderConfig) DockerWatcher

Creates a Docker watcher for the given Docker configuration.

Event Streaming ​

go
func (w DockerWatcher) Events(ctx context.Context) (<-chan Event, <-chan error)
func (w DockerWatcher) EventsWithOptions(ctx context.Context, options DockerListOptions) (<-chan Event, <-chan error)

Returns event and error channels. Events uses default filters; EventsWithOptions allows custom filters.

Predefined Filters ​

go
var (
    DockerFilterContainer = NewDockerFilter("type", string(dockerEvents.ContainerEventType))
    DockerFilterStart     = NewDockerFilter("event", string(dockerEvents.ActionStart))
    DockerFilterStop      = NewDockerFilter("event", string(dockerEvents.ActionStop))
    DockerFilterDie       = NewDockerFilter("event", string(dockerEvents.ActionDie))
    DockerFilterDestroy   = NewDockerFilter("event", string(dockerEvents.ActionDestroy))
    DockerFilterKill      = NewDockerFilter("event", string(dockerEvents.ActionKill))
    DockerFilterPause     = NewDockerFilter("event", string(dockerEvents.ActionPause))
    DockerFilterUnpause   = NewDockerFilter("event", string(dockerEvents.ActionUnPause))
)

Common filters for Docker event watching.

Filter Builder ​

go
func NewDockerFilter(term string, values ...string) DockerFilter
func NewDockerFilters(filters ...DockerFilter) client.Filters
func DockerFilterContainerNameID(nameOrID string) DockerFilter

Create custom event filters.

Architecture ​

Core Components ​

ComponentResponsibility
WatcherInterface defining event streaming contract
DockerWatcherConnects to Docker daemon and streams container events
fileWatcherWatches individual files for changes
ConfigFileWatcherWatches configuration files for reloads
DirectoryWatcherWatches directories for file changes

Event Flow ​

Docker Event Processing ​

Events are parsed and mapped to watcher event types:

go
func (w DockerWatcher) handleEvent(event dockerEvents.Message, ch chan<- Event) {
    action, ok := events.DockerEventMap[event.Action]
    if !ok {
        return // Unknown action, ignore
    }
    ch <- Event{
        Type:            events.EventTypeDocker,
        ActorID:         event.Actor.ID,
        ActorAttributes: event.Actor.Attributes,
        ActorName:       event.Actor.Attributes["name"],
        Action:          action,
    }
}

Error Handling and Recovery ​

On connection errors:

  1. Error is emitted to error channel
  2. Force reload event is sent to clear routes
  3. 3-second retry interval begins
  4. On success, force reload triggers route reload
  5. Event channel is reopened

Configuration Surface ​

Docker watcher is configured via types.DockerProviderConfig:

  • Host - Docker daemon socket/URL
  • Timeout - Connection timeout

Dependency and Integration Map ​

Internal Dependencies ​

PackagePurpose
internal/dockerDocker client management
internal/watcher/eventsEvent type definitions (Event, Action)
internal/typesConfiguration types
github.com/yusing/goutils/taskLifetime management

External Dependencies ​

DependencyPurpose
github.com/moby/mobyDocker API types and client
github.com/rs/zerologStructured logging
github.com/yusing/goutils/errsError handling

Integration Points ​

  • Events channel feeds into goutils/eventqueue.EventQueue for buffering
  • Route provider subscribes to events for configuration reloads

Observability ​

Logs ​

LevelMessage PatternWhen
DebugDocker watcher opened/closedConnection lifecycle
Errordocker watcher: failed to initialize clientClient creation failure
Debugdocker watcher: connection failedReconnection attempts

Metrics ​

None exposed directly. Polling metrics are tracked by the period package for uptime monitoring.

Failure Modes and Recovery ​

FailureDetectionRecovery
Docker daemon unavailableclient.IsErrConnectionFailedAuto-retry every 3s
Context cancellation<-ctx.Done()Graceful shutdown
Unknown event actionsMissing in DockerEventMapEvent ignored (no error)
Channel blocked writing3-second timeout on notificationEvent dropped, continue

Panic Recovery ​

The Docker watcher does not recover from panics in the event loop. Panic will terminate the process.

Usage Examples ​

Basic Docker Event Watching ​

go
import (
    "context"
    "github.com/yusing/godoxy/internal/watcher"
    "github.com/yusing/godoxy/internal/types"
)

func watchContainers(ctx context.Context, dockerCfg types.DockerProviderConfig) error {
    dw := watcher.NewDockerWatcher(dockerCfg)
    eventCh, errCh := dw.Events(ctx)

    for {
        select {
        case <-ctx.Done():
            return nil
        case event := <-eventCh:
            handleContainerEvent(event)
        case err := <-errCh:
            log.Error().Err(err).Msg("docker watcher error")
        }
    }
}

func handleContainerEvent(event watcher.Event) {
    switch event.Action {
    case events.ActionContainerStart:
        log.Info().Str("container", event.ActorName).Msg("Container started")
    case events.ActionContainerStop, events.ActionContainerDie:
        log.Info().Str("container", event.ActorName).Msg("Container stopped")
    case events.ActionContainerDestroy:
        log.Info().Str("container", event.ActorName).Msg("Container destroyed")
    }
}

Custom Event Filters ​

go
import "github.com/yusing/godoxy/internal/watcher"

options := watcher.DockerListOptions{
    Filters: watcher.NewDockerFilters(
        watcher.DockerFilterContainer,
        watcher.DockerFilterStart,
        watcher.DockerFilterContainerNameID("my-app"),
    ),
}

dw := watcher.NewDockerWatcher(cfg)
eventCh, errCh := dw.EventsWithOptions(ctx, options)

Testing Notes ​

  • Mock Docker client via internal/docker package
  • Event queue can be tested with synthetic events
  • Connection failures tested via client.IsErrConnectionFailed simulation
  • internal/watcher/events - Event type definitions (Event, Action, EventType)
  • goutils/eventqueue - Generic buffered event queue
  • internal/docker - Docker client management
  • internal/route/routes - Route management

Released under the MIT License.