使用Golang编写Webhook向钉钉告警

skywalking-webhook

  • 不要问,为什么要用Golang写这个脚本

  • 问就是刚刚学Golang没有项目练手~~~

  • 脚本需求:

    使用Golang编写Webhook向钉钉告警

  • 需求:

    skywalking9.7不知什么原因,告警不出来

    刚刚好学习了Go语言就想着要不用Golang写一个webhook脚本吧,然后就有了以下内容

  • 版本:go1.23.0

初始化go项目

  • 初始化项目,如果是简单的脚本,怎么方便怎么来
  • 直接进入项目目录初始化也行
cd /apps/app/skywalking-webhook/
go mod init TestSkywalkingWebhook
  • 但是如果是项目,或者复杂一点点就,建议使用
go mod init gitee.com/xxx/skills/
  • Golang代理推荐
# 下包报错换一下试试吧,不行再换一个
go env -w GOPROXY=https://goproxy.cn,direct

创建钉钉告警机器人

请添加图片描述

配置skywlking告警配置文件

cat alarm-settings.yml
rules:
  ###@@@=================== 服务的响应时间 ===================@@@###
  service_resp_time_rule:
    expression: sum(service_resp_time > 6000) >= 3
    period: 10
    silence-period: 20
    #message: SkyWalking Response time of service {name} is more than 1000ms in 3 minutes of last 10 minutes.
    message: "当前服务响应时间在 10 分钟内 有出现 3 次 超时 6 秒"

  ###@@@=================== 服务的成功率是否低于某个阈值 ===================@@@###
  service_sla_rule:
    expression: sum(service_sla < 8000) >= 3
    period: 10
    silence-period: 20
    #message: SkyWalking Successful rate of service {name} is lower than 80% in 2 minutes of last 10 minutes
    message: "当前服务在 10 分钟内出现 3 次,健康检查接口成功率低于 80%。"

  ###@@@=================== 用于监控服务的响应时间的百分比分布情况 ===================@@@###
  #service_resp_time_percentile_rule:
  #  expression: sum(service_percentile{_='0,1,2,3,4'} > 1000) >= 3
  #  period: 10
  #  silence-period: 5
  #  message: SkyWalking Percentile response time of service {name} alarm in 3 minutes of last 10 minutes, due to more than one condition of p50 > 1000, p75 > 1000, p90 > 1000, p95 > 1000, p99 > 1000

  ###@@@=================== 服务实例的响应时间是否超过了 ** 毫秒 ===================@@@###
  service_instance_resp_time_rule:
    expression: sum(service_instance_resp_time > 6000) >= 3
    period: 10
    silence-period: 5
    #message: SkyWalking Response time of service instance {name} is more than 1000ms in 2 minutes of last 10 minutes
    #message: "当前服务实例 {name}  响应时间在 10 分钟内 有出现 3 次 超时 1000 毫秒"
    message: "当前服务实例 响应时间在 10 分钟内 有出现 3 次 超时 6 秒"


  database_access_resp_time_rule:
    expression: sum(database_access_resp_time > 6000) >= 3
    period: 10
    #message: SkyWalking Response time of database access {name} is more than 1000ms in 2 minutes of last 10 minutes
    #message: "{name} 访问数据库的响应时间在 10 分钟内有 2 次超过 1000 毫秒"
    message: "{name} 访问数据库的响应时间在 10 分钟内有 3 次超过 6 秒"


  endpoint_relation_resp_time_rule:
    expression: sum(endpoint_relation_resp_time > 6000) >= 2
    period: 10
    #message: SkyWalking Response time of endpoint relation {name} is more than 1000ms in 2 minutes of last 10 minutes
    #message: "端点关系 {name} 的响应时间在过去 10 分钟内有 2 次超过 1000 毫秒"
    message: "端点关系 的响应时间在过去 10 分钟内有 2 次超过 6 秒"


###  这个就是SkyWalking官方文档使用的告警办法,我发现我无论如何都告警不了
#dingtalk:
#  default:
#    is-default: true
#    text-template: |-
#      {
#        "msgtype": "text",
#        "text": {
#          "content": "Apache SkyWalking Alarm: \n %s."
#        }
#      }      
#    webhooks:
#    - url: https://oapixxxxxxxxxxxxxxxxxxxxxx731b69

# 调用自己写的webhook
hooks:
  webhook:
    default:
      is-default: true
      urls:
        - http://localhost:8000/webhook

编写Golang脚本

  • 有问题的话,可以使用了Print大法具体排错,下面已经写好并注释了
package main

import (
	"bytes"
	"encoding/json"
	"fmt"

	//"time"
    //"strings"
    
	"io/ioutil"
	"net/http"
)

// Tag 数据结构
type Tag struct {
	Key   string `json:"key"`
	Value string `json:"value"`
}

// SkyWalking 告警数据结构
type SkyWalkingAlert struct {
	ScopeId  int    `json:"scopeId"`
	Scope    string `json:"scope"`
	Name     string `json:"name"`
	Id0      string `json:"id0"`
	Id1      string `json:"id1"`
	RuleName string `json:"ruleName"`
	AlertName    string `json:"alertName"`
	AlarmMessage string `json:"alarmMessage"`
	StartTime    int64  `json:"startTime"`
	Tags         []Tag  `json:"tags"`
}

// 使用Message方式处理发出的告警信息
type DingTalkMessage struct {
	Msgtype  string `json:"msgtype"`
	Markdown struct {
		Title string `json:"title"`
		Text  string `json:"text"`
	} `json:"markdown"`
}

