Go脚本+夜莺回调地址实现邮件告警

本例子演示的夜莺监控版本为v5.8.0,如图:

说明:如果将夜莺监控部署在纯内网,想要实现邮件告警只能通过代理tcp端口(比如25),如果内网和外网的tcp端口不能代理,并且内网和外网之间只能通过80端口通信,那么代理tcp端口方式就比较困难,此时可以通过回调地址来实现自定义告警,调用回调地址,然后通过Nginx代理出去,将脚本放到外网机器即可,首先可以看到夜莺告警规则的回调地址位置如下,这里我已经填写了我自己的地址,如图:

默认情况下,触发告警的时候,发送给回调地址的JSON数据格式如下:

{"id":1,"cluster":"Default","group_id":1,"group_name":"Default Busi Group","hash":"d533f164ecde00cf16bbb435c18d6b62","rule_id":1,"rule_name":"nginx80端口Down","rule_note":"test_machine","rule_prod":"","rule_algo":"","severity":3,"prom_for_duration":60,"prom_ql":"net_response_result_code{ident=\"192.168.51.246\", protocol=\"tcp\", target=\"127.0.0.1:80\"} != 0","prom_eval_interval":15,"callbacks":["http://127.0.0.1:4321"],"runbook_url":"","notify_recovered":1,"notify_channels":[],"notify_groups":[],"notify_groups_obj":[],"target_ident":"192.168.51.246","target_note":"","trigger_time":1733988554,"trigger_value":"2","tags":["__name__=net_response_result_code","ident=192.168.51.246","protocol=tcp","target=127.0.0.1:80"],"is_recovered":false,"notify_users_obj":[],"last_eval_time":1733988554,"last_sent_time":1733988554}

告警恢复的时候,发送给回调地址的JSON数据格式如下:

告警和恢复的数据格式都是JSON,只需要提取其中的部分字段处理后即可实现自定义邮件告警,数据发送到回调地址是通过POST方法请求,因此golang脚本也需要定义POST方法,原理如下:

JSON数据通过post请求发送给golang脚本,脚本获取数据后,将数据通过ShouldBindJSON绑定到结构体对应字段中(注意:字段大小写没关系,但是格式和内容必须对应),绑定后开始判断Is_recovered是true还是false,如果是false说明是触发告警,否则就是告警恢复,然后将内容通过邮件发送即可

脚本内容如下:

package main

import (
	"fmt"
	"github.com/gin-gonic/gin"
	"github.com/jordan-wright/email"
	"log"
	"net/smtp"
	"time"
	"io/ioutil"
	"gopkg.in/yaml.v2"
)

type test struct {
	ID            int      //id
	GroupID       int      //组id
	Cluster       string   //所属集群
	Group_name    string   //组名
	Rule_name     string   //告警策略名
	Rule_note     string   //告警策略备注
	Severity      int      //告警级别
	Trigger_value string   //触发时候的值,当不为0的时候就会触发告警
	Target_ident   string   //当前告警的服务器ip地址
	Trigger_time   int      //触发时间
	Is_recovered  bool     //是否恢复
	RecoveredTime int      //恢复时间
	AlarmStatus   string   //告警状态
}
type mailConfig struct {
    Send_mail string              //发件人
    Recv_mail []string            //收件人,可以多个,所以用字符串切片
    Cc_mail []string              //抄送人,也可以多个
    Smtp_address string           //smtp服务器地址
    Smtp_port  int                //smtp端口
    Auth_code string              //授权码
}
func createMail() *mailConfig{
     var mc *mailConfig
     by,err := ioutil.ReadFile("/etc/monitor.yaml")
     if err != nil {
          log.Fatal(err)
     }
     err = yaml.Unmarshal(by,&mc)
     if err != nil {
        log.Fatal(err)
     }
     return mc
}
func sendmail(id, groupid, severity int, cluster, groupname, rulename, rulenote, targetident, triggertime, triggervalue, recoveredtime, alarmstatus string, isrecovered bool) {
	mc := createMail()
	e := email.NewEmail()
	//设置发送方的邮箱
	e.From = mc.Send_mail
	// 设置接收方的邮箱
	e.To = mc.Recv_mail
	e.Cc = mc.Cc_mail
	//设置主题
	e.Subject = alarmstatus
	//设置文件发送的内容
	text := fmt.Sprintf("ID:%d\n组ID:%d\n所属集群:%s\n组名:%s\n告警级别:%d\n告警规则:%s\n告警规则备注:%s\n告警设备:%s\n告警时间:%s\n当前值:%s\n是否恢复:%v\n恢复时间:%s", id, groupid, cluster,groupname, severity, rulename, rulenote, targetident, triggertime, triggervalue,isrecovered, recoveredtime)
	e.Text = []byte(text)
	//设置服务器相关的配置
	e.Send(fmt.Sprintf("%s:%d",mc.Smtp_address,mc.Smtp_port),smtp.PlainAuth("",mc.Send_mail,mc.Auth_code,mc.Smtp_address))

}

// 将时间戳转换为标准时间
func timestamp(t int) string {
	tint64 := int64(t)
	ts := time.Unix(tint64, 0)
	return ts.Format("2006-01-02 15:04:05")
}
func main() {
	r := gin.New()
	r.POST("/json", func(c *gin.Context) {
		t := &test{}
		err := c.ShouldBindJSON(t)
		if err != nil {
			log.Fatal(err)
		}
		fmt.Println("Datasource:",t)
		id := t.ID
		groupid := t.GroupID
		severity := t.Severity
		cluster := t.Cluster
		groupname := t.Group_name
		rulename := t.Rule_name
		rulenote := t.Rule_note
		targetident := t.Target_ident
		triggervalue := t.Trigger_value
		triggertime := timestamp(t.Trigger_time)
		isrecovered := t.Is_recovered
		recoveredtime := ""
		alarmstatus := ""
		//如果为true,说明已经收到了告警恢复的回调数据
		if t.Is_recovered {
			recoveredtime = time.Now().Format("2006-01-02 15:04:05") 
			alarmstatus = fmt.Sprintf("告警恢复:%s",t.Rule_name)
		} else {
			alarmstatus = fmt.Sprintf("告警触发:%s",t.Rule_name)
		}
		sendmail(id, groupid, severity, cluster, groupname, rulename, rulenote, targetident, triggertime, triggervalue, recoveredtime, alarmstatus, isrecovered)
	})
	r.Run(":8080")
}

说明:告警恢复的回调地址数据中没有恢复时间,因此我手动添加了一个,通过判断传递过来的Is_recovered如果是true,说明恢复了,此时设置下recoveredtime为当前时间,这样恢复内容就可以显示触发时间和恢复时间了

配置文件monitor.yaml是单独定义的,可以方便后续修改发件人、授权码以及收件人,配置文件内容如下:

#发件人
send_mail: 88994433@qq.com
#收件人邮箱,可写多个
recv_mail:
  - def@qq.com
#抄送人邮箱
cc_mail:
  - abcd2023@outlook.com 
#smtp服务器地址
smtp_address: smtp.qq.com
#smtp端口
smtp_port: 25
#授权码
auth_code: aucoabcdeituwgegh

最后实现告警截图如下:

告警恢复如下,有恢复时间:

标签