not stable version
This commit is contained in:
parent
b43f196d86
commit
403cb5a6ad
|
@ -54,17 +54,30 @@ IP = ""
|
||||||
# unit ms
|
# unit ms
|
||||||
Interval = 1000
|
Interval = 1000
|
||||||
|
|
||||||
|
[SMTP]
|
||||||
|
Host = "smtp.163.com"
|
||||||
|
Port = 994
|
||||||
|
User = "username"
|
||||||
|
Pass = "password"
|
||||||
|
From = "username@163.com"
|
||||||
|
InsecureSkipVerify = true
|
||||||
|
|
||||||
[Alerting]
|
[Alerting]
|
||||||
NotifyScriptPath = "./etc/script/notify.py"
|
|
||||||
NotifyConcurrency = 100
|
|
||||||
TemplatesDir = "./etc/template"
|
TemplatesDir = "./etc/template"
|
||||||
|
NotifyConcurrency = 100
|
||||||
|
|
||||||
|
[Alerting.CallScript]
|
||||||
|
# built in sending capability in go code
|
||||||
|
# so, no need enable script sender
|
||||||
|
Enable = false
|
||||||
|
ScriptPath = "./etc/script/notify.py"
|
||||||
|
|
||||||
[Alerting.RedisPub]
|
[Alerting.RedisPub]
|
||||||
Enable = false
|
Enable = false
|
||||||
# complete redis key: ${ChannelPrefix} + ${Cluster}
|
# complete redis key: ${ChannelPrefix} + ${Cluster}
|
||||||
ChannelPrefix = "/alerts/"
|
ChannelPrefix = "/alerts/"
|
||||||
|
|
||||||
[Alerting.GlobalCallback]
|
[Alerting.Webhook]
|
||||||
Enable = false
|
Enable = false
|
||||||
Url = "http://a.com/n9e/callback"
|
Url = "http://a.com/n9e/callback"
|
||||||
BasicAuthUser = ""
|
BasicAuthUser = ""
|
||||||
|
|
|
@ -79,16 +79,16 @@ func MustLoad(fpaths ...string) {
|
||||||
C.Heartbeat.Endpoint = fmt.Sprintf("%s:%d", C.Heartbeat.IP, C.HTTP.Port)
|
C.Heartbeat.Endpoint = fmt.Sprintf("%s:%d", C.Heartbeat.IP, C.HTTP.Port)
|
||||||
C.Alerting.RedisPub.ChannelKey = C.Alerting.RedisPub.ChannelPrefix + C.ClusterName
|
C.Alerting.RedisPub.ChannelKey = C.Alerting.RedisPub.ChannelPrefix + C.ClusterName
|
||||||
|
|
||||||
if C.Alerting.GlobalCallback.Enable {
|
if C.Alerting.Webhook.Enable {
|
||||||
if C.Alerting.GlobalCallback.Timeout == "" {
|
if C.Alerting.Webhook.Timeout == "" {
|
||||||
C.Alerting.GlobalCallback.TimeoutDuration = time.Second * 5
|
C.Alerting.Webhook.TimeoutDuration = time.Second * 5
|
||||||
} else {
|
} else {
|
||||||
dur, err := time.ParseDuration(C.Alerting.GlobalCallback.Timeout)
|
dur, err := time.ParseDuration(C.Alerting.Webhook.Timeout)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("failed to parse Alerting.GlobalCallback.Timeout")
|
fmt.Println("failed to parse Alerting.Webhook.Timeout")
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
C.Alerting.GlobalCallback.TimeoutDuration = dur
|
C.Alerting.Webhook.TimeoutDuration = dur
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -104,6 +104,7 @@ type Config struct {
|
||||||
Log logx.Config
|
Log logx.Config
|
||||||
HTTP httpx.Config
|
HTTP httpx.Config
|
||||||
BasicAuth gin.Accounts
|
BasicAuth gin.Accounts
|
||||||
|
SMTP SMTPConfig
|
||||||
Heartbeat HeartbeatConfig
|
Heartbeat HeartbeatConfig
|
||||||
Alerting Alerting
|
Alerting Alerting
|
||||||
NoData NoData
|
NoData NoData
|
||||||
|
@ -123,12 +124,26 @@ type HeartbeatConfig struct {
|
||||||
Endpoint string
|
Endpoint string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type SMTPConfig struct {
|
||||||
|
Host string
|
||||||
|
Port int
|
||||||
|
User string
|
||||||
|
Pass string
|
||||||
|
From string
|
||||||
|
InsecureSkipVerify bool
|
||||||
|
}
|
||||||
|
|
||||||
type Alerting struct {
|
type Alerting struct {
|
||||||
NotifyScriptPath string
|
|
||||||
NotifyConcurrency int
|
|
||||||
TemplatesDir string
|
TemplatesDir string
|
||||||
|
NotifyConcurrency int
|
||||||
|
CallScript CallScript
|
||||||
RedisPub RedisPub
|
RedisPub RedisPub
|
||||||
GlobalCallback GlobalCallback
|
Webhook Webhook
|
||||||
|
}
|
||||||
|
|
||||||
|
type CallScript struct {
|
||||||
|
Enable bool
|
||||||
|
ScriptPath string
|
||||||
}
|
}
|
||||||
|
|
||||||
type RedisPub struct {
|
type RedisPub struct {
|
||||||
|
@ -137,7 +152,7 @@ type RedisPub struct {
|
||||||
ChannelKey string
|
ChannelKey string
|
||||||
}
|
}
|
||||||
|
|
||||||
type GlobalCallback struct {
|
type Webhook struct {
|
||||||
Enable bool
|
Enable bool
|
||||||
Url string
|
Url string
|
||||||
BasicAuthUser string
|
BasicAuthUser string
|
||||||
|
|
|
@ -1,10 +1,6 @@
|
||||||
package engine
|
package engine
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
|
||||||
"io/ioutil"
|
|
||||||
"net/http"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
@ -15,41 +11,9 @@ import (
|
||||||
"github.com/didi/nightingale/v5/src/pkg/ibex"
|
"github.com/didi/nightingale/v5/src/pkg/ibex"
|
||||||
"github.com/didi/nightingale/v5/src/server/config"
|
"github.com/didi/nightingale/v5/src/server/config"
|
||||||
"github.com/didi/nightingale/v5/src/server/memsto"
|
"github.com/didi/nightingale/v5/src/server/memsto"
|
||||||
|
"github.com/didi/nightingale/v5/src/server/poster"
|
||||||
)
|
)
|
||||||
|
|
||||||
func PostJSON(url string, timeout time.Duration, v interface{}) (response []byte, code int, err error) {
|
|
||||||
var bs []byte
|
|
||||||
|
|
||||||
bs, err = json.Marshal(v)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
bf := bytes.NewBuffer(bs)
|
|
||||||
|
|
||||||
client := http.Client{
|
|
||||||
Timeout: timeout,
|
|
||||||
}
|
|
||||||
|
|
||||||
req, err := http.NewRequest("POST", url, bf)
|
|
||||||
req.Header.Set("Content-Type", "application/json")
|
|
||||||
|
|
||||||
var resp *http.Response
|
|
||||||
resp, err = client.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
code = resp.StatusCode
|
|
||||||
|
|
||||||
if resp.Body != nil {
|
|
||||||
defer resp.Body.Close()
|
|
||||||
response, err = ioutil.ReadAll(resp.Body)
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func callback(event *models.AlertCurEvent) {
|
func callback(event *models.AlertCurEvent) {
|
||||||
urls := strings.Fields(event.Callbacks)
|
urls := strings.Fields(event.Callbacks)
|
||||||
for _, url := range urls {
|
for _, url := range urls {
|
||||||
|
@ -68,7 +32,7 @@ func callback(event *models.AlertCurEvent) {
|
||||||
url = "http://" + url
|
url = "http://" + url
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, code, err := PostJSON(url, 5*time.Second, event)
|
resp, code, err := poster.PostJSON(url, 5*time.Second, event)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Errorf("event_callback(rule_id=%d url=%s) fail, resp: %s, err: %v, code: %d", event.RuleId, url, string(resp), err, code)
|
logger.Errorf("event_callback(rule_id=%d url=%s) fail, resp: %s, err: %v, code: %d", event.RuleId, url, string(resp), err, code)
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -31,14 +31,14 @@ var fns = template.FuncMap{
|
||||||
"urlconvert": func(str string) interface{} { return template.URL(str) },
|
"urlconvert": func(str string) interface{} { return template.URL(str) },
|
||||||
"timeformat": func(ts int64, pattern ...string) string {
|
"timeformat": func(ts int64, pattern ...string) string {
|
||||||
defp := "2006-01-02 15:04:05"
|
defp := "2006-01-02 15:04:05"
|
||||||
if pattern != nil && len(pattern) > 0 {
|
if len(pattern) > 0 {
|
||||||
defp = pattern[0]
|
defp = pattern[0]
|
||||||
}
|
}
|
||||||
return time.Unix(ts, 0).Format(defp)
|
return time.Unix(ts, 0).Format(defp)
|
||||||
},
|
},
|
||||||
"timestamp": func(pattern ...string) string {
|
"timestamp": func(pattern ...string) string {
|
||||||
defp := "2006-01-02 15:04:05"
|
defp := "2006-01-02 15:04:05"
|
||||||
if pattern != nil && len(pattern) > 0 {
|
if len(pattern) > 0 {
|
||||||
defp = pattern[0]
|
defp = pattern[0]
|
||||||
}
|
}
|
||||||
return time.Now().Format(defp)
|
return time.Now().Format(defp)
|
||||||
|
@ -89,7 +89,7 @@ type Notice struct {
|
||||||
Tpls map[string]string `json:"tpls"`
|
Tpls map[string]string `json:"tpls"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildStdin(event *models.AlertCurEvent) ([]byte, error) {
|
func genNotice(event *models.AlertCurEvent) Notice {
|
||||||
// build notice body with templates
|
// build notice body with templates
|
||||||
ntpls := make(map[string]string)
|
ntpls := make(map[string]string)
|
||||||
for filename, tpl := range tpls {
|
for filename, tpl := range tpls {
|
||||||
|
@ -101,36 +101,40 @@ func buildStdin(event *models.AlertCurEvent) ([]byte, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return json.Marshal(Notice{Event: event, Tpls: ntpls})
|
return Notice{Event: event, Tpls: ntpls}
|
||||||
|
}
|
||||||
|
|
||||||
|
func alertingRedisPub(bs []byte) {
|
||||||
|
// pub all alerts to redis
|
||||||
|
if config.C.Alerting.RedisPub.Enable {
|
||||||
|
err := storage.Redis.Publish(context.Background(), config.C.Alerting.RedisPub.ChannelKey, bs).Err()
|
||||||
|
if err != nil {
|
||||||
|
logger.Errorf("event_notify: redis publish %s err: %v", config.C.Alerting.RedisPub.ChannelKey, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleNotice(notice Notice, bs []byte) {
|
||||||
|
alertingCallScript(bs)
|
||||||
|
|
||||||
|
// TODO 弄个channel发邮件,学习daemon写法
|
||||||
|
// 收集tokens、phones,发呗
|
||||||
}
|
}
|
||||||
|
|
||||||
func notify(event *models.AlertCurEvent) {
|
func notify(event *models.AlertCurEvent) {
|
||||||
logEvent(event, "notify")
|
logEvent(event, "notify")
|
||||||
|
|
||||||
stdin, err := buildStdin(event)
|
notice := genNotice(event)
|
||||||
|
stdinBytes, err := json.Marshal(notice)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Errorf("event_notify: build stdin failed: %v", err)
|
logger.Errorf("event_notify: failed to marshal notice: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// pub all alerts to redis
|
alertingRedisPub(stdinBytes)
|
||||||
if config.C.Alerting.RedisPub.Enable {
|
alertingWebhook(event)
|
||||||
err = storage.Redis.Publish(context.Background(), config.C.Alerting.RedisPub.ChannelKey, stdin).Err()
|
|
||||||
if err != nil {
|
|
||||||
logger.Errorf("event_notify: redis publish %s err: %v", config.C.Alerting.RedisPub.ChannelKey, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if config.C.Alerting.GlobalCallback.Enable {
|
handleNotice(notice, stdinBytes)
|
||||||
DoGlobalCallback(event)
|
|
||||||
}
|
|
||||||
|
|
||||||
// no notify.py? do nothing
|
|
||||||
if config.C.Alerting.NotifyScriptPath == "" {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
callScript(stdin)
|
|
||||||
|
|
||||||
// handle alert subscribes
|
// handle alert subscribes
|
||||||
subs, has := memsto.AlertSubscribeCache.Get(event.RuleId)
|
subs, has := memsto.AlertSubscribeCache.Get(event.RuleId)
|
||||||
|
@ -144,8 +148,13 @@ func notify(event *models.AlertCurEvent) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func DoGlobalCallback(event *models.AlertCurEvent) {
|
func alertingWebhook(event *models.AlertCurEvent) {
|
||||||
conf := config.C.Alerting.GlobalCallback
|
conf := config.C.Alerting.Webhook
|
||||||
|
|
||||||
|
if !conf.Enable {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if conf.Url == "" {
|
if conf.Url == "" {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -159,7 +168,7 @@ func DoGlobalCallback(event *models.AlertCurEvent) {
|
||||||
|
|
||||||
req, err := http.NewRequest("POST", conf.Url, bf)
|
req, err := http.NewRequest("POST", conf.Url, bf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Warning("DoGlobalCallback failed to new request", err)
|
logger.Warning("alertingWebhook failed to new request", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -180,17 +189,17 @@ func DoGlobalCallback(event *models.AlertCurEvent) {
|
||||||
var resp *http.Response
|
var resp *http.Response
|
||||||
resp, err = client.Do(req)
|
resp, err = client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Warning("DoGlobalCallback failed to call url, error: ", err)
|
logger.Warning("alertingWebhook failed to call url, error: ", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var body []byte
|
var body []byte
|
||||||
if resp.Body != nil {
|
if resp.Body != nil {
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
body, err = ioutil.ReadAll(resp.Body)
|
body, _ = ioutil.ReadAll(resp.Body)
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.Debugf("DoGlobalCallback done, url: %s, response code: %d, body: %s", conf.Url, resp.StatusCode, string(body))
|
logger.Debugf("alertingWebhook done, url: %s, response code: %d, body: %s", conf.Url, resp.StatusCode, string(body))
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleSubscribes(event models.AlertCurEvent, subs []*models.AlertSubscribe) {
|
func handleSubscribes(event models.AlertCurEvent, subs []*models.AlertSubscribe) {
|
||||||
|
@ -223,17 +232,27 @@ func handleSubscribe(event models.AlertCurEvent, sub *models.AlertSubscribe) {
|
||||||
|
|
||||||
fillUsers(&event)
|
fillUsers(&event)
|
||||||
|
|
||||||
stdin, err := buildStdin(&event)
|
notice := genNotice(&event)
|
||||||
|
stdinBytes, err := json.Marshal(notice)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Errorf("event_notify: build stdin failed when handle subscribe: %v", err)
|
logger.Errorf("event_notify: failed to marshal notice: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
callScript(stdin)
|
handleNotice(notice, stdinBytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
func callScript(stdinBytes []byte) {
|
func alertingCallScript(stdinBytes []byte) {
|
||||||
fpath := config.C.Alerting.NotifyScriptPath
|
if !config.C.Alerting.CallScript.Enable {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// no notify.py? do nothing
|
||||||
|
if config.C.Alerting.CallScript.ScriptPath == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fpath := config.C.Alerting.CallScript.ScriptPath
|
||||||
cmd := exec.Command(fpath)
|
cmd := exec.Command(fpath)
|
||||||
cmd.Stdin = bytes.NewReader(stdinBytes)
|
cmd.Stdin = bytes.NewReader(stdinBytes)
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,42 @@
|
||||||
|
package poster
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func PostJSON(url string, timeout time.Duration, v interface{}) (response []byte, code int, err error) {
|
||||||
|
var bs []byte
|
||||||
|
|
||||||
|
bs, err = json.Marshal(v)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
bf := bytes.NewBuffer(bs)
|
||||||
|
|
||||||
|
client := http.Client{
|
||||||
|
Timeout: timeout,
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := http.NewRequest("POST", url, bf)
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
|
||||||
|
var resp *http.Response
|
||||||
|
resp, err = client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
code = resp.StatusCode
|
||||||
|
|
||||||
|
if resp.Body != nil {
|
||||||
|
defer resp.Body.Close()
|
||||||
|
response, err = ioutil.ReadAll(resp.Body)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
|
@ -0,0 +1,55 @@
|
||||||
|
package sender
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/didi/nightingale/v5/src/server/poster"
|
||||||
|
"github.com/toolkits/pkg/logger"
|
||||||
|
)
|
||||||
|
|
||||||
|
type DingtalkMessage struct {
|
||||||
|
Title string
|
||||||
|
Text string
|
||||||
|
AtMobiles []string
|
||||||
|
Tokens []string
|
||||||
|
}
|
||||||
|
|
||||||
|
type dingtalkMarkdown struct {
|
||||||
|
Title string `json:"title"`
|
||||||
|
Text string `json:"text"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type dingtalkAt struct {
|
||||||
|
AtMobiles []string `json:"atMobiles"`
|
||||||
|
IsAtAll bool `json:"isAtAll"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type dingtalk struct {
|
||||||
|
Msgtype string `json:"msgtype"`
|
||||||
|
Markdown dingtalkMarkdown `json:"markdown"`
|
||||||
|
At dingtalkAt `json:"at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func SendDingtalk(message DingtalkMessage) {
|
||||||
|
for i := 0; i < len(message.Tokens); i++ {
|
||||||
|
url := "https://oapi.dingtalk.com/robot/send?access_token=" + message.Tokens[i]
|
||||||
|
body := dingtalk{
|
||||||
|
Msgtype: "markdown",
|
||||||
|
Markdown: dingtalkMarkdown{
|
||||||
|
Title: message.Title,
|
||||||
|
Text: message.Text,
|
||||||
|
},
|
||||||
|
At: dingtalkAt{
|
||||||
|
AtMobiles: message.AtMobiles,
|
||||||
|
IsAtAll: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
res, code, err := poster.PostJSON(url, time.Second*5, body)
|
||||||
|
if err != nil {
|
||||||
|
logger.Errorf("dingtalk_sender: result=fail url=%s code=%d error=%v response=%s", url, code, err, string(res))
|
||||||
|
} else {
|
||||||
|
logger.Infof("dingtalk_sender: result=succ url=%s code=%d response=%s", url, code, string(res))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,52 @@
|
||||||
|
package sender
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/didi/nightingale/v5/src/server/poster"
|
||||||
|
"github.com/toolkits/pkg/logger"
|
||||||
|
)
|
||||||
|
|
||||||
|
type FeishuMessage struct {
|
||||||
|
Text string
|
||||||
|
AtMobiles []string
|
||||||
|
Tokens []string
|
||||||
|
}
|
||||||
|
|
||||||
|
type feishuContent struct {
|
||||||
|
Text string `json:"text"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type feishuAt struct {
|
||||||
|
AtMobiles []string `json:"atMobiles"`
|
||||||
|
IsAtAll bool `json:"isAtAll"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type feishu struct {
|
||||||
|
Msgtype string `json:"msg_type"`
|
||||||
|
Content feishuContent `json:"content"`
|
||||||
|
At feishuAt `json:"at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func SendFeishu(message FeishuMessage) {
|
||||||
|
for i := 0; i < len(message.Tokens); i++ {
|
||||||
|
url := "https://open.feishu.cn/open-apis/bot/v2/hook/" + message.Tokens[i]
|
||||||
|
body := feishu{
|
||||||
|
Msgtype: "text",
|
||||||
|
Content: feishuContent{
|
||||||
|
Text: message.Text,
|
||||||
|
},
|
||||||
|
At: feishuAt{
|
||||||
|
AtMobiles: message.AtMobiles,
|
||||||
|
IsAtAll: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
res, code, err := poster.PostJSON(url, time.Second*5, body)
|
||||||
|
if err != nil {
|
||||||
|
logger.Errorf("feishu_sender: result=fail url=%s code=%d error=%v response=%s", url, code, err, string(res))
|
||||||
|
} else {
|
||||||
|
logger.Infof("feishu_sender: result=succ url=%s code=%d response=%s", url, code, string(res))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,41 @@
|
||||||
|
package sender
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/didi/nightingale/v5/src/server/poster"
|
||||||
|
"github.com/toolkits/pkg/logger"
|
||||||
|
)
|
||||||
|
|
||||||
|
type WecomMessage struct {
|
||||||
|
Text string
|
||||||
|
Tokens []string
|
||||||
|
}
|
||||||
|
|
||||||
|
type wecomMarkdown struct {
|
||||||
|
Content string `json:"content"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type wecom struct {
|
||||||
|
Msgtype string `json:"msgtype"`
|
||||||
|
Markdown wecomMarkdown `json:"markdown"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func SendWecom(message WecomMessage) {
|
||||||
|
for i := 0; i < len(message.Tokens); i++ {
|
||||||
|
url := "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=" + message.Tokens[i]
|
||||||
|
body := wecom{
|
||||||
|
Msgtype: "markdown",
|
||||||
|
Markdown: wecomMarkdown{
|
||||||
|
Content: message.Text,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
res, code, err := poster.PostJSON(url, time.Second*5, body)
|
||||||
|
if err != nil {
|
||||||
|
logger.Errorf("wecom_sender: result=fail url=%s code=%d error=%v response=%s", url, code, err, string(res))
|
||||||
|
} else {
|
||||||
|
logger.Infof("wecom_sender: result=succ url=%s code=%d response=%s", url, code, string(res))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue