mirror of
https://github.com/yusing/godoxy.git
synced 2026-02-24 03:14:50 +01: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.
6.6 KiB
6.6 KiB
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(¬if.LogMessage{
Level: zerolog.InfoLevel,
Title: "Container Started",
Body: notif.ListBody{
"Container: myapp",
"Status: Running",
},
})
Provider-Specific Notification
notif.Notify(¬if.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(¬if.NotificationConfig{
ProviderName: notif.ProviderWebhook,
Provider: ¬if.Webhook{
URL: "https://hooks.example.com/webhook",
},
})
dispatcher.RegisterProvider(¬if.NotificationConfig{
ProviderName: notif.ProviderGotify,
Provider: ¬if.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 = errors.New("missing notification provider")
ErrInvalidNotifProviderType = errors.New("invalid notification provider type")
ErrUnknownNotifProvider = errors.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