diff --git a/etc/script/notify/Makefile b/etc/script/notify/Makefile new file mode 100644 index 00000000..4d4f7d04 --- /dev/null +++ b/etc/script/notify/Makefile @@ -0,0 +1,9 @@ + +.phony: all +all: plugin + +.phony: plugin +plugin: + export GOPROXY=http://goproxy.cn,direct + go build -buildmode=plugin -o notify.so notify.go + diff --git a/etc/script/notify/README.md b/etc/script/notify/README.md new file mode 100644 index 00000000..e8b2d68d --- /dev/null +++ b/etc/script/notify/README.md @@ -0,0 +1,35 @@ +通过go plugin模式处理告警通知 +--- + +相比于调用py脚本方式,该方式一般无需考虑依赖问题 + +### (1) 编写动态链接库逻辑 + +```go +package main + +type inter interface { + Descript() string + Notify([]byte) +} + +// 0、Descript 可用于该插件在 server 中的描述 +// 1、在 Notify 方法中实现要处理的自定义逻辑 +``` + +实现以上接口的 `struct` 实例即为合法 `plugin` + +### (2) 构建链接库 + +参考 `notify.go` 实现方式,执行 `make` 后可以看到生成一个 `notify.so` 链接文件,放到 n9e 对应项目位置即可 + +### (3) 更新 n9e 配置 + +```text +[Alerting.CallPlugin] +Enable = false +PluginPath = "./etc/script/notify.so" +# 注意此处caller必须在notify.so中作为变量暴露 +Caller = "n9eCaller" +``` + diff --git a/etc/script/notify/notify.go b/etc/script/notify/notify.go new file mode 100644 index 00000000..bfd360a0 --- /dev/null +++ b/etc/script/notify/notify.go @@ -0,0 +1,45 @@ +package main + +import ( + "fmt" + "time" + + "github.com/tidwall/gjson" +) + +// the caller can be called for alerting notify by complete this interface +type inter interface { + Descript() string + Notify([]byte) +} + +// N9E complete +type N9EPlugin struct { + Name string + Description string + BuildAt string +} + +func (n *N9EPlugin) Descript() string { + return fmt.Sprintf("%s: %s", n.Name, n.Description) +} + +func (n *N9EPlugin) Notify(bs []byte) { + var channels = []string{ + "dingtalk_robot_token", + "wecom_robot_token", + "feishu_robot_token", + } + for _, ch := range channels { + if ret := gjson.GetBytes(bs, ch); ret.Exists() { + fmt.Printf("do something...") + } + } +} + +// will be loaded for alertingCall +var n9eCaller = N9EPlugin{ + Name: "n9e", + Description: "演示告警通过动态链接库方式通知", + BuildAt: time.Now().Local().Format("2006/01/02 15:04:05"), +} diff --git a/etc/server.conf b/etc/server.conf index 6bf2e7b1..fb440362 100644 --- a/etc/server.conf +++ b/etc/server.conf @@ -75,6 +75,12 @@ NotifyBuiltinEnable = true Enable = false ScriptPath = "./etc/script/notify.py" +[Alerting.CallPlugin] +Enable = false +# use a plugin via `go build -buildmode=plugin -o notify.so` +PluginPath = "./etc/script/notify.so" +Caller = "n9eCaller" + [Alerting.RedisPub] Enable = false # complete redis key: ${ChannelPrefix} + ${Cluster} @@ -211,4 +217,4 @@ MaxIdleConnsPerHost = 100 # KeepAlive = 30000 # MaxConnsPerHost = 0 # MaxIdleConns = 100 -# MaxIdleConnsPerHost = 100 \ No newline at end of file +# MaxIdleConnsPerHost = 100 diff --git a/go.mod b/go.mod index a4bb3adf..e169087b 100644 --- a/go.mod +++ b/go.mod @@ -23,7 +23,7 @@ require ( github.com/prometheus/client_golang v1.11.0 github.com/prometheus/common v0.26.0 github.com/prometheus/prometheus v2.5.0+incompatible - github.com/tidwall/gjson v1.14.0 // indirect + github.com/tidwall/gjson v1.14.0 github.com/toolkits/pkg v1.2.9 github.com/urfave/cli/v2 v2.3.0 golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d // indirect @@ -31,7 +31,7 @@ require ( google.golang.org/genproto v0.0.0-20211007155348-82e027067bd4 // indirect google.golang.org/grpc v1.41.0 // indirect gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect - gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df // indirect + gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df gorm.io/driver/mysql v1.1.2 gorm.io/driver/postgres v1.1.1 gorm.io/gorm v1.21.15 diff --git a/src/server/config/config.go b/src/server/config/config.go index 096f55ba..369914d4 100644 --- a/src/server/config/config.go +++ b/src/server/config/config.go @@ -139,6 +139,7 @@ type Alerting struct { NotifyConcurrency int NotifyBuiltinEnable bool CallScript CallScript + CallPlugin CallPlugin RedisPub RedisPub Webhook Webhook } @@ -148,6 +149,12 @@ type CallScript struct { ScriptPath string } +type CallPlugin struct { + Enable bool + PluginPath string + Caller string +} + type RedisPub struct { Enable bool ChannelPrefix string diff --git a/src/server/engine/notify.go b/src/server/engine/notify.go index dae8b577..9a112191 100644 --- a/src/server/engine/notify.go +++ b/src/server/engine/notify.go @@ -9,6 +9,8 @@ import ( "net/http" "os/exec" "path" + "plugin" + "runtime" "strings" "time" @@ -119,6 +121,8 @@ func alertingRedisPub(bs []byte) { func handleNotice(notice Notice, bs []byte) { alertingCallScript(bs) + alertingCallPlugin(bs) + if !config.C.Alerting.NotifyBuiltinEnable { return } @@ -396,3 +400,37 @@ func alertingCallScript(stdinBytes []byte) { logger.Infof("event_notify: exec %s output: %s", fpath, buf.String()) } + +type Notifier interface { + Descript() string + Notify([]byte) +} + +// call notify.so via golang plugin build +// ig. etc/script/notify/notify.so +func alertingCallPlugin(stdinBytes []byte) { + if runtime.GOOS == "windows" { + logger.Errorf("call notify plugin on unsupported os: %s", runtime.GOOS) + return + } + if !config.C.Alerting.CallPlugin.Enable { + return + } + p, err := plugin.Open(config.C.Alerting.CallPlugin.PluginPath) + if err != nil { + logger.Errorf("failed to open notify plugin: %v", err) + return + } + caller, err := p.Lookup(config.C.Alerting.CallPlugin.Caller) + if err != nil { + logger.Errorf("failed to load caller: %v", err) + return + } + notifier, ok := caller.(Notifier) + if !ok { + logger.Errorf("notifier interface not implemented): %v", err) + return + } + notifier.Notify(stdinBytes) + logger.Debugf("alertingCallPlugin done. %s", notifier.Descript()) +}