Alarm support wechat webhook (#5474)

Co-authored-by: 吴晟 Wu Sheng <wu.sheng@foxmail.com>
This commit is contained in:
xbkaishui 2020-09-12 21:35:42 +08:00 committed by GitHub
parent efd080840e
commit c408cf9678
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 340 additions and 5 deletions

View File

@ -180,6 +180,23 @@ slackHooks:
- https://hooks.slack.com/services/x/y/z
```
## WeChat Hook
Note, only WeCom(WeChat Company Edition) supports webhook. To use the WeChat webhook you need to follow the [Wechat Webhooks guide](https://work.weixin.qq.com/help?doc_id=13376).
The alarm message would send through HTTP post by `application/json` content type after you set up Wechat Webhooks as following:
```yml
wechatHooks:
textTemplate: |-
{
"msgtype": "text",
"text": {
"content": "Apache SkyWalking Alarm: \n %s."
}
}
webhooks:
- https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=dummy_key
```
## Update the settings dynamically
Since 6.5.0, the alarm settings can be updated dynamically at runtime by [Dynamic Configuration](dynamic-config.md),
which will override the settings in `alarm-settings.yml`.

View File

@ -30,6 +30,7 @@ import org.apache.skywalking.oap.server.core.Const;
import org.apache.skywalking.oap.server.core.alarm.AlarmModule;
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 org.apache.skywalking.oap.server.library.module.ModuleProvider;
/**
@ -114,4 +115,8 @@ public class AlarmRulesWatcher extends ConfigChangeWatcher {
public SlackSettings getSlackSettings() {
return this.rules.getSlacks();
}
public WechatSettings getWechatSettings() {
return this.rules.getWecchats();
}
}

View File

@ -33,6 +33,7 @@ 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.grpc.GRPCCallback;
import org.apache.skywalking.oap.server.core.alarm.provider.slack.SlackhookCallback;
import org.apache.skywalking.oap.server.core.alarm.provider.wechat.WechatHookCallback;
import org.apache.skywalking.oap.server.core.analysis.IDManager;
import org.apache.skywalking.oap.server.core.analysis.metrics.Metrics;
import org.apache.skywalking.oap.server.core.analysis.metrics.MetricsMetaInfo;
@ -160,6 +161,7 @@ public class NotifyHandler implements MetricsNotify {
allCallbacks.add(new WebhookCallback(alarmRulesWatcher));
allCallbacks.add(new GRPCCallback(alarmRulesWatcher));
allCallbacks.add(new SlackhookCallback(alarmRulesWatcher));
allCallbacks.add(new WechatHookCallback(alarmRulesWatcher));
core.start(allCallbacks);
}
}

View File

@ -25,6 +25,7 @@ import lombok.Setter;
import lombok.ToString;
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;
@Setter
@Getter
@ -34,6 +35,7 @@ public class Rules {
private List<String> webhooks;
private GRPCAlarmSetting grpchookSetting;
private SlackSettings slacks;
private WechatSettings wecchats;
public Rules() {
this.rules = new ArrayList<>();

View File

@ -26,6 +26,7 @@ import java.util.Map;
import java.util.Objects;
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 org.yaml.snakeyaml.Yaml;
/**
@ -118,15 +119,23 @@ public class RulesReader {
List<String> slackWebhooks = (List<String>) slacks.get("webhooks");
if (slackWebhooks != null) {
slackWebhooks.forEach(
url -> slackSettings.getWebhooks().add(url)
);
slackSettings.getWebhooks().addAll(slackWebhooks);
}
rules.setSlacks(slackSettings);
}
}
Map wechatConfig = (Map) yamlData.get("wechatHooks");
if (wechatConfig != null) {
WechatSettings wechatSettings = new WechatSettings();
Object textTemplate = wechatConfig.getOrDefault("textTemplate", "");
wechatSettings.setTextTemplate((String) textTemplate);
List<String> wechatWebhooks = (List<String>) wechatConfig.get("webhooks");
if (wechatWebhooks != null) {
wechatSettings.getWebhooks().addAll(wechatWebhooks);
}
rules.setWecchats(wechatSettings);
}
}
return rules;
}
}

View File

@ -0,0 +1,101 @@
/*
* 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.wechat;
import io.netty.handler.codec.http.HttpHeaderValues;
import lombok.extern.slf4j.Slf4j;
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.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 java.io.IOException;
import java.util.List;
/**
* Use SkyWalking alarm wechat webhook API.
*/
@Slf4j
public class WechatHookCallback 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 WechatHookCallback(final AlarmRulesWatcher alarmRulesWatcher) {
this.alarmRulesWatcher = alarmRulesWatcher;
this.requestConfig = RequestConfig.custom()
.setConnectTimeout(HTTP_CONNECT_TIMEOUT)
.setConnectionRequestTimeout(HTTP_CONNECTION_REQUEST_TIMEOUT)
.setSocketTimeout(HTTP_SOCKET_TIMEOUT)
.build();
}
@Override
public void doAlarm(List<AlarmMessage> alarmMessages) {
if (this.alarmRulesWatcher.getWechatSettings() == null || this.alarmRulesWatcher.getWechatSettings().getWebhooks().isEmpty()) {
return;
}
CloseableHttpClient httpClient = HttpClients.custom().build();
try {
this.alarmRulesWatcher.getWechatSettings().getWebhooks().forEach(url -> {
alarmMessages.forEach(alarmMessage -> {
String requestBody = String.format(
this.alarmRulesWatcher.getWechatSettings().getTextTemplate(), alarmMessage.getAlarmMessage()
);
sendAlarmMessage(httpClient, url, requestBody);
});
});
} finally {
try {
httpClient.close();
} catch (IOException e) {
log.error(e.getMessage(), e);
}
}
}
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 wechat alarm to {} failure. Response code: {} ", url, statusLine.getStatusCode());
}
} catch (Throwable e) {
log.error("send wechat alarm to {} failure.", url, e);
}
}
}

View File

@ -0,0 +1,42 @@
/*
* 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.wechat;
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;
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Setter
@Getter
@ToString
public class WechatSettings {
private String textTemplate;
@Builder.Default
private List<String> webhooks = new ArrayList<>();
}

View File

@ -0,0 +1,147 @@
/*
* 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.wechat;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
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 WechatHookCallbackTest implements Servlet {
private Server server;
private int port;
private volatile boolean isSuccess = false;
private int count;
@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("/wechathook");
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 testWechatWebhook() {
List<String> remoteEndpoints = new ArrayList<>();
remoteEndpoints.add("http://127.0.0.1:" + port + "/wechathook/receiveAlarm");
Rules rules = new Rules();
String template = "{\"msgtype\":\"text\",\"text\":{\"content\":\"Skywaling alarm: %s\"}}";
rules.setWecchats(WechatSettings.builder().webhooks(remoteEndpoints).textTemplate(template).build());
AlarmRulesWatcher alarmRulesWatcher = new AlarmRulesWatcher(rules, null);
WechatHookCallback wechatHookCallback = new WechatHookCallback(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);
wechatHookCallback.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("msgtype").getAsString();
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() {
}
}

View File

@ -59,3 +59,13 @@ slackHooks:
webhooks:
# - https://hooks.slack.com/services/x/y/zssss
wechatHooks:
textTemplate: |-
{
"msgtype": "text",
"text": {
"content": "Apache SkyWalking Alarm: \n %s."
}
}
webhooks:
- https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=dummy_key