Files
..
2025-10-11 18:13:19 +08:00

Notifications

The notif package provides a notification dispatching system for GoDoxy, supporting multiple providers (Webhook, Gotify, Ntfy) with retry logic and exponential backoff.

Overview

The notif package implements a notification dispatcher that delivers messages to multiple providers with automatic retry handling, provider management, and logging support.

Key Features

  • Multiple notification providers (Webhook, Gotify, Ntfy)
  • Provider registration and management
  • Retry logic with exponential backoff
  • Message queuing with configurable buffer
  • Selective provider targeting
  • Colored message support

Architecture

graph TD
    A[LogMessage] --> B[Dispatcher]
    B --> C{Provider Filter}
    C -->|Matches| D[Send to Provider]
    C -->|No Match| E[Skip]

    D --> F{Send Success?}
    F -->|Yes| G[Log Success]
    F -->|No| H[Retry Queue]

    H --> I{Retry Limit?}
    I -->|No| J[Schedule Retry]
    J --> B
    I -->|Yes| K[Log Failure]

    subgraph Providers
        L[Webhook]
        M[Gotify]
        N[Ntfy]
    end

    D --> L
    D --> M
    D --> N

Core Components

Dispatcher

type Dispatcher struct {
    task        *task.Task
    providers   *xsync.Map[Provider, struct{}]
    logCh       chan *LogMessage
    retryMsg    *xsync.Map[*RetryMessage, struct{}]
    retryTicker *time.Ticker
}

LogMessage

type LogMessage struct {
    Level zerolog.Level
    Title string
    Body  LogBody
    Color Color
    To    []string // Provider names to target
}

type LogBody []string

Provider Interface

type Provider interface {
    GetName() string
    Send(ctx context.Context, msg *LogMessage) error
}

Providers

Webhook

type Webhook struct {
    URL    string `json:"url"`
    Method string `json:"method"` // default: POST
}

Gotify

type GotifyClient struct {
    URL    string `json:"url"`
    Token  string `json:"token"`
}

Ntfy

type Ntfy struct {
    URL   string `json:"url"`
    Topic string `json:"topic"`
}

Public API

Dispatcher Management

// StartNotifDispatcher initializes the notification dispatcher.
func StartNotifDispatcher(parent task.Parent) *Dispatcher

Notification

// Notify sends a log message to all providers.
func Notify(msg *LogMessage)

Dispatcher Methods

// RegisterProvider registers a notification provider.
func (disp *Dispatcher) RegisterProvider(cfg *NotificationConfig)

Usage

Basic Notification

notif.Notify(&notif.LogMessage{
    Level: zerolog.InfoLevel,
    Title: "Container Started",
    Body: notif.ListBody{
        "Container: myapp",
        "Status: Running",
    },
})

Provider-Specific Notification

notif.Notify(&notif.LogMessage{
    Level:    zerolog.WarnLevel,
    Title:    "High Memory Usage",
    Body:     notif.MessageBody("Memory: 85%"),
    Color:    notif.ColorRed,
    To:       []string{"gotify"}, // Only send to provider with name "gotify"
})

Registering Providers

dispatcher := notif.StartNotifDispatcher(parent)

dispatcher.RegisterProvider(&notif.NotificationConfig{
    ProviderName: notif.ProviderWebhook,
    Provider: &notif.Webhook{
        URL: "https://hooks.example.com/webhook",
    },
})

dispatcher.RegisterProvider(&notif.NotificationConfig{
    ProviderName: notif.ProviderGotify,
    Provider: &notif.GotifyClient{
        URL:   "https://gotify.example.com",
        Token: "secret",
    },
})

Retry Logic

Configuration

const (
    retryInterval     = time.Second
    maxBackoffDelay   = 5 * time.Minute
    backoffMultiplier = 2.0
)

Backoff Calculation

func calculateBackoffDelay(trials int) time.Duration {
    if trials == 0 {
        return retryInterval
    }

    delay := min(float64(retryInterval)*math.Pow(backoffMultiplier, float64(trials)),
                 float64(maxBackoffDelay))

    // Add 20% jitter
    jitter := delay * 0.2 * (rand.Float64() - 0.5)
    return time.Duration(delay + jitter)
}

Retry Schedule

Trial Delay Jitter Total (approx)
0 1s +/- 100ms 0.9-1.1s
1 2s +/- 200ms 1.8-2.2s
2 4s +/- 400ms 3.6-4.4s
3 8s +/- 800ms 7.2-8.8s
4 16s +/- 1.6s 14.4-17.6s
5 32s +/- 3.2s 28.8-35.2s
... max 5m +/- 30s 4.5-5.5m

Data Flow

sequenceDiagram
    participant App
    participant Dispatcher
    participant Queue
    participant Provider
    participant RetryScheduler

    App->>Dispatcher: Notify(LogMessage)
    Dispatcher->>Dispatcher: Buffer Message
    Dispatcher->>Dispatcher: Dispatch Async

    par Parallel Provider Delivery
        Dispatcher->>Provider: Send()
        alt Success
            Provider-->>Dispatcher: nil
            Dispatcher->>Dispatcher: Log Success
        else Failure
            Provider-->>Dispatcher: Error
            Dispatcher->>RetryScheduler: Schedule Retry
        end
    end

    loop Retry Ticker
        RetryScheduler->>Dispatcher: Process Retries
        Dispatcher->>Provider: Retry Send
    end

Message Colors

type Color uint

const (
    ColorDefault Color = iota
    ColorRed
    ColorGreen
    ColorBlue
    ColorYellow
    ColorPurple
)

Configuration

YAML Configuration

providers:
  notification:
    - provider: webhook
      url: https://hooks.example.com/webhook
      method: POST

    - provider: gotify
      url: https://gotify.example.com
      token: your-token

    - provider: ntfy
      url: https://ntfy.example.com
      topic: godoxy

Integration Points

The notif package integrates with:

  • ACL: Blocked access notifications
  • Route: Route status changes
  • Idlewatcher: Container idle/alive notifications
  • Health: Health check alerts
  • Autocert: Certificate expiration warnings

Error Handling

Provider Errors

var (
    ErrMissingNotifProvider     = gperr.New("missing notification provider")
    ErrInvalidNotifProviderType = gperr.New("invalid notification provider type")
    ErrUnknownNotifProvider     = gperr.New("unknown notification provider")
)

Retry Limits

Retry limits depend on message level:

var maxRetries = map[zerolog.Level]int{
    zerolog.ErrorLevel:   3,
    zerolog.WarnLevel:    2,
    zerolog.InfoLevel:    1,
    zerolog.DebugLevel:   0,
}

Performance Considerations

  • Buffered channel with 100 message capacity
  • Non-blocking sends to provider
  • Batched retry processing
  • Provider filtering reduces unnecessary calls
  • Exponential backoff prevents thundering herd