This is an automated email from the ASF dual-hosted git repository.
wusheng pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/skywalking.git
The following commit(s) were added to refs/heads/master by this push:
new 4e43f22 Support Alarm to feishu (#6193)
4e43f22 is described below
commit 4e43f227b4a579b7bd48996974b5872d30c191f1
Author: HendSame <[email protected]>
AuthorDate: Mon Jan 18 09:24:20 2021 +0800
Support Alarm to feishu (#6193)
---
CHANGES.md | 1 +
docs/en/setup/backend/backend-alarm.md | 19 ++
.../core/alarm/provider/AlarmRulesWatcher.java | 5 +
.../server/core/alarm/provider/NotifyHandler.java | 2 +
.../oap/server/core/alarm/provider/Rules.java | 2 +
.../server/core/alarm/provider/RulesReader.java | 33 +++-
.../alarm/provider/feishu/FeishuHookCallback.java | 173 +++++++++++++++++
.../{Rules.java => feishu/FeishuSettings.java} | 40 ++--
.../core/alarm/provider/RulesReaderTest.java | 10 +
.../provider/feishu/FeishuHookCallbackTest.java | 211 +++++++++++++++++++++
.../src/test/resources/alarm-settings.yml | 14 ++
.../src/main/resources/alarm-settings.yml | 14 ++
12 files changed, 500 insertions(+), 24 deletions(-)
diff --git a/CHANGES.md b/CHANGES.md
index 51d722c..694be60 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -67,6 +67,7 @@ Release Notes.
* Fix bug endpoint name grouping not work due to setting service name and
endpoint name out of order.
* Fix receiver analysis error count metrics
* Log collecting and query implementation
+* Support Alarm to feishu
#### UI
* Fix un-removed tags in trace query.
diff --git a/docs/en/setup/backend/backend-alarm.md
b/docs/en/setup/backend/backend-alarm.md
index e3a730d..978abc4 100644
--- a/docs/en/setup/backend/backend-alarm.md
+++ b/docs/en/setup/backend/backend-alarm.md
@@ -237,6 +237,25 @@ dingtalkHooks:
secret: dummysecret
```
+## Feishu Hook
+To do this you need to follow the [Feishu Webhooks
guide](https://www.feishu.cn/hc/zh-cn/articles/360024984973) and create new
Webhooks.
+For security issue, you can config optional secret for individual webhook url.
+if you want to at someone, you can config `ats` which is the feishu's user_id
and separated by "," .
+The alarm message will send through HTTP post by `application/json` content
type if you configured Feishu Webhooks as following:
+```yml
+feishuHooks:
+ textTemplate: |-
+ {
+ "msg_type": "text",
+ "content": {
+ "text": "Apache SkyWalking Alarm: \n %s."
+ },
+ "ats":"feishu_user_id_1,feishu_user_id_2"
+ }
+ webhooks:
+ - url: https://open.feishu.cn/open-apis/bot/v2/hook/dummy_token
+ secret: dummysecret
+```
## Update the settings dynamically
Since 6.5.0, the alarm settings can be updated dynamically at runtime by
[Dynamic Configuration](dynamic-config.md),
diff --git
a/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/AlarmRulesWatcher.java
b/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/AlarmRulesWatcher.java
index af6364a..6884905 100644
---
a/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/AlarmRulesWatcher.java
+++
b/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/AlarmRulesWatcher.java
@@ -31,6 +31,7 @@ import
org.apache.skywalking.oap.server.core.alarm.AlarmModule;
import
org.apache.skywalking.oap.server.core.alarm.provider.dingtalk.DingtalkSettings;
import
org.apache.skywalking.oap.server.core.alarm.provider.expression.Expression;
import
org.apache.skywalking.oap.server.core.alarm.provider.expression.ExpressionContext;
+import
org.apache.skywalking.oap.server.core.alarm.provider.feishu.FeishuSettings;
import
org.apache.skywalking.oap.server.core.alarm.provider.grpc.GRPCAlarmSetting;
import
org.apache.skywalking.oap.server.core.alarm.provider.slack.SlackSettings;
import
org.apache.skywalking.oap.server.core.alarm.provider.wechat.WechatSettings;
@@ -134,4 +135,8 @@ public class AlarmRulesWatcher extends ConfigChangeWatcher {
return this.rules.getDingtalks();
}
+ public FeishuSettings getFeishuSettings() {
+ return this.rules.getFeishus();
+ }
+
}
diff --git
a/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/NotifyHandler.java
b/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/NotifyHandler.java
index 755bc08..3b295dd 100644
---
a/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/NotifyHandler.java
+++
b/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/NotifyHandler.java
@@ -32,6 +32,7 @@ import
org.apache.skywalking.oap.server.core.alarm.MetricsNotify;
import org.apache.skywalking.oap.server.core.alarm.ServiceInstanceMetaInAlarm;
import org.apache.skywalking.oap.server.core.alarm.ServiceMetaInAlarm;
import
org.apache.skywalking.oap.server.core.alarm.provider.dingtalk.DingtalkHookCallback;
+import
org.apache.skywalking.oap.server.core.alarm.provider.feishu.FeishuHookCallback;
import org.apache.skywalking.oap.server.core.alarm.provider.grpc.GRPCCallback;
import
org.apache.skywalking.oap.server.core.alarm.provider.slack.SlackhookCallback;
import
org.apache.skywalking.oap.server.core.alarm.provider.wechat.WechatHookCallback;
@@ -164,6 +165,7 @@ public class NotifyHandler implements MetricsNotify {
allCallbacks.add(new SlackhookCallback(alarmRulesWatcher));
allCallbacks.add(new WechatHookCallback(alarmRulesWatcher));
allCallbacks.add(new DingtalkHookCallback(alarmRulesWatcher));
+ allCallbacks.add(new FeishuHookCallback(alarmRulesWatcher));
core.start(allCallbacks);
}
}
diff --git
a/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/Rules.java
b/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/Rules.java
index 7831bd6..1e93acd 100644
---
a/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/Rules.java
+++
b/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/Rules.java
@@ -24,6 +24,7 @@ import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import
org.apache.skywalking.oap.server.core.alarm.provider.dingtalk.DingtalkSettings;
+import
org.apache.skywalking.oap.server.core.alarm.provider.feishu.FeishuSettings;
import
org.apache.skywalking.oap.server.core.alarm.provider.grpc.GRPCAlarmSetting;
import
org.apache.skywalking.oap.server.core.alarm.provider.slack.SlackSettings;
import
org.apache.skywalking.oap.server.core.alarm.provider.wechat.WechatSettings;
@@ -39,6 +40,7 @@ public class Rules {
private WechatSettings wecchats;
private List<CompositeAlarmRule> compositeRules;
private DingtalkSettings dingtalks;
+ private FeishuSettings feishus;
public Rules() {
this.rules = new ArrayList<>();
diff --git
a/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/RulesReader.java
b/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/RulesReader.java
index 196e7f7..fea2e8c 100644
---
a/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/RulesReader.java
+++
b/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/RulesReader.java
@@ -25,6 +25,7 @@ import java.util.List;
import java.util.Map;
import java.util.Objects;
import
org.apache.skywalking.oap.server.core.alarm.provider.dingtalk.DingtalkSettings;
+import
org.apache.skywalking.oap.server.core.alarm.provider.feishu.FeishuSettings;
import
org.apache.skywalking.oap.server.core.alarm.provider.grpc.GRPCAlarmSetting;
import
org.apache.skywalking.oap.server.core.alarm.provider.slack.SlackSettings;
import
org.apache.skywalking.oap.server.core.alarm.provider.wechat.WechatSettings;
@@ -61,6 +62,7 @@ public class RulesReader {
readWechatConfig(rules);
readCompositeRuleConfig(rules);
readDingtalkConfig(rules);
+ readFeishuConfig(rules);
}
return rules;
}
@@ -90,9 +92,9 @@ public class RulesReader {
alarmRule.setIncludeNamesRegex((String)
settings.getOrDefault("include-names-regex", ""));
alarmRule.setExcludeNamesRegex((String)
settings.getOrDefault("exclude-names-regex", ""));
alarmRule.setIncludeLabels(
- (ArrayList) settings.getOrDefault("include-labels", new
ArrayList(0)));
+ (ArrayList) settings.getOrDefault("include-labels",
new ArrayList(0)));
alarmRule.setExcludeLabels(
- (ArrayList) settings.getOrDefault("exclude-labels", new
ArrayList(0)));
+ (ArrayList) settings.getOrDefault("exclude-labels",
new ArrayList(0)));
alarmRule.setIncludeLabelsRegex((String)
settings.getOrDefault("include-labels-regex", ""));
alarmRule.setExcludeLabelsRegex((String)
settings.getOrDefault("exclude-labels-regex", ""));
alarmRule.setThreshold(settings.get("threshold").toString());
@@ -103,8 +105,8 @@ public class RulesReader {
alarmRule.setSilencePeriod((Integer)
settings.getOrDefault("silence-period", alarmRule.getPeriod()));
alarmRule.setOnlyAsCondition((Boolean)
settings.getOrDefault("only-as-condition", false));
alarmRule.setMessage(
- (String) settings.getOrDefault("message", "Alarm caused by
Rule " + alarmRule
- .getAlarmRuleName()));
+ (String) settings.getOrDefault("message", "Alarm
caused by Rule " + alarmRule
+ .getAlarmRuleName()));
rules.getRules().add(alarmRule);
}
@@ -200,7 +202,7 @@ public class RulesReader {
}
compositeAlarmRule.setExpression(expression);
compositeAlarmRule.setMessage(
- (String) settings.getOrDefault("message", "Alarm caused by
Rule " + ruleName));
+ (String) settings.getOrDefault("message", "Alarm
caused by Rule " + ruleName));
rules.getCompositeRules().add(compositeAlarmRule);
}
});
@@ -226,4 +228,25 @@ public class RulesReader {
rules.setDingtalks(dingtalkSettings);
}
}
+
+ /**
+ * Read feishu hook config into {@link FeishuSettings}
+ */
+ private void readFeishuConfig(Rules rules) {
+ Map feishuConfig = (Map) yamlData.get("feishuHooks");
+ if (feishuConfig != null) {
+ FeishuSettings feishuSettings = new FeishuSettings();
+ Object textTemplate = feishuConfig.getOrDefault("textTemplate",
"");
+ feishuSettings.setTextTemplate((String) textTemplate);
+ List<Map<String, Object>> wechatWebhooks = (List<Map<String,
Object>>) feishuConfig.get("webhooks");
+ if (wechatWebhooks != null) {
+ wechatWebhooks.forEach(wechatWebhook -> {
+ Object secret = wechatWebhook.getOrDefault("secret", "");
+ Object url = wechatWebhook.getOrDefault("url", "");
+ feishuSettings.getWebhooks().add(new
FeishuSettings.WebHookUrl((String) secret, (String) url));
+ });
+ }
+ rules.setFeishus(feishuSettings);
+ }
+ }
}
diff --git
a/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/feishu/FeishuHookCallback.java
b/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/feishu/FeishuHookCallback.java
new file mode 100644
index 0000000..bff3a55
--- /dev/null
+++
b/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/feishu/FeishuHookCallback.java
@@ -0,0 +1,173 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.apache.skywalking.oap.server.core.alarm.provider.feishu;
+
+import com.google.gson.Gson;
+import com.google.gson.JsonObject;
+import io.netty.handler.codec.http.HttpHeaderValues;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.codec.binary.Base64;
+import org.apache.http.HttpHeaders;
+import org.apache.http.HttpStatus;
+import org.apache.http.StatusLine;
+import org.apache.http.client.config.RequestConfig;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.entity.ContentType;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClients;
+import org.apache.http.util.EntityUtils;
+import org.apache.skywalking.apm.util.StringUtil;
+import org.apache.skywalking.oap.server.core.alarm.AlarmCallback;
+import org.apache.skywalking.oap.server.core.alarm.AlarmMessage;
+import org.apache.skywalking.oap.server.core.alarm.provider.AlarmRulesWatcher;
+import javax.crypto.Mac;
+import javax.crypto.spec.SecretKeySpec;
+import java.io.IOException;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/**
+ * Use SkyWalking alarm feishu webhook API.
+ */
+@Slf4j
+public class FeishuHookCallback implements AlarmCallback {
+
+ private static final int HTTP_CONNECT_TIMEOUT = 1000;
+ private static final int HTTP_CONNECTION_REQUEST_TIMEOUT = 1000;
+ private static final int HTTP_SOCKET_TIMEOUT = 10000;
+ private AlarmRulesWatcher alarmRulesWatcher;
+ private RequestConfig requestConfig;
+
+ public FeishuHookCallback(final AlarmRulesWatcher alarmRulesWatcher) {
+ this.alarmRulesWatcher = alarmRulesWatcher;
+ this.requestConfig = RequestConfig.custom()
+ .setConnectTimeout(HTTP_CONNECT_TIMEOUT)
+ .setConnectionRequestTimeout(HTTP_CONNECTION_REQUEST_TIMEOUT)
+ .setSocketTimeout(HTTP_SOCKET_TIMEOUT)
+ .build();
+ }
+
+ /**
+ * Send alarm message if the settings not empty
+ */
+ @Override
+ public void doAlarm(List<AlarmMessage> alarmMessages) {
+ if (this.alarmRulesWatcher.getFeishuSettings() == null ||
this.alarmRulesWatcher.getFeishuSettings().getWebhooks().isEmpty()) {
+ return;
+ }
+ CloseableHttpClient httpClient = HttpClients.custom().build();
+ try {
+ FeishuSettings feishuSettings =
this.alarmRulesWatcher.getFeishuSettings();
+ feishuSettings.getWebhooks().forEach(webHookUrl -> {
+ alarmMessages.forEach(alarmMessage -> {
+ String requestBody = getRequestBody(webHookUrl,
alarmMessage);
+ sendAlarmMessage(httpClient, webHookUrl.getUrl(),
requestBody);
+ });
+ });
+ } finally {
+ try {
+ httpClient.close();
+ } catch (IOException e) {
+ log.error(e.getMessage(), e);
+ }
+ }
+ }
+
+ /**
+ * deal requestBody,if has sign set the sign
+ */
+ private String getRequestBody(FeishuSettings.WebHookUrl webHookUrl,
AlarmMessage alarmMessage) {
+ String requestBody = String.format(
+ this.alarmRulesWatcher.getFeishuSettings().getTextTemplate(),
alarmMessage.getAlarmMessage()
+ );
+ Gson gson = new Gson();
+ JsonObject jsonObject = gson.fromJson(requestBody, JsonObject.class);
+ Map<String, Object> content = buildContent(jsonObject);
+ if (!StringUtil.isBlank(webHookUrl.getSecret())) {
+ Long timestamp = System.currentTimeMillis() / 1000;
+ content.put("timestamp", timestamp);
+ try {
+ content.put("sign", sign(timestamp, webHookUrl.getSecret()));
+ } catch (NoSuchAlgorithmException | InvalidKeyException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ return gson.toJson(content);
+ }
+
+ /**
+ * build content,if has ats someone set the ats
+ */
+ private Map<String, Object> buildContent(JsonObject jsonObject) {
+ Map<String, Object> content = new HashMap<>();
+ content.put("msg_type", jsonObject.get("msg_type").getAsString());
+ if (jsonObject.get("ats") != null) {
+ String ats = jsonObject.get("ats").getAsString();
+ String text =
jsonObject.get("content").getAsJsonObject().get("text").getAsString();
+ List<String> collect = Arrays.stream(ats.split(","))
+ .map(String::trim).collect(Collectors.toList());
+ for (String userId : collect) {
+ text += "<at user_id=\"" + userId + "\"></at>";
+ }
+ jsonObject.get("content").getAsJsonObject().addProperty("text",
text);
+ }
+ content.put("content", jsonObject.get("content").getAsJsonObject());
+ return content;
+ }
+
+ /**
+ * Sign webhook url using HmacSHA256 algorithm
+ */
+ private String sign(final Long timestamp, String secret) throws
NoSuchAlgorithmException, InvalidKeyException {
+ String stringToSign = timestamp + "\n" + secret;
+ Mac mac = Mac.getInstance("HmacSHA256");
+ mac.init(new SecretKeySpec(stringToSign.getBytes(), "HmacSHA256"));
+ byte[] signData = mac.doFinal();
+ return Base64.encodeBase64String(signData);
+ }
+
+ /**
+ * Send alarm message to remote endpoint
+ */
+ private void sendAlarmMessage(CloseableHttpClient httpClient, String url,
String requestBody) {
+ try {
+ HttpPost post = new HttpPost(url);
+ post.setConfig(requestConfig);
+ post.setHeader(HttpHeaders.ACCEPT,
HttpHeaderValues.APPLICATION_JSON.toString());
+ post.setHeader(HttpHeaders.CONTENT_TYPE,
HttpHeaderValues.APPLICATION_JSON.toString());
+ StringEntity entity = new StringEntity(requestBody,
ContentType.APPLICATION_JSON);
+ post.setEntity(entity);
+ CloseableHttpResponse httpResponse = httpClient.execute(post);
+ StatusLine statusLine = httpResponse.getStatusLine();
+ if (statusLine != null && statusLine.getStatusCode() !=
HttpStatus.SC_OK) {
+ log.error("send feishu alarm to {} failure. Response code: {},
Response content: {}", url, statusLine.getStatusCode(),
+ EntityUtils.toString(httpResponse.getEntity()));
+ }
+ } catch (Throwable e) {
+ log.error("send feishu alarm to {} failure.", url, e);
+ }
+ }
+}
diff --git
a/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/Rules.java
b/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/feishu/FeishuSettings.java
similarity index 53%
copy from
oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/Rules.java
copy to
oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/feishu/FeishuSettings.java
index 7831bd6..fa66b95 100644
---
a/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/Rules.java
+++
b/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/feishu/FeishuSettings.java
@@ -16,33 +16,35 @@
*
*/
-package org.apache.skywalking.oap.server.core.alarm.provider;
+package org.apache.skywalking.oap.server.core.alarm.provider.feishu;
-import java.util.ArrayList;
-import java.util.List;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
import lombok.Getter;
+import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
-import
org.apache.skywalking.oap.server.core.alarm.provider.dingtalk.DingtalkSettings;
-import
org.apache.skywalking.oap.server.core.alarm.provider.grpc.GRPCAlarmSetting;
-import
org.apache.skywalking.oap.server.core.alarm.provider.slack.SlackSettings;
-import
org.apache.skywalking.oap.server.core.alarm.provider.wechat.WechatSettings;
+import java.util.ArrayList;
+import java.util.List;
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
@Setter
@Getter
@ToString
-public class Rules {
- private List<AlarmRule> rules;
- private List<String> webhooks;
- private GRPCAlarmSetting grpchookSetting;
- private SlackSettings slacks;
- private WechatSettings wecchats;
- private List<CompositeAlarmRule> compositeRules;
- private DingtalkSettings dingtalks;
+public class FeishuSettings {
+
+ private String textTemplate;
+ @Builder.Default
+ private List<WebHookUrl> webhooks = new ArrayList<>();
- public Rules() {
- this.rules = new ArrayList<>();
- this.webhooks = new ArrayList<>();
- this.compositeRules = new ArrayList<>();
+ @AllArgsConstructor
+ @Setter
+ @Getter
+ @ToString
+ public static class WebHookUrl {
+ private final String secret;
+ private final String url;
}
}
diff --git
a/oap-server/server-alarm-plugin/src/test/java/org/apache/skywalking/oap/server/core/alarm/provider/RulesReaderTest.java
b/oap-server/server-alarm-plugin/src/test/java/org/apache/skywalking/oap/server/core/alarm/provider/RulesReaderTest.java
index 9a81990..a53850b 100644
---
a/oap-server/server-alarm-plugin/src/test/java/org/apache/skywalking/oap/server/core/alarm/provider/RulesReaderTest.java
+++
b/oap-server/server-alarm-plugin/src/test/java/org/apache/skywalking/oap/server/core/alarm/provider/RulesReaderTest.java
@@ -19,6 +19,7 @@
package org.apache.skywalking.oap.server.core.alarm.provider;
import
org.apache.skywalking.oap.server.core.alarm.provider.dingtalk.DingtalkSettings;
+import
org.apache.skywalking.oap.server.core.alarm.provider.feishu.FeishuSettings;
import
org.apache.skywalking.oap.server.core.alarm.provider.grpc.GRPCAlarmSetting;
import
org.apache.skywalking.oap.server.core.alarm.provider.slack.SlackSettings;
import
org.apache.skywalking.oap.server.core.alarm.provider.wechat.WechatSettings;
@@ -86,5 +87,14 @@ public class RulesReaderTest {
assertThat(webHookUrls.get(0).getSecret(), is("dummysecret"));
assertThat(webHookUrls.get(1).getUrl(),
is("https://oapi.dingtalk.com/robot/send?access_token=dummy_token2"));
assertNull(webHookUrls.get(1).getSecret());
+
+ FeishuSettings feishuSettings = rules.getFeishus();
+ assertThat(feishuSettings.getTextTemplate(), any(String.class));
+ List<FeishuSettings.WebHookUrl> feishuSettingsWebhooks =
feishuSettings.getWebhooks();
+ assertThat(feishuSettingsWebhooks.size(), is(2));
+ assertThat(feishuSettingsWebhooks.get(0).getUrl(),
is("https://open.feishu.cn/open-apis/bot/v2/hook/dummy_token"));
+ assertThat(feishuSettingsWebhooks.get(0).getSecret(),
is("dummysecret"));
+ assertThat(feishuSettingsWebhooks.get(1).getUrl(),
is("https://open.feishu.cn/open-apis/bot/v2/hook/dummy_token2"));
+ assertNull(feishuSettingsWebhooks.get(1).getSecret());
}
}
diff --git
a/oap-server/server-alarm-plugin/src/test/java/org/apache/skywalking/oap/server/core/alarm/provider/feishu/FeishuHookCallbackTest.java
b/oap-server/server-alarm-plugin/src/test/java/org/apache/skywalking/oap/server/core/alarm/provider/feishu/FeishuHookCallbackTest.java
new file mode 100644
index 0000000..41b336b
--- /dev/null
+++
b/oap-server/server-alarm-plugin/src/test/java/org/apache/skywalking/oap/server/core/alarm/provider/feishu/FeishuHookCallbackTest.java
@@ -0,0 +1,211 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.apache.skywalking.oap.server.core.alarm.provider.feishu;
+
+import com.google.gson.Gson;
+import com.google.gson.JsonObject;
+import org.apache.skywalking.apm.util.StringUtil;
+import org.apache.skywalking.oap.server.core.alarm.AlarmMessage;
+import org.apache.skywalking.oap.server.core.alarm.provider.AlarmRulesWatcher;
+import org.apache.skywalking.oap.server.core.alarm.provider.Rules;
+import org.apache.skywalking.oap.server.core.source.DefaultScopeDefine;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.servlet.ServletHolder;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import javax.servlet.Servlet;
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.InetSocketAddress;
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.junit.Assert.assertTrue;
+
+public class FeishuHookCallbackTest implements Servlet {
+
+ private Server server;
+ private int port;
+ private volatile boolean isSuccess = false;
+ private int count;
+ private volatile boolean checkSign = false;
+ private final String secret = "dummy-secret";
+
+ @Before
+ public void init() throws Exception {
+ server = new Server(new InetSocketAddress("127.0.0.1", 0));
+ ServletContextHandler servletContextHandler = new
ServletContextHandler(ServletContextHandler.NO_SESSIONS);
+ servletContextHandler.setContextPath("/feishuhook");
+ server.setHandler(servletContextHandler);
+ ServletHolder servletHolder = new ServletHolder();
+ servletHolder.setServlet(this);
+ servletContextHandler.addServlet(servletHolder, "/receiveAlarm");
+ server.start();
+ port = server.getURI().getPort();
+ assertTrue(port > 0);
+ }
+
+ @Test
+ public void testFeishuWebhookWithoutSign() {
+ List<FeishuSettings.WebHookUrl> webHooks = new ArrayList<>();
+ webHooks.add(new FeishuSettings.WebHookUrl("", "http://127.0.0.1:" +
port + "/feishuhook/receiveAlarm?token=dummy_token"));
+ Rules rules = new Rules();
+ String template =
"{\"msg_type\":\"text\",\"content\":{\"text\":\"Skywaling alarm: %s\"}}";
+
rules.setFeishus(FeishuSettings.builder().webhooks(webHooks).textTemplate(template).build());
+
+ AlarmRulesWatcher alarmRulesWatcher = new AlarmRulesWatcher(rules,
null);
+ FeishuHookCallback feishuHookCallback = new
FeishuHookCallback(alarmRulesWatcher);
+ List<AlarmMessage> alarmMessages = new ArrayList<>(2);
+ AlarmMessage alarmMessage = new AlarmMessage();
+ alarmMessage.setScopeId(DefaultScopeDefine.ALL);
+ alarmMessage.setRuleName("service_resp_time_rule");
+ alarmMessage.setAlarmMessage("alarmMessage with
[DefaultScopeDefine.All]");
+ alarmMessages.add(alarmMessage);
+ AlarmMessage anotherAlarmMessage = new AlarmMessage();
+ anotherAlarmMessage.setRuleName("service_resp_time_rule_2");
+ anotherAlarmMessage.setScopeId(DefaultScopeDefine.ENDPOINT);
+ anotherAlarmMessage.setAlarmMessage("anotherAlarmMessage with
[DefaultScopeDefine.Endpoint]");
+ alarmMessages.add(anotherAlarmMessage);
+ feishuHookCallback.doAlarm(alarmMessages);
+ Assert.assertTrue(isSuccess);
+ }
+
+ @Test
+ public void testFeishuWebhookWithSign() {
+ checkSign = true;
+ List<FeishuSettings.WebHookUrl> webHooks = new ArrayList<>();
+ webHooks.add(new FeishuSettings.WebHookUrl(secret, "http://127.0.0.1:"
+ port + "/feishuhook/receiveAlarm?token=dummy_token"));
+ Rules rules = new Rules();
+ String template =
"{\"msg_type\":\"text\",\"content\":{\"text\":\"Skywaling alarm: %s\"}}";
+
rules.setFeishus(FeishuSettings.builder().webhooks(webHooks).textTemplate(template).build());
+
+ AlarmRulesWatcher alarmRulesWatcher = new AlarmRulesWatcher(rules,
null);
+ FeishuHookCallback feishuHookCallback = new
FeishuHookCallback(alarmRulesWatcher);
+ List<AlarmMessage> alarmMessages = new ArrayList<>(2);
+ AlarmMessage alarmMessage = new AlarmMessage();
+ alarmMessage.setScopeId(DefaultScopeDefine.ALL);
+ alarmMessage.setRuleName("service_resp_time_rule");
+ alarmMessage.setAlarmMessage("alarmMessage with
[DefaultScopeDefine.All]");
+ alarmMessages.add(alarmMessage);
+ AlarmMessage anotherAlarmMessage = new AlarmMessage();
+ anotherAlarmMessage.setRuleName("service_resp_time_rule_2");
+ anotherAlarmMessage.setScopeId(DefaultScopeDefine.ENDPOINT);
+ anotherAlarmMessage.setAlarmMessage("anotherAlarmMessage with
[DefaultScopeDefine.Endpoint]");
+ alarmMessages.add(anotherAlarmMessage);
+ feishuHookCallback.doAlarm(alarmMessages);
+ Assert.assertTrue(isSuccess);
+ }
+
+ @Test
+ public void testFeishuWebhookWithSignAndAt() {
+ checkSign = true;
+ List<FeishuSettings.WebHookUrl> webHooks = new ArrayList<>();
+ webHooks.add(new FeishuSettings.WebHookUrl(secret, "http://127.0.0.1:"
+ port + "/feishuhook/receiveAlarm?token=dummy_token"));
+ Rules rules = new Rules();
+ String template =
"{\"msg_type\":\"text\",\"content\":{\"text\":\"Skywaling alarm:
%s\"},\"ats\":\"123\"}";
+
rules.setFeishus(FeishuSettings.builder().webhooks(webHooks).textTemplate(template).build());
+
+ AlarmRulesWatcher alarmRulesWatcher = new AlarmRulesWatcher(rules,
null);
+ FeishuHookCallback feishuHookCallback = new
FeishuHookCallback(alarmRulesWatcher);
+ List<AlarmMessage> alarmMessages = new ArrayList<>(2);
+ AlarmMessage alarmMessage = new AlarmMessage();
+ alarmMessage.setScopeId(DefaultScopeDefine.ALL);
+ alarmMessage.setRuleName("service_resp_time_rule");
+ alarmMessage.setAlarmMessage("alarmMessage with
[DefaultScopeDefine.All]");
+ alarmMessages.add(alarmMessage);
+ AlarmMessage anotherAlarmMessage = new AlarmMessage();
+ anotherAlarmMessage.setRuleName("service_resp_time_rule_2");
+ anotherAlarmMessage.setScopeId(DefaultScopeDefine.ENDPOINT);
+ anotherAlarmMessage.setAlarmMessage("anotherAlarmMessage with
[DefaultScopeDefine.Endpoint]");
+ alarmMessages.add(anotherAlarmMessage);
+ feishuHookCallback.doAlarm(alarmMessages);
+ Assert.assertTrue(isSuccess);
+ }
+
+ @After
+ public void stop() throws Exception {
+ server.stop();
+ }
+
+ @Override
+ public void init(ServletConfig servletConfig) throws ServletException {
+ }
+
+ @Override
+ public ServletConfig getServletConfig() {
+ return null;
+ }
+
+ @Override
+ public void service(ServletRequest request, ServletResponse response)
throws ServletException, IOException {
+ HttpServletRequest httpServletRequest = (HttpServletRequest) request;
+ if (httpServletRequest.getContentType().equals("application/json")) {
+ InputStream inputStream = request.getInputStream();
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ byte[] buffer = new byte[2048];
+ int readCntOnce;
+
+ while ((readCntOnce = inputStream.read(buffer)) >= 0) {
+ out.write(buffer, 0, readCntOnce);
+ }
+
+ JsonObject jsonObject = new Gson().fromJson(new
String(out.toByteArray()), JsonObject.class);
+ String type = jsonObject.get("msg_type").getAsString();
+ if (checkSign) {
+ String timestamp = jsonObject.get("timestamp").getAsString();
+ String sign = jsonObject.get("sign").getAsString();
+ if (StringUtil.isEmpty(timestamp) || StringUtil.isEmpty(sign))
{
+ ((HttpServletResponse) response).setStatus(500);
+ return;
+ }
+ }
+ if (type.equalsIgnoreCase("text")) {
+ ((HttpServletResponse) response).setStatus(200);
+ count = count + 1;
+ if (count == 2) {
+ isSuccess = true;
+ }
+ return;
+ }
+
+ ((HttpServletResponse) response).setStatus(500);
+ }
+ }
+
+ @Override
+ public String getServletInfo() {
+ return null;
+ }
+
+ @Override
+ public void destroy() {
+ }
+
+}
diff --git
a/oap-server/server-alarm-plugin/src/test/resources/alarm-settings.yml
b/oap-server/server-alarm-plugin/src/test/resources/alarm-settings.yml
index 9dd339f..0b31ddc 100755
--- a/oap-server/server-alarm-plugin/src/test/resources/alarm-settings.yml
+++ b/oap-server/server-alarm-plugin/src/test/resources/alarm-settings.yml
@@ -107,3 +107,17 @@ dingtalkHooks:
secret: dummysecret
- url: https://oapi.dingtalk.com/robot/send?access_token=dummy_token2
secret:
+
+feishuHooks:
+ textTemplate: |-
+ {
+ "msg_type": "text",
+ "content": {
+ "text": "Apache SkyWalking Alarm: \n %s."
+ }
+ }
+ webhooks:
+ - url: https://open.feishu.cn/open-apis/bot/v2/hook/dummy_token
+ secret: dummysecret
+ - url: https://open.feishu.cn/open-apis/bot/v2/hook/dummy_token2
+ secret:
diff --git a/oap-server/server-bootstrap/src/main/resources/alarm-settings.yml
b/oap-server/server-bootstrap/src/main/resources/alarm-settings.yml
index f78427d..b7fa979 100755
--- a/oap-server/server-bootstrap/src/main/resources/alarm-settings.yml
+++ b/oap-server/server-bootstrap/src/main/resources/alarm-settings.yml
@@ -81,3 +81,17 @@ dingtalkHooks:
webhooks:
# - url: https://oapi.dingtalk.com/robot/send?access_token=dummy_token
# secret: dummysecret
+
+feishuHooks:
+ textTemplate: |-
+ {
+ "msg_type": "text",
+ # at someone with feishu_user_ids
+ # "ats": "feishu_user_id_1,feishu_user_id_2",
+ "content": {
+ "text": "Apache SkyWalking Alarm: \n %s."
+ }
+ }
+ webhooks:
+# - url: https://open.feishu.cn/open-apis/bot/v2/hook/dummy_token
+# secret: dummysecret