Skip to content

Commit 24a5674

Browse files
committed
added processing & sending alerts
1 parent 9c290ca commit 24a5674

9 files changed

+272
-63
lines changed

README.md

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@
44

55
This plugin can be used for connecting [Graylog](https://www.graylog.org/) alerts to the [Prometheus](https://prometheus.io/) [AlertManager](https://prometheus.io/docs/alerting/alertmanager/).
66

7-
**Required Graylog version:** 4.0 and later
7+
Similar to [Graylog AlertManager Notification Plugin](https://github.com/GDATASoftwareAG/Graylog-Plugin-AlertManager-Callback), but uses new Graylog API for notifications.
8+
9+
**Required Graylog version:** 4.x (tested only on 4.2.5)
810

911
Installation
1012
------------
@@ -16,6 +18,61 @@ and can be configured in your `graylog.conf` file.
1618

1719
Restart `graylog-server` and you are done.
1820

21+
Screenshots
22+
-----------
23+
![image](https://user-images.githubusercontent.com/2664578/151272305-5699394c-89de-40c3-a240-32201a99bd5b.png)
24+
25+
![image](https://user-images.githubusercontent.com/2664578/151272408-8592e929-0ef0-4f84-b42a-f4c2a41b818a.png)
26+
27+
28+
Custom variables
29+
----------------
30+
31+
Options allow use JMTE Templates in labels & annotations.
32+
33+
Allowed ones:
34+
```
35+
# config - plugin configuration (AlertManagerNotifyConfig)
36+
config.api_url
37+
config.alert_name
38+
config.labels
39+
config.annotations
40+
config.grace
41+
42+
# context - info about event definition (EventNotificationModelData)
43+
context.event_definition_id
44+
context.event_definition_type
45+
context.event_definition_title
46+
context.event_definition_description
47+
context.job_definition_id
48+
context.job_trigger_id
49+
50+
# event - info about current event (EventDto)
51+
event.id
52+
event.event_definition_type
53+
event.event_definition_id
54+
event.origin_context
55+
event.timestamp
56+
event.timestamp_processing
57+
event.timerange_start
58+
event.timerange_end
59+
event.streams
60+
event.source_streams
61+
event.message
62+
event.source
63+
event.key_tuple
64+
event.key
65+
event.priority
66+
event.alert
67+
event.fields
68+
event.group_by_fields
69+
70+
node_id
71+
backlog - matched messages
72+
backlog_size - amount of messages in backlog
73+
message - event.message without context.event_definition_title prefix
74+
```
75+
1976
Development
2077
-----------
2178

Lines changed: 167 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,39 @@
11
package com.strayge;
22

3+
import static java.util.Objects.requireNonNull;
4+
5+
import java.io.ByteArrayInputStream;
6+
import java.io.IOException;
7+
import java.io.InputStream;
8+
import java.io.OutputStream;
9+
import java.net.HttpURLConnection;
10+
import java.net.URL;
11+
import java.nio.charset.StandardCharsets;
12+
import java.util.HashMap;
13+
import java.util.Map;
14+
import java.util.Properties;
15+
16+
import javax.inject.Inject;
17+
18+
import com.fasterxml.jackson.core.JsonProcessingException;
19+
import com.fasterxml.jackson.databind.ObjectMapper;
20+
import com.floreysoft.jmte.Engine;
21+
import com.google.common.collect.ImmutableList;
22+
323
import org.graylog.events.notifications.EventNotification;
424
import org.graylog.events.notifications.EventNotificationContext;
5-
// import org.graylog.events.notifications.EventNotificationModelData;
6-
// import org.graylog.events.notifications.EventNotificationService;
25+
import org.graylog.events.notifications.EventNotificationModelData;
26+
import org.graylog.events.notifications.EventNotificationService;
727
import org.graylog.events.notifications.PermanentEventNotificationException;
828
import org.graylog.events.notifications.TemporaryEventNotificationException;
9-
// import org.graylog2.plugin.MessageSummary;
29+
import org.graylog2.jackson.TypeReferences;
30+
import org.graylog2.notifications.NotificationService;
31+
import org.graylog2.plugin.MessageSummary;
32+
import org.graylog2.plugin.system.NodeId;
33+
import org.graylog2.streams.StreamService;
34+
import org.joda.time.DateTime;
1035
import org.slf4j.Logger;
1136
import org.slf4j.LoggerFactory;
12-
// import com.google.common.collect.ImmutableList;
1337

1438
/**
1539
* This is the plugin. Your class should implement one of the existing plugin
@@ -22,52 +46,149 @@ public interface Factory extends EventNotification.Factory {
2246
}
2347

2448
private static final Logger LOG = LoggerFactory.getLogger(AlertManagerNotify.class);
25-
// private final EventNotificationService notificationCallbackService;
49+
private final EventNotificationService notificationCallbackService;
50+
private final NodeId nodeId;
51+
private final ObjectMapper objectMapper;
52+
53+
@Inject
54+
public AlertManagerNotify(
55+
EventNotificationService notificationCallbackService,
56+
StreamService streamService,
57+
NotificationService notificationService,
58+
NodeId nodeId,
59+
ObjectMapper objectMapper
60+
) {
61+
this.notificationCallbackService = notificationCallbackService;
62+
this.nodeId = requireNonNull(nodeId, "nodeId");
63+
this.objectMapper = requireNonNull(objectMapper, "objectMapper");
64+
}
2665

2766
@Override
2867
public void execute(EventNotificationContext ctx) throws TemporaryEventNotificationException, PermanentEventNotificationException {
29-
LOG.info("AlertManagerNotify.execute called");
30-
31-
final AlertManagerNotifyConfig config = (AlertManagerNotifyConfig) ctx.notificationConfig();
32-
// config.url()
33-
34-
// ImmutableList<MessageSummary> backlog = notificationCallbackService.getBacklogForEvent(ctx);
35-
// final EventNotificationModelData model = EventNotificationModelData.of(ctx, backlog);
36-
37-
// final EventNotificationModelData model = getModel(ctx, backlog);
38-
// model.eventDefinitionTitle()
39-
// ctx.notificationId()
40-
41-
// final Request request = new Request.Builder()
42-
// .url(httpUrl)
43-
// .post(RequestBody.create(CONTENT_TYPE, body))
44-
// .build();
45-
46-
// try (final Response r = httpClient.newCall(request).execute()) {
47-
// if (!r.isSuccessful()) {
48-
// throw new PermanentEventNotificationException(
49-
// "Expected successful HTTP response [2xx] but got [" + r.code() + "]. " + config.url());
50-
// }
51-
// } catch (IOException e) {
52-
// throw new PermanentEventNotificationException(e.getMessage());
53-
// }
68+
AlertManagerNotifyConfig config = (AlertManagerNotifyConfig) ctx.notificationConfig();
69+
ImmutableList<MessageSummary> backlog = notificationCallbackService.getBacklogForEvent(ctx);
70+
EventNotificationModelData model = EventNotificationModelData.of(ctx, backlog);
71+
72+
Engine templateEngine = Engine.createEngine();
73+
Map<String, Object> templateModel = createTemplateModel(config, backlog, model);
74+
75+
Map<String, String> annotations = extractKeyValuePairsFromField(config.annotations());
76+
Map<String, Object> resolvedAnnotations = transformTemplateValues(templateEngine, templateModel, annotations);
77+
78+
Map<String, String> labels = extractKeyValuePairsFromField(config.labels());
79+
labels.put("alertname", config.alertName());
80+
Map<String, Object> resolvedLabels = transformTemplateValues(templateEngine, templateModel, labels);
81+
82+
int grace_period = 1;
83+
if (!"".equals(config.grace())) {
84+
try {
85+
grace_period = Integer.parseInt(config.grace());
86+
} catch (NumberFormatException e) {
87+
LOG.error("AlertManagerNotify: invalid grace period");
88+
}
89+
}
90+
DateTime startAt = new DateTime();
91+
DateTime endsAt = startAt.plusMinutes(grace_period).plusSeconds(20);
92+
93+
AlertManagerPayload payloadObject = new AlertManagerPayload();
94+
payloadObject.annotations = resolvedAnnotations;
95+
payloadObject.labels = resolvedLabels;
96+
payloadObject.generatorURL = model.eventDefinitionId();
97+
payloadObject.startsAt = startAt.toString();
98+
payloadObject.endsAt = endsAt.toString();
99+
100+
try {
101+
String payload = this.objectMapper.writeValueAsString(payloadObject);
102+
sendToAlertManager(config.apiUrl(), payload);
103+
} catch (JsonProcessingException e) {
104+
throw new PermanentEventNotificationException(e.getMessage());
105+
}
54106
}
55107

56-
// private EventNotificationModelData getModel(EventNotificationContext ctx, ImmutableList<MessageSummary> backlog) {
57-
// final Optional<EventDefinitionDto> definitionDto = ctx.eventDefinition();
58-
// final Optional<JobTriggerDto> jobTriggerDto = ctx.jobTrigger();
59-
// return EventNotificationModelData.builder()
60-
// // .eventDefinition(definitionDto)
61-
// .eventDefinitionId(definitionDto.map(EventDefinitionDto::id).orElse(UNKNOWN))
62-
// .eventDefinitionType(definitionDto.map(d -> d.config().type()).orElse(UNKNOWN))
63-
// .eventDefinitionTitle(definitionDto.map(EventDefinitionDto::title).orElse(UNKNOWN))
64-
// .eventDefinitionDescription(definitionDto.map(EventDefinitionDto::description).orElse(UNKNOWN))
65-
// .jobDefinitionId(jobTriggerDto.map(JobTriggerDto::jobDefinitionId).orElse(UNKNOWN))
66-
// .jobTriggerId(jobTriggerDto.map(JobTriggerDto::id).orElse(UNKNOWN))
67-
// .event(ctx.event())
68-
// .backlog(backlog)
69-
// // .backlogSize(backlog.size())
70-
// .build();
71-
// }
108+
private Map<String, Object> createTemplateModel(
109+
AlertManagerNotifyConfig config,
110+
ImmutableList<MessageSummary> backlog,
111+
EventNotificationModelData model
112+
) {
113+
114+
Map<String, Object> templateModel = new HashMap<>();
115+
templateModel.put("node_id", this.nodeId);
116+
templateModel.put("config", objectToJson(config));
117+
templateModel.put("backlog", backlog);
118+
templateModel.put("backlog_size", backlog.size());
119+
templateModel.put("context", objectToJson(model));
120+
templateModel.put("event", objectToJson(model.event()));
121+
122+
String message = model.event().message();
123+
String messagePrefix = model.eventDefinitionTitle() + ": ";
124+
if (message.startsWith(messagePrefix)) {
125+
message = message.substring(messagePrefix.length());
126+
}
127+
templateModel.put("message", message);
128+
return templateModel;
129+
}
130+
131+
private Map<String, Object> transformTemplateValues(
132+
Engine templateEngine,
133+
Map<String, Object> templateModel,
134+
Map<String, String> customValueMap
135+
) {
136+
final Map<String, Object> transformedCustomValueMap = new HashMap<>();
137+
customValueMap.forEach((key, value) -> {
138+
if (value instanceof String) {
139+
transformedCustomValueMap.put(key, templateEngine.transform((String) value, templateModel));
140+
} else {
141+
transformedCustomValueMap.put(key, value);
142+
}
143+
});
144+
return transformedCustomValueMap;
145+
}
146+
147+
private Map<String, String> extractKeyValuePairsFromField(String textFieldValue) {
148+
Map<String, String> extractedPairs = new HashMap<>();
149+
150+
if (textFieldValue != null && !"".equals(textFieldValue)) {
151+
final String preparedTextFieldValue = textFieldValue.replaceAll(";", "\n");
152+
Properties properties = new Properties();
153+
InputStream stringInputStream = new ByteArrayInputStream(preparedTextFieldValue.getBytes(StandardCharsets.UTF_8));
154+
try {
155+
properties.load(stringInputStream);
156+
properties.forEach((key, value) -> extractedPairs.put((String) key, (String) value));
157+
} catch (IOException e) {
158+
LOG.error("AlertManagerNotify: parse property failed " + e.getMessage());
159+
}
160+
}
161+
return extractedPairs;
162+
}
163+
164+
private boolean sendToAlertManager(String url, String payload) {
165+
try {
166+
payload = "[" + payload + "]";
167+
URL alertManagerUrl = new URL(url);
168+
HttpURLConnection connection = (HttpURLConnection) alertManagerUrl.openConnection();
169+
connection.setDoInput(true);
170+
connection.setDoOutput(true);
171+
172+
connection.setRequestProperty("Content-Type", "application/json;");
173+
connection.setRequestProperty("Accept", "application/json,text/plain");
174+
connection.setRequestProperty("Method", "POST");
175+
try (OutputStream os = connection.getOutputStream()) {
176+
os.write(payload.getBytes(StandardCharsets.UTF_8));
177+
}
178+
int HttpResult = connection.getResponseCode();
179+
connection.disconnect();
180+
if (HttpResult != HttpURLConnection.HTTP_OK) {
181+
LOG.error("AlertManagerNotify: AlertManager returned bad code: " + HttpResult);
182+
}
183+
return HttpResult == HttpURLConnection.HTTP_OK;
184+
} catch (IOException e) {
185+
LOG.error("AlertManagerNotify: request failed " + e.getMessage());
186+
return false;
187+
}
188+
}
189+
190+
private Object objectToJson(Object object) {
191+
return this.objectMapper.convertValue(object, TypeReferences.MAP_STRING_OBJECT);
192+
}
72193

73194
}

src/main/java/com/strayge/AlertManagerNotifyConfig.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import com.fasterxml.jackson.annotation.JsonTypeName;
77
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
88
import com.google.auto.value.AutoValue;
9+
910
import org.graylog.events.event.EventDto;
1011
import org.graylog.events.notifications.EventNotificationConfig;
1112
import org.graylog.events.notifications.EventNotificationExecutionJob;

src/main/java/com/strayge/AlertManagerNotifyConfigEntity.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
11
package com.strayge;
22

3+
import java.util.Map;
4+
35
import com.fasterxml.jackson.annotation.JsonCreator;
46
import com.fasterxml.jackson.annotation.JsonTypeName;
57
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
68
import com.google.auto.value.AutoValue;
9+
710
import org.graylog.events.contentpack.entities.EventNotificationConfigEntity;
811
import org.graylog.events.notifications.EventNotificationConfig;
9-
import org.graylog2.contentpacks.model.entities.references.ValueReference;
1012
import org.graylog2.contentpacks.model.entities.EntityDescriptor;
11-
import java.util.Map;
13+
import org.graylog2.contentpacks.model.entities.references.ValueReference;
1214

1315
@AutoValue
1416
@JsonTypeName(AlertManagerNotifyConfigEntity.TYPE_NAME)

src/main/java/com/strayge/AlertManagerNotifyMetaData.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
package com.strayge;
22

3-
import org.graylog2.plugin.PluginMetaData;
4-
import org.graylog2.plugin.ServerStatus;
5-
import org.graylog2.plugin.Version;
6-
73
import java.net.URI;
84
import java.util.Collections;
95
import java.util.Set;
106

7+
import org.graylog2.plugin.PluginMetaData;
8+
import org.graylog2.plugin.ServerStatus;
9+
import org.graylog2.plugin.Version;
10+
1111
/**
1212
* Implement the PluginMetaData interface here.
1313
*/

src/main/java/com/strayge/AlertManagerNotifyModule.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
package com.strayge;
22

3-
import org.graylog2.plugin.PluginConfigBean;
4-
import org.graylog2.plugin.PluginModule;
5-
63
import java.util.Collections;
74
import java.util.Set;
85

6+
import org.graylog2.plugin.PluginConfigBean;
7+
import org.graylog2.plugin.PluginModule;
8+
99
/**
1010
* Extend the PluginModule abstract class here to add you plugin to the system.
1111
*/
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package com.strayge;
2+
3+
import java.util.Map;
4+
5+
import com.fasterxml.jackson.annotation.JsonProperty;
6+
7+
class AlertManagerPayload {
8+
@JsonProperty("labels")
9+
public Map<String, Object> labels;
10+
11+
@JsonProperty("annotations")
12+
public Map<String, Object> annotations;
13+
14+
@JsonProperty("generatorURL")
15+
public String generatorURL;
16+
17+
@JsonProperty("startsAt")
18+
public String startsAt;
19+
20+
@JsonProperty("endsAt")
21+
public String endsAt;
22+
}

0 commit comments

Comments
 (0)