Skip to content

Commit 9d68d16

Browse files
bigcyyyunfan24tomsun28
authored
[feature] Support TencentCloud alert source (apache#3149)
Co-authored-by: yunfan24 <[email protected]> Co-authored-by: tomsun28 <[email protected]>
1 parent 290474a commit 9d68d16

File tree

6 files changed

+280
-15
lines changed

6 files changed

+280
-15
lines changed

hertzbeat-alerter/src/main/java/org/apache/hertzbeat/alert/dto/TencentCloudExternAlert.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -118,11 +118,11 @@ public static class Conditions {
118118
/**
119119
* Transaction alarm
120120
*/
121-
private static final String EVENT = "event";
121+
public static final String EVENT = "event";
122122

123123
/**
124124
* Indicator alarm
125125
*/
126-
private static final String METRIC = "metric";
126+
public static final String METRIC = "metric";
127127

128128
}

hertzbeat-alerter/src/main/java/org/apache/hertzbeat/alert/service/impl/TencentExternAlertService.java

Lines changed: 174 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,22 @@
1818
package org.apache.hertzbeat.alert.service.impl;
1919

2020
import lombok.extern.slf4j.Slf4j;
21+
import org.apache.commons.lang3.StringUtils;
2122
import org.apache.hertzbeat.alert.dto.TencentCloudExternAlert;
2223
import org.apache.hertzbeat.alert.reduce.AlarmCommonReduce;
2324
import org.apache.hertzbeat.alert.service.ExternAlertService;
25+
import org.apache.hertzbeat.common.constants.CommonConstants;
2426
import org.apache.hertzbeat.common.entity.alerter.SingleAlert;
2527
import org.apache.hertzbeat.common.util.JsonUtil;
2628
import org.springframework.beans.factory.annotation.Autowired;
2729
import org.springframework.stereotype.Service;
2830

31+
import java.text.ParseException;
32+
import java.text.SimpleDateFormat;
33+
import java.util.HashMap;
34+
import java.util.Map;
35+
import java.util.Objects;
36+
2937
/**
3038
* Default external alarm service impl
3139
*/
@@ -43,13 +51,177 @@ public void addExternAlert(String content) {
4351
log.warn("parse extern alert content failed! content: {}", content);
4452
return;
4553
}
46-
// todo convert TenCloudAlertReport to SingleAlert
47-
SingleAlert alert = new SingleAlert();
54+
SingleAlert alert = new TencentCloudAlertConverter().convert(report);
4855
alarmCommonReduce.reduceAndSendAlarm(alert);
4956
}
5057

