Support Alarm to feishu (#6193)
This commit is contained in:
parent
9021e67912
commit
4e43f227b4
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<>();
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* 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 lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Setter
|
||||
@Getter
|
||||
@ToString
|
||||
public class FeishuSettings {
|
||||
|
||||
private String textTemplate;
|
||||
@Builder.Default
|
||||
private List<WebHookUrl> webhooks = new ArrayList<>();
|
||||
|
||||
@AllArgsConstructor
|
||||
@Setter
|
||||
@Getter
|
||||
@ToString
|
||||
public static class WebHookUrl {
|
||||
private final String secret;
|
||||
private final String url;
|
||||
}
|
||||
}
|
||||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in New Issue