Skip to content

Commit b7143b1

Browse files
committed
feat: add log alerting function to api
1 parent 953138c commit b7143b1

3 files changed

Lines changed: 78 additions & 14 deletions

File tree

api/mw/monitor/monitor.go

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,22 +41,60 @@ type MonitorConfig struct {
4141
var (
4242
apiMonitorInstance *apiMonitor
4343
apiMonitorStartOnce sync.Once
44+
apiMonitorStop = func(context.Context) {}
4445
)
4546

46-
func StartAPIMonitor(cfg MonitorConfig) {
47+
// StartAPIMonitor 启动 API 监控后台检查,并返回用于优雅停止监控的函数。
48+
// 停止函数会等待后台检查 goroutine 退出,便于服务关闭时完成资源清理。
49+
func StartAPIMonitor(cfg MonitorConfig) func(context.Context) {
50+
// 监控进程生命周期与服务进程一致,只允许初始化一次。
4751
apiMonitorStartOnce.Do(func() {
4852
apiMonitorInstance = newAPIMonitor(cfg)
4953
if !apiMonitorInstance.enabled() {
54+
// 未启用监控时不启动后台任务,返回默认的空操作停止函数。
5055
return
5156
}
5257

58+
interval := apiMonitorInstance.checkInterval()
59+
if interval <= 0 {
60+
// 无效的检查间隔不能用于创建 ticker,避免启动时 panic。
61+
return
62+
}
63+
64+
// cancel 用于通知后台任务退出,done 用于确认检查 goroutine 已结束。
65+
ctx, cancel := context.WithCancel(context.Background())
66+
checkDone := make(chan struct{})
5367
go func() {
68+
defer close(checkDone)
69+
70+
ticker := time.NewTicker(interval)
71+
defer ticker.Stop()
72+
5473
for {
55-
time.Sleep(apiMonitorInstance.checkInterval())
56-
apiMonitorInstance.check()
74+
select {
75+
case <-ticker.C:
76+
// 定期清理滑动窗口并检查各路由的错误率。
77+
apiMonitorInstance.check()
78+
case <-ctx.Done():
79+
// 服务关闭时优先响应取消信号,不再执行新的检查。
80+
return
81+
}
5782
}
5883
}()
84+
85+
apiMonitorStop = func(ctx context.Context) {
86+
cancel()
87+
// 等待检查循环退出,避免服务关闭后仍残留后台任务。
88+
select {
89+
case <-checkDone:
90+
case <-ctx.Done():
91+
return
92+
}
93+
}
5994
})
95+
96+
// API 服务会在 OnShutdown hook 中调用该函数。
97+
return apiMonitorStop
6098
}
6199

62100
func APIMonitorMiddleware() app.HandlerFunc {

api/mw/monitor/monitor_test.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ limitations under the License.
1717
package monitor
1818

1919
import (
20+
"context"
21+
"sync"
2022
"testing"
2123
"time"
2224

@@ -108,6 +110,34 @@ func TestMonitorRecordSkipsDisabledAndBlacklisted(t *testing.T) {
108110
assert.Equal(t, "/api/bar", enabled.events[0].route)
109111
}
110112

113+
func TestStartAPIMonitorStopReturnsBeforeNextCheck(t *testing.T) {
114+
apiMonitorInstance = nil
115+
apiMonitorStartOnce = sync.Once{}
116+
apiMonitorStop = func(context.Context) {}
117+
defer func() {
118+
apiMonitorInstance = nil
119+
apiMonitorStartOnce = sync.Once{}
120+
apiMonitorStop = func(context.Context) {}
121+
}()
122+
123+
stop := StartAPIMonitor(MonitorConfig{
124+
Enabled: true,
125+
CheckInterval: time.Hour,
126+
})
127+
128+
done := make(chan struct{})
129+
go func() {
130+
stop(context.Background())
131+
close(done)
132+
}()
133+
134+
select {
135+
case <-done:
136+
case <-time.After(time.Second):
137+
t.Fatal("api monitor stop should not wait for the next check interval")
138+
}
139+
}
140+
111141
func TestMonitorAlertCooldownAndRecover(t *testing.T) {
112142
monitor := newAPIMonitor(MonitorConfig{
113143
Enabled: true,

cmd/api/main.go

Lines changed: 7 additions & 11 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)