5158
@Override
5259
public String supportSource() {
5360
return "tencent";
5461
}
62+
63+
/**
64+
* Converter: TencentCloud alert to SingleAlert
65+
*/
66+
public static class TencentCloudAlertConverter {
67+
68+
/**
69+
* Metric alert content template
70+
*/
71+
private final String metricTemplate = "Tencent Cloud Metric Alert: {metricShowName} {calcType} {calcValue}{calcUnit}, Current: {currentValue}{unit}";
72+
73+
/**
74+
* Event alert content template
75+
*/
76+
private final String eventTemplate = "Tencent Cloud Event Alert: {productShowName} - {eventShowName}";
77+
78+
79+
/**
80+
* Convert TencentCloud alert to SingleAlert
81+
*/
82+
public SingleAlert convert(TencentCloudExternAlert tencentAlert) {
83+
// Building basic information
84+
SingleAlert.SingleAlertBuilder builder = SingleAlert.builder()
85+
.status(convertStatus(tencentAlert.getAlarmStatus()))
86+
.startAt(parseTime(tencentAlert.getFirstOccurTime()))
87+
.activeAt(parseTime(tencentAlert.getFirstOccurTime()))
88+
.triggerTimes(1);
89+
// If it is a recovery state, set the end time.
90+
if ("0".equals(tencentAlert.getAlarmStatus())) {
91+
builder.endAt(parseTime(tencentAlert.getRecoverTime()));
92+
}
93+
94+
// Build labels
95+
Map<String, String> labels = new HashMap<>();
96+
buildLabels(labels, tencentAlert);
97+
98+
// Build annotations
99+
Map<String, String> annotations = new HashMap<>();
100+
buildAnnotations(annotations, tencentAlert);
101+
102+
// Build content
103+
String content = generateContent(tencentAlert);
104+
105+
return builder
106+
.labels(labels)
107+
.annotations(annotations)
108+
.content(content)
109+
.build();
110+
}
111+
112+
private void buildLabels(Map<String, String> labels, TencentCloudExternAlert alert) {
113+
labels.put("__source__", "tencent_cloud");
114+
labels.put("alert_type", alert.getAlarmType());
115+
if (TencentCloudExternAlert.METRIC.equals(alert.getAlarmType())) {
116+
labels.put("metric_name", alert.getAlarmPolicyInfo().getConditions().getMetricName());
117+
labels.put("namespace", alert.getAlarmObjInfo().getNamespace());
118+
} else {
119+
labels.put("event_name", alert.getAlarmPolicyInfo().getConditions().getEventName());
120+
labels.put("product_name", alert.getAlarmPolicyInfo().getConditions().getProductName());
121+
}
122+
}
123+
124+
private void buildAnnotations(Map<String, String> annotations, TencentCloudExternAlert alert) {
125+
TencentCloudExternAlert.AlarmPolicyInfo alarmPolicyInfo = alert.getAlarmPolicyInfo();
126+
TencentCloudExternAlert.AlarmObjInfo alarmObjInfo = alert.getAlarmObjInfo();
127+
TencentCloudExternAlert.Dimensions dimensions = alert.getAlarmObjInfo().getDimensions();
128+
129+
putIfNotNull(annotations, "policy_id", alarmPolicyInfo.getPolicyId());
130+
putIfNotNull(annotations, "policy_type", alarmPolicyInfo.getPolicyType());
131+
putIfNotNull(annotations, "policy_name", alarmPolicyInfo.getPolicyName());
132+
putIfNotNull(annotations, "policy_type_cname", alarmPolicyInfo.getPolicyTypeCname());
133+
134+
putIfNotNull(annotations, "namespace", alarmObjInfo.getNamespace());
135+
putIfNotNull(annotations, "region", alarmObjInfo.getRegion());
136+
putIfNotNull(annotations, "app_id", alarmObjInfo.getAppId());
137+
putIfNotNull(annotations, "uin", alarmObjInfo.getUin());
138+
139+
putIfNotNull(annotations, "instance_id", dimensions.getUnInstanceId());
140+
putIfNotNull(annotations, "obj_id", dimensions.getObjId());
141+
142+
}
143+
144+
/**
145+
* Generate alert content with template
146+
*/
147+
private String generateContent(TencentCloudExternAlert alert) {
148+
if (TencentCloudExternAlert.METRIC.equals(alert.getAlarmType())) {
149+
return generateMetricContent(alert);
150+
} else {
151+
return generateEventContent(alert);
152+
}
153+
}
154+
155+
/**
156+
* Generate metric alert content
157+
*/
158+
private String generateMetricContent(TencentCloudExternAlert alert) {
159+
TencentCloudExternAlert.Conditions conditions = alert.getAlarmPolicyInfo().getConditions();
160+
Map<String, String> params = new HashMap<>();
161+
162+
params.put("metricShowName", conditions.getMetricShowName());
163+
164+
if (StringUtils.isNotEmpty(conditions.getCalcType())
165+
&& StringUtils.isNotEmpty(conditions.getCalcValue())) {
166+
params.put("calcType", conditions.getCalcType());
167+
params.put("calcValue", conditions.getCalcValue());
168+
params.put("calcUnit", Objects.toString(conditions.getCalcUnit(), ""));
169+
} else {
170+
params.put("calcType", "");
171+
params.put("calcValue", "N/A");
172+
params.put("calcUnit", "");
173+
}
174+
175+
if (StringUtils.isNotEmpty(conditions.getCurrentValue())) {
176+
params.put("currentValue", conditions.getCurrentValue());
177+
params.put("unit", Objects.toString(conditions.getUnit(), ""));
178+
} else {
179+
params.put("currentValue", "N/A");
180+
params.put("unit", "");
181+
}
182+
return replacePlaceholders(metricTemplate, params);
183+
}
184+
185+
/**
186+
* Generate event alert content
187+
*/
188+
private String generateEventContent(TencentCloudExternAlert alert) {
189+
TencentCloudExternAlert.Conditions conditions = alert.getAlarmPolicyInfo().getConditions();
190+
Map<String, String> params = new HashMap<>();
191+
params.put("productShowName", conditions.getProductShowName());
192+
params.put("eventShowName", conditions.getEventShowName());
193+
return replacePlaceholders(eventTemplate, params);
194+
}
195+
196+
/**
197+
* Replace placeholders in template with actual values
198+
*/
199+
private String replacePlaceholders(String template, Map<String, String> params) {
200+
String result = template;
201+
for (Map.Entry<String, String> entry : params.entrySet()) {
202+
result = result.replace("{" + entry.getKey() + "}", Objects.toString(entry.getValue(), "NULL"));
203+
}
204+
return result;
205+
}
206+
207+
private String convertStatus(String alarmStatus) {
208+
return "1".equals(alarmStatus) ? CommonConstants.ALERT_STATUS_FIRING : CommonConstants.ALERT_STATUS_RESOLVED;
209+
}
210+
211+
private Long parseTime(String timeStr) {
212+
try {
213+
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
214+
return sdf.parse(timeStr).getTime();
215+
} catch (ParseException e) {
216+
log.error("Failed to parse time: {}", timeStr);
217+
throw new IllegalArgumentException("Failed to parse time: " + timeStr, e);
218+
}
219+
}
220+
221+
private void putIfNotNull(Map<String, String> map, String key, String value){
222+
if (StringUtils.isNotEmpty(value)){
223+
map.put(key, value);
224+
}
225+
}
226+
}
55227
}