// 发送钉钉消息
func sendToDingTalk(webhookURL string, message DingTalkMessage) error {
	jsonData, err := json.Marshal(message)
	if err != nil {
		return err
	}

	req, err := http.NewRequest("POST", webhookURL, bytes.NewBuffer(jsonData))
	if err != nil {
		return err
	}
	req.Header.Set("Content-Type", "application/json")

	client := &http.Client{}
	resp, err := client.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	// 可选:检查响应状态码等
	return nil
}

// 接收并处理 Webhook 请求
func webhookHandler(w http.ResponseWriter, r *http.Request) {
	// 读取请求体(二进制)
	body, err := ioutil.ReadAll(r.Body)
	if err != nil {
		http.Error(w, "Failed to read request body", http.StatusBadRequest)
		// fmt.Println("Test1.打印解析数组是否有报错:", err)
		return
	}
	defer r.Body.Close()

	// 解析告警数据数组
	var alerts []SkyWalkingAlert
	err = json.Unmarshal(body, &alerts)
	if err != nil {
		http.Error(w, "Failed to parse alert data", http.StatusBadRequest)
		// fmt.Println("Test2.打印解析数组是否有报错:", err)
		return
	}

	//fmt.Printf("Test3.打印数组了解钉钉告警的数据结构: %s\n", alerts)
	// for _, alert := range alerts {
	//     fmt.Printf("Test4.Print大法: 服务名:%s\n 告警规则:%s\n 告警信息:%s\n 告警时间:%s\n", alert.Name, alert.RuleName, alert.AlarmMessage, alert.StartTime)
	// }

	for _, alert := range alerts {
		// 将时间戳转换为time.Time类型,并假设它是纳秒级的时间戳
		//startTime := time.Unix(0, alert.StartTime).Format("2006-01-02 15:04:05")

		// 构造钉钉消息
		dingTalkMessage := DingTalkMessage{
			Msgtype: "markdown",
			Markdown: struct {
				Title string `json:"title"`
				Text  string `json:"text"`
			}{
				Title: fmt.Sprintf("SkyWalking 告警通知"),
				Text:  fmt.Sprintf("<font color='#FF7D00'> 服务名 </font>:%s<br> <font color='#FF7D00'> 告警规则 </font>:%s<br> <font color='#FF7D00'> 告警信息 </font>:%s<br>", alert.Name, alert.RuleName, alert.AlarmMessage),
			},
		}

		// 发送钉钉消息
		if err := sendToDingTalk("https://oaxxxxxx----你的钉钉webhook----xxxxxxxx", dingTalkMessage); err != nil {
			http.Error(w, "Failed to send alert to DingTalk", http.StatusInternalServerError)
			fmt.Println("Test5.发送钉钉消息报错:", err)
			return
		}
	}

	// 响应 HTTP 请求
	w.WriteHeader(http.StatusOK)
	w.Write([]byte("Webhook received and alert sent to DingTalk successfully"))
}

func main() {
	http.HandleFunc("/webhook", webhookHandler)
	fmt.Println("Server is listening on :8000")
	if err := http.ListenAndServe(":8000", nil); err != nil {
		panic(err)
	}
}
go mod tidy
  • 告警出来大概是这样,具体可以自己调整,都在SkyWalkingAlert这个结构体中,自己拿出来打印就好了

请添加图片描述

测试脚本可用性

  • 执行脚本测试
curl -X POST http://localhost:8000/webhook -H "Content-Type: application/json" -d '
[
    {
        "scopeId": 1,
        "scope": "Service",
        "name": "test-service",
        "id0": "0",
        "id1": "0",
        "ruleName": "Response Time Degradation",
        "ruleId": 1,
        "alertName": "High Response Time",
        "alarmMessage": "Response time has exceeded 1000ms",
        "startTime": 1672531200000,
        "tags": [
            {"key": "endpoint", "value": "/api/test"},
            {"key": "instance", "value": "127.0.0.1:8080"}
        ]
    }
]'
使用Golang编写一个简单的HTTP服务器可以有多种方式。下面是两种常见的实现方式: 第一种方式是使用`http.HandleFunc`函数和一个处理函数来实现。代码如下所示: ```go package main import ( "fmt" "net/http" ) func indexHandler(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "hello world") } func main() { http.HandleFunc("/", indexHandler) http.ListenAndServe(":8000", nil) } ``` 在这个例子中,我们定义了一个`indexHandler`函数来处理根路径的请求。当有请求到达时,`indexHandler`函数会将"hello world"作为响应写入到`http.ResponseWriter`中。 第二种方式是使用自定义的处理器类型和`http.Handle`函数来实现。代码如下所示: ```go package main import ( "fmt" "net/http" ) type indexHandler struct { content string } func (ih *indexHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, ih.content) } func main() { http.Handle("/", &indexHandler{content: "hello world!"}) http.ListenAndServe(":8001", nil) } ``` 在这个例子中,我们定义了一个`indexHandler`类型,并实现了`ServeHTTP`方法。当有请求到达时,`ServeHTTP`方法会将`indexHandler`的`content`字段作为响应写入到`http.ResponseWriter`中。 无论是哪种方式,我们都需要使用`http.ListenAndServe`函数来启动服务器。这个函数会监听指定的地址,并将请求交给指定的处理器来处理。 希望这个例子能帮助你理解如何使用Golang编写一个简单的HTTP服务器。 #### 引用[.reference_title] - *1* *2* *3* [golang http Server介绍](https://blog.csdn.net/zrg3699/article/details/122280399)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值