M README.md => README.md +9 -0
@@ 60,6 60,15 @@ ntfy {
user user
password pass
}
+
+# When the alert-mode is set to single, ntfy-alertmanager will cache each single alert
+# to avoid sending recurrences.
+cache {
+ # How long messages stay in the cache for
+ duration 24h
+ # Interval in which the cache is cleaned up
+ cleanup-interval 1h
+}
```
### Alertmanager config
M cache.go => cache.go +7 -5
@@ 12,16 12,18 @@ type cachedAlert struct {
}
type cache struct {
- mu sync.Mutex
- alerts map[fingerprint]*cachedAlert
+ mu sync.Mutex
+ duration time.Duration
+ alerts map[fingerprint]*cachedAlert
}
func (a *cachedAlert) expired() bool {
return a.expires.Before(time.Now())
}
-func newCache() *cache {
+func newCache(d time.Duration) *cache {
c := new(cache)
+ c.duration = d
c.alerts = make(map[fingerprint]*cachedAlert)
return c
@@ 37,11 39,11 @@ func (c *cache) cleanup() {
}
}
-func (c *cache) set(f fingerprint, d time.Duration) {
+func (c *cache) set(f fingerprint) {
c.mu.Lock()
defer c.mu.Unlock()
alert := new(cachedAlert)
- alert.expires = time.Now().Add(d)
+ alert.expires = time.Now().Add(c.duration)
c.alerts[f] = alert
}
M config.go => config.go +44 -0
@@ 3,6 3,7 @@ package main
import (
"fmt"
"strings"
+ "time"
"git.sr.ht/~emersion/go-scfg"
)
@@ 22,6 23,7 @@ type config struct {
Password string
ntfy ntfyConfig
labels labels
+ cache cacheConfig
}
type ntfyConfig struct {
@@ 40,6 42,11 @@ type labelConfig struct {
Tags []string
}
+type cacheConfig struct {
+ CleanupInterval time.Duration
+ Duration time.Duration
+}
+
func readConfig(path string) (*config, error) {
cfg, err := scfg.Load(path)
if err != nil {
@@ 52,6 59,9 @@ func readConfig(path string) (*config, error) {
config.LogLevel = "info"
config.alertMode = single
+ config.cache.CleanupInterval = time.Hour
+ config.cache.Duration = time.Hour * 24
+
d := cfg.Get("log-level")
if d != nil {
if err := d.ParseParams(&config.LogLevel); err != nil {
@@ 172,5 182,39 @@ func readConfig(path string) (*config, error) {
}
}
+ cacheDir := cfg.Get("cache")
+
+ if cacheDir != nil {
+ var durationString string
+ d = cacheDir.Children.Get("duration")
+ if d != nil {
+ if err := d.ParseParams(&durationString); err != nil {
+ return nil, err
+ }
+
+ duration, err := time.ParseDuration(durationString)
+ if err != nil {
+ return nil, err
+ }
+
+ config.cache.Duration = duration
+ }
+
+ var cleanupIntervalString string
+ d = cacheDir.Children.Get("cleanup-interval")
+ if d != nil {
+ if err := d.ParseParams(&cleanupIntervalString); err != nil {
+ return nil, err
+ }
+
+ interval, err := time.ParseDuration(cleanupIntervalString)
+ if err != nil {
+ return nil, err
+ }
+
+ config.cache.CleanupInterval = interval
+ }
+ }
+
return config, nil
}
M config_test.go => config_test.go +15 -1
@@ 5,6 5,7 @@ import (
"path/filepath"
"reflect"
"testing"
+ "time"
)
func TestReadConfig(t *testing.T) {
@@ 44,6 45,15 @@ ntfy {
user user
password pass
}
+
+# When the alert-mode is set to single, ntfy-alertmanager will cache each single alert
+# to avoid sending recurrences.
+cache {
+ # How long messages stay in the cache for
+ duration 48h
+ # Interval in which the cache is cleaned up
+ # cleanup-interval 1h
+}
`
expectedCfg := &config{
@@ 53,7 63,11 @@ ntfy {
Label: map[string]labelConfig{
"severity:critical": {Priority: "5", Tags: []string{"rotating_light"}},
"severity:info": {Priority: "1"},
- "instance:example.com": {Tags: []string{"computer", "example"}}}}}
+ "instance:example.com": {Tags: []string{"computer", "example"}},
+ },
+ },
+ cache: cacheConfig{CleanupInterval: time.Hour, Duration: 48 * time.Hour},
+ }
configPath := filepath.Join(t.TempDir(), "config")
err := os.WriteFile(configPath, []byte(configContent), 0600)
M main.go => main.go +3 -5
@@ 58,8 58,7 @@ func (rcv *receiver) singleAlertNotifications(p *payload) []*notification {
rcv.logger.Debugf("Alert %s skipped: Still in cache", alert.Fingerprint)
continue
}
- // TODO: Make configurable
- rcv.cache.set(alert.Fingerprint, time.Hour*24)
+ rcv.cache.set(alert.Fingerprint)
n := new(notification)
@@ 286,8 285,7 @@ func (rcv *receiver) basicAuthMiddleware(handler http.HandlerFunc) http.HandlerF
func (rcv *receiver) runCleanup() {
for {
- // TODO: Make configurable
- time.Sleep(time.Hour)
+ time.Sleep(rcv.cfg.cache.CleanupInterval)
rcv.logger.Info("Pruning cache")
rcv.cache.cleanup()
}
@@ 316,7 314,7 @@ func main() {
logger.Errorf("config: %v", err)
}
- receiver := &receiver{cfg: cfg, logger: logger, cache: newCache()}
+ receiver := &receiver{cfg: cfg, logger: logger, cache: newCache(cfg.cache.Duration)}
logger.Infof("Listening on %s, ntfy-alertmanager %s", cfg.HTTPAddress, version)