feat(api): add log webhook notification service

Implement webhook notifications for log error threshold alerts with
configurable thresholds, time windows, and cooldown periods.

- Add LogWebhookService with Redis-backed configuration storage
- Add admin endpoints for webhook config management (GET/PUT)
- Trigger webhook notifications when error count exceeds threshold
- Support status code threshold and error message detection
- Include sample log record data in webhook payload
This commit is contained in:
zenfun
2025-12-21 14:13:35 +08:00
parent 00192f937e
commit 4c1e03f83d
6 changed files with 505 additions and 6 deletions

View File

@@ -0,0 +1,60 @@
package service
import (
"context"
"net/http"
"net/http/httptest"
"sync/atomic"
"testing"
"github.com/alicebob/miniredis/v2"
"github.com/ez-api/ez-api/internal/model"
"github.com/redis/go-redis/v9"
)
func TestLogWebhookServiceNotifyThreshold(t *testing.T) {
mr := miniredis.RunT(t)
rdb := redis.NewClient(&redis.Options{Addr: mr.Addr()})
svc := NewLogWebhookService(rdb)
var hits int64
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
atomic.AddInt64(&hits, 1)
w.WriteHeader(http.StatusOK)
}))
defer srv.Close()
cfg := LogWebhookConfig{
Enabled: true,
URL: srv.URL,
Threshold: 2,
WindowSeconds: 60,
CooldownSeconds: 300,
StatusCodeThreshold: 500,
}
if _, err := svc.SetConfig(context.Background(), cfg); err != nil {
t.Fatalf("set config: %v", err)
}
rec := model.LogRecord{StatusCode: 500, ErrorMessage: "upstream error"}
svc.NotifyIfNeeded(context.Background(), rec)
svc.NotifyIfNeeded(context.Background(), rec)
if atomic.LoadInt64(&hits) != 1 {
t.Fatalf("expected 1 webhook hit, got %d", hits)
}
}
func TestLogWebhookServiceDefaults(t *testing.T) {
mr := miniredis.RunT(t)
rdb := redis.NewClient(&redis.Options{Addr: mr.Addr()})
svc := NewLogWebhookService(rdb)
cfg, err := svc.GetConfig(context.Background())
if err != nil {
t.Fatalf("get config: %v", err)
}
if cfg.Threshold != defaultWebhookThreshold || cfg.WindowSeconds != defaultWebhookWindow {
t.Fatalf("unexpected defaults: %+v", cfg)
}
}