web-app/src/app/routes/alert/alert-integration/alert-integration.component.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -67,12 +67,12 @@ export class AlertIntegrationComponent implements OnInit {
6767
id: 'skywalking',
6868
name: this.i18nSvc.fanyi('alert.integration.source.skywalking'),
6969
icon: 'assets/img/integration/skywalking.svg'
70+
},
71+
{
72+
id: 'tencent',
73+
name: this.i18nSvc.fanyi('alert.integration.source.tencent'),
74+
icon: 'assets/img/integration/tencent.svg'
7075
}
71-
// {
72-
// id: 'tencent',
73-
// name: this.i18nSvc.fanyi('alert.integration.source.tencent'),
74-
// icon: 'assets/img/integration/tencent.svg'
75-
// }
7676
];
7777

7878
selectedSource: DataSource | null = null;
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
>Send Tencent Cloud alerts to the HertzBeat alert platform via Webhook.
2+
3+
### Configure Tencent Cloud Alert Callback
4+
5+
#### Configure Notification Template
6+
1. Log in to [Tencent Cloud Observability Platform](https://console.cloud.tencent.com/monitorv2)
7+
2. Navigate to **Alarm Management** > **Alarm Configuration** > **Notification Templates**
8+
3. Click **Create Notification Template**
9+
4. On the new notification template page, fill in the basic information
10+
5. In the **API Callback** module, enter the HertzBeat Webhook API URL:
11+
```
12+
http://{your_system_host}/api/alerts/tencent
13+
```
14+
6. Save the notification template
15+
16+
#### Bind Alert Policy
17+
1. Go to **Alert Policy List**
18+
2. Select the alert policy that needs to be bound with the Webhook callback, click the policy name to enter the management page
19+
3. In the notification template configuration, bind the notification template created in the previous step
20+
4. Save the policy configuration
21+
22+
### Common Issues
23+
24+
#### Not Receiving Alerts
25+
- Ensure the Webhook URL is accessible from the public network
26+
- Check if there are request records in the server logs
27+
- Test if the Webhook is available on the Tencent Cloud notification template page
28+
29+
#### Alerts Not Triggered
30+
- Ensure the alert policy conditions are correct and the notification template is bound
31+
- Check the alert history on the Tencent Cloud monitoring page to confirm if the policy was triggered
32+
33+
For more information, please refer to [Tencent Cloud Alert Configuration Documentation](https://cloud.tencent.com/document/product/248/50409)
34+
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
>将腾讯云的告警通过 Webhook 方式发送到 HertzBeat 告警平台。
2+
3+
### 配置腾讯云告警回调
4+
5+
#### 进入通知模板配置
6+
1. 登录 [腾讯云可观测平台](https://console.cloud.tencent.com/monitorv2)
7+
2. 进入 **告警管理** > **告警配置** > **通知模板**
8+
3. 单击 **新建通知模板**
9+
4. 在新建通知模板页面,填写基础信息
10+
5.**接口回调** 模块中,填写 HertzBeat 提供的 Webhook 接口地址 URL:
11+
```
12+
http://{your_system_host}/api/alerts/tencent
13+
```
14+
6. 保存通知模板
15+
16+
#### 绑定告警策略
17+
1. 进入 **告警策略列表**
18+
2. 选择需要绑定 Webhook 回调的告警策略,点击策略名称进入管理页面
19+
3. 在通知模板配置项中,绑定上一步创建的通知模板
20+
4. 保存策略配置
21+
22+
### 常见问题
23+
24+
#### 未收到告警
25+
- 确保 Webhook URL 可以被公网访问
26+
- 检查服务器日志是否有请求记录
27+
- 在腾讯云通知模板页面测试 Webhook 是否可用
28+
29+
#### 告警未触发
30+
- 确保告警策略的条件正确,并已绑定通知模板
31+
- 在腾讯云监控页面查看告警历史,确保策略被触发
32+
33+
更多信息请参考 [腾讯云告警配置文档](https://cloud.tencent.com/document/product/248/50409)
34+

web-app/src/assets/doc/alert-integration/tencent.zh-TW.md

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,34 @@
1-
## 验证配置
1+
>將騰訊雲的告警通過 Webhook 方式發送到 HertzBeat 告警平台。
22
3-
1. 确保 Prometheus 配置正确并重新加载配置
4-
2. 触发一个测试告警
5-
3. 在 HertzBeat 告警中心查看是否收到告警
3+
### 配置騰訊雲告警回調
64

7-
更多信息请参考 [Prometheus 官方文档](https://prometheus.io/docs/alerting/latest/configuration/)
5+
#### 進入通知模板配置
6+
1. 登錄 [騰訊雲可觀測平台](https://console.cloud.tencent.com/monitorv2)
7+
2. 進入 **告警管理** > **告警配置** > **通知模板**
8+
3. 單擊 **新建通知模板**
9+
4. 在新建通知模板頁面,填寫基礎信息
10+
5.**接口回調** 模塊中,填寫 HertzBeat 提供的 Webhook 接口地址 URL:
11+
```
12+
http://{your_system_host}/api/alerts/tencent
13+
```
14+
6. 保存通知模板
15+
16+
#### 綁定告警策略
17+
1. 進入 **告警策略列表**
18+
2. 選擇需要綁定 Webhook 回調的告警策略,點擊策略名稱進入管理頁面
19+
3. 在通知模板配置項中,綁定上一步創建的通知模板
20+
4. 保存策略配置
21+
22+
### 常見問題
23+
24+
#### 未收到告警
25+
- 確保 Webhook URL 可以被公網訪問
26+
- 檢查服務器日誌是否有請求記錄
27+
- 在騰訊雲通知模板頁面測試 Webhook 是否可用
28+
29+
#### 告警未觸發
30+
- 確保告警策略的條件正確,並已綁定通知模板
31+
- 在騰訊雲監控頁面查看告警歷史,確保策略被觸發
32+
33+
更多信息請參考 [騰訊雲告警配置文檔](https://cloud.tencent.com/document/product/248/50409)
834

9-
## Alert Rule Example

0 commit comments

Comments
 (0)