mirror of
https://github.com/yusing/godoxy.git
synced 2026-04-17 14:09:44 +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.
333 lines
6.6 KiB
Markdown
333 lines
6.6 KiB
Markdown
# 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
|
|
|
|
```mermaid
|
|
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
|
|
|
|
```go
|
|
type Dispatcher struct {
|
|
task *task.Task
|
|
providers *xsync.Map[Provider, struct{}]
|
|
logCh chan *LogMessage
|
|
retryMsg *xsync.Map[*RetryMessage, struct{}]
|
|
retryTicker *time.Ticker
|
|
}
|
|
```
|
|
|
|
### LogMessage
|
|
|
|
```go
|
|
type LogMessage struct {
|
|
Level zerolog.Level
|
|
Title string
|
|
Body LogBody
|
|
Color Color
|
|
To []string // Provider names to target
|
|
}
|
|
|
|
type LogBody []string
|
|
```
|
|
|
|
### Provider Interface
|
|
|
|
```go
|
|
type Provider interface {
|
|
GetName() string
|
|
Send(ctx context.Context, msg *LogMessage) error
|
|
}
|
|
```
|
|
|
|
## Providers
|
|
|
|
### Webhook
|
|
|
|
```go
|
|
type Webhook struct {
|
|
URL string `json:"url"`
|
|
Method string `json:"method"` // default: POST
|
|
}
|
|
```
|
|
|
|
### Gotify
|
|
|
|
```go
|
|
type GotifyClient struct {
|
|
URL string `json:"url"`
|
|
Token string `json:"token"`
|
|
}
|
|
```
|
|
|
|
### Ntfy
|
|
|
|
```go
|
|
type Ntfy struct {
|
|
URL string `json:"url"`
|
|
Topic string `json:"topic"`
|
|
}
|
|
```
|
|
|
|
## Public API
|
|
|
|
### Dispatcher Management
|
|
|
|
```go
|
|
// StartNotifDispatcher initializes the notification dispatcher.
|
|
func StartNotifDispatcher(parent task.Parent) *Dispatcher
|
|
```
|
|
|
|
### Notification
|
|
|
|
```go
|
|
// Notify sends a log message to all providers.
|
|
func Notify(msg *LogMessage)
|
|
```
|
|
|
|
### Dispatcher Methods
|
|
|
|
```go
|
|
// RegisterProvider registers a notification provider.
|
|
func (disp *Dispatcher) RegisterProvider(cfg *NotificationConfig)
|
|
```
|
|
|
|
## Usage
|
|
|
|
### Basic Notification
|
|
|
|
```go
|
|
notif.Notify(¬if.LogMessage{
|
|
Level: zerolog.InfoLevel,
|
|
Title: "Container Started",
|
|
Body: notif.ListBody{
|
|
"Container: myapp",
|
|
"Status: Running",
|
|
},
|
|
})
|
|
```
|
|
|
|
### Provider-Specific Notification
|
|
|
|
```go
|
|
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
|
|
|
|
```go
|
|
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
|
|
|
|
```go
|
|
const (
|
|
retryInterval = time.Second
|
|
maxBackoffDelay = 5 * time.Minute
|
|
backoffMultiplier = 2.0
|
|
)
|
|
```
|
|
|
|
### Backoff Calculation
|
|
|
|
```go
|
|
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
|
|
|
|
```mermaid
|
|
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
|
|
|
|
```go
|
|
type Color uint
|
|
|
|
const (
|
|
ColorDefault Color = iota
|
|
ColorRed
|
|
ColorGreen
|
|
ColorBlue
|
|
ColorYellow
|
|
ColorPurple
|
|
)
|
|
```
|
|
|
|
## Configuration
|
|
|
|
### YAML Configuration
|
|
|
|
```yaml
|
|
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
|
|
|
|
```go
|
|
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:
|
|
|
|
```go
|
|
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
|