Skip to content

Commit

Permalink
Support PagerDuty Alarm Hook (apache#9247)
Browse files Browse the repository at this point in the history
  • Loading branch information
chen-ni authored Jun 18, 2022
1 parent 0945b36 commit 61278dd
Show file tree
Hide file tree
Showing 12 changed files with 329 additions and 1 deletion.
1 change: 1 addition & 0 deletions docs/en/changes/changes.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
* Add more entities for Zipkin to improve performance.
* ElasticSearch: scroll id should be updated when scrolling as it may change.
* Mesh: fix only last rule works when multiple rules are defined in metadata-service-mapping.yaml.
* Support sending alarm messages to PagerDuty.

#### UI

Expand Down
16 changes: 16 additions & 0 deletions docs/en/setup/backend/backend-alarm.md
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,22 @@ welinkHooks:
robot_name: robot
```


## PagerDuty Hook
The PagerDuty hook is based on [Events API v2](https://developer.pagerduty.com/docs/ZG9jOjExMDI5NTgw-events-api-v2-overview).

Follow the [Getting Started](https://developer.pagerduty.com/docs/ZG9jOjExMDI5NTgw-events-api-v2-overview#getting-started) section to create an **Events API v2** integration on your PagerDuty service and copy the integration key.

Then configure as follows:
```yml
pagerDutyHooks:
textTemplate: "Apache SkyWalking Alarm: \n %s."
integrationKeys:
- 5c6d805c9dcf4e03d09dfa81e8789ba1
```

You can also configure multiple integration keys.

## Update the settings dynamically
Since 6.5.0, the alerting settings can be updated dynamically at runtime by [Dynamic Configuration](dynamic-config.md),
which will override the settings in `alarm-settings.yml`.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
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.pagerduty.PagerDutySettings;
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.core.alarm.provider.welink.WeLinkSettings;
Expand Down Expand Up @@ -142,4 +143,8 @@ public FeishuSettings getFeishuSettings() {
public WeLinkSettings getWeLinkSettings() {
return this.rules.getWelinks();
}

public PagerDutySettings getPagerDutySettings() {
return this.rules.getPagerDutySettings();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
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.pagerduty.PagerDutyHookCallback;
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.alarm.provider.welink.WeLinkHookCallback;
Expand Down Expand Up @@ -185,6 +186,7 @@ public void init(AlarmCallback... callbacks) {
allCallbacks.add(new FeishuHookCallback(alarmRulesWatcher));
allCallbacks.add(new EventHookCallback(this.manager));
allCallbacks.add(new WeLinkHookCallback(alarmRulesWatcher));
allCallbacks.add(new PagerDutyHookCallback(alarmRulesWatcher));
core.start(allCallbacks);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
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.pagerduty.PagerDutySettings;
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.core.alarm.provider.welink.WeLinkSettings;
Expand All @@ -43,6 +44,7 @@ public class Rules {
private DingtalkSettings dingtalks;
private FeishuSettings feishus;
private WeLinkSettings welinks;
private PagerDutySettings pagerDutySettings;

public Rules() {
this.rules = new ArrayList<>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;

import org.apache.skywalking.oap.server.core.alarm.provider.pagerduty.PagerDutySettings;
import org.apache.skywalking.oap.server.library.util.StringUtil;
import org.apache.skywalking.oap.server.core.alarm.provider.dingtalk.DingtalkSettings;
import org.apache.skywalking.oap.server.core.alarm.provider.feishu.FeishuSettings;
Expand Down Expand Up @@ -70,6 +72,7 @@ public Rules readRules() {
readDingtalkConfig(rules);
readFeishuConfig(rules);
readWeLinkConfig(rules);
readPagerDutyConfig(rules);
}
return rules;
}
Expand Down Expand Up @@ -281,4 +284,23 @@ private void readWeLinkConfig(Rules rules) {
welinkSettings.setWebhooks(webHookUrls);
rules.setWelinks(welinkSettings);
}

/**
* Read PagerDuty hook config into {@link PagerDutySettings}
*/
private void readPagerDutyConfig(Rules rules) {
Map<String, Object> pagerDutyConfig = (Map<String, Object>) yamlData.get("pagerDutyHooks");
if (pagerDutyConfig != null) {
PagerDutySettings pagerDutySettings = new PagerDutySettings();
String textTemplate = (String) pagerDutyConfig.getOrDefault("textTemplate", "");
pagerDutySettings.setTextTemplate(textTemplate);

List<String> integrationKeys = (List<String>) pagerDutyConfig.get("integrationKeys");
if (integrationKeys != null) {
pagerDutySettings.getIntegrationKeys().addAll(integrationKeys);
}

rules.setPagerDutySettings(pagerDutySettings);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
/*
* 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.pagerduty;

import com.google.gson.Gson;
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
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.http.util.EntityUtils;
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.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.UUID;

@Slf4j
public class PagerDutyHookCallback implements AlarmCallback {
private static final String PAGER_DUTY_EVENTS_API_V2_URL = "https://events.pagerduty.com/v2/enqueue";
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 static final Gson GSON = new Gson();

private AlarmRulesWatcher alarmRulesWatcher;
private RequestConfig requestConfig;

public PagerDutyHookCallback(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.getPagerDutySettings() == null || this.alarmRulesWatcher.getPagerDutySettings().getIntegrationKeys().isEmpty()) {
return;
}

CloseableHttpClient httpClient = HttpClients.custom().build();
try {
this.alarmRulesWatcher.getPagerDutySettings().getIntegrationKeys().forEach(integrationKey -> {
alarmMessages.forEach(alarmMessage -> {
sendAlarmMessage(httpClient, alarmMessage, integrationKey);
});
});
} finally {
try {
httpClient.close();
} catch (IOException e) {
log.error(e.getMessage(), e);
}
}
}

private void sendAlarmMessage(CloseableHttpClient httpClient, AlarmMessage alarmMessage, String integrationKey) {
CloseableHttpResponse httpResponse = null;
try {
HttpPost post = new HttpPost(PAGER_DUTY_EVENTS_API_V2_URL);
post.setConfig(requestConfig);
post.setHeader(HttpHeaders.ACCEPT, HttpHeaderValues.APPLICATION_JSON.toString());
post.setHeader(HttpHeaders.CONTENT_TYPE, HttpHeaderValues.APPLICATION_JSON.toString());
post.setEntity(
getStringEntity(alarmMessage, integrationKey)
);
httpResponse = httpClient.execute(post);
StatusLine statusLine = httpResponse.getStatusLine();
if (statusLine != null && statusLine.getStatusCode() != HttpStatus.SC_ACCEPTED) {
log.error("send PagerDuty alarm to {} failure. Response code: {}, message: {} ",
PAGER_DUTY_EVENTS_API_V2_URL, statusLine.getStatusCode(),
EntityUtils.toString(httpResponse.getEntity(), StandardCharsets.UTF_8)
);
}
} catch (Throwable e) {
log.error("send PagerDuty alarm to {} failure.", PAGER_DUTY_EVENTS_API_V2_URL, e);
} finally {
if (httpResponse != null) {
try {
httpResponse.close();
} catch (IOException e) {
log.error(e.getMessage(), e);
}
}
}
}

private StringEntity getStringEntity(AlarmMessage alarmMessage, String integrationKey) throws UnsupportedEncodingException {
JsonObject body = new JsonObject();
JsonObject payload = new JsonObject();
payload.add("summary", new JsonPrimitive(getFormattedMessage(alarmMessage)));
payload.add("severity", new JsonPrimitive("warning"));
payload.add("source", new JsonPrimitive("Skywalking"));
body.add("payload", payload);
body.add("routing_key", new JsonPrimitive(integrationKey));
body.add("dedup_key", new JsonPrimitive(UUID.randomUUID().toString()));
body.add("event_action", new JsonPrimitive("trigger"));

return new StringEntity(GSON.toJson(body), ContentType.APPLICATION_JSON);
}

private String getFormattedMessage(AlarmMessage alarmMessage) {
return String.format(
this.alarmRulesWatcher.getPagerDutySettings().getTextTemplate(), alarmMessage.getAlarmMessage()
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* 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.pagerduty;

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 PagerDutySettings {

private String textTemplate;

@Builder.Default
private List<String> integrationKeys = new ArrayList<>();
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
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.pagerduty.PagerDutySettings;
import org.apache.skywalking.oap.server.core.alarm.provider.slack.SlackSettings;
import org.apache.skywalking.oap.server.core.alarm.provider.wechat.WechatSettings;
import org.junit.Assert;
Expand All @@ -33,6 +34,7 @@
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertEquals;

public class RulesReaderTest {
@Test
Expand Down Expand Up @@ -96,5 +98,12 @@ public void testReadRules() {
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());

PagerDutySettings pagerDutySettings = rules.getPagerDutySettings();
assertEquals("dummy_text_template", pagerDutySettings.getTextTemplate());
List<String> pagerDutyIntegrationKeys = pagerDutySettings.getIntegrationKeys();
assertEquals(2, pagerDutyIntegrationKeys.size());
assertEquals("dummy_key", pagerDutyIntegrationKeys.get(0));
assertEquals("dummy_key2", pagerDutyIntegrationKeys.get(1));
}
}
Loading

0 comments on commit 61278dd

Please sign in to comment.