Skip to content

Commit

Permalink
added processing & sending alerts
Browse files Browse the repository at this point in the history
  • Loading branch information
strayge committed Jan 27, 2022
1 parent 9c290ca commit 24a5674
Show file tree
Hide file tree
Showing 9 changed files with 272 additions and 63 deletions.
59 changes: 58 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@

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/).

**Required Graylog version:** 4.0 and later
Similar to [Graylog AlertManager Notification Plugin](https://github.com/GDATASoftwareAG/Graylog-Plugin-AlertManager-Callback), but uses new Graylog API for notifications.

**Required Graylog version:** 4.x (tested only on 4.2.5)

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

Restart `graylog-server` and you are done.

Screenshots
-----------
![image](https://user-images.githubusercontent.com/2664578/151272305-5699394c-89de-40c3-a240-32201a99bd5b.png)

![image](https://user-images.githubusercontent.com/2664578/151272408-8592e929-0ef0-4f84-b42a-f4c2a41b818a.png)


Custom variables
----------------

Options allow use JMTE Templates in labels & annotations.

Allowed ones:
```
# config - plugin configuration (AlertManagerNotifyConfig)
config.api_url
config.alert_name
config.labels
config.annotations
config.grace
# context - info about event definition (EventNotificationModelData)
context.event_definition_id
context.event_definition_type
context.event_definition_title
context.event_definition_description
context.job_definition_id
context.job_trigger_id
# event - info about current event (EventDto)
event.id
event.event_definition_type
event.event_definition_id
event.origin_context
event.timestamp
event.timestamp_processing
event.timerange_start
event.timerange_end
event.streams
event.source_streams
event.message
event.source
event.key_tuple
event.key
event.priority
event.alert
event.fields
event.group_by_fields
node_id
backlog - matched messages
backlog_size - amount of messages in backlog
message - event.message without context.event_definition_title prefix
```

Development
-----------

Expand Down
213 changes: 167 additions & 46 deletions src/main/java/com/strayge/AlertManagerNotify.java
Original file line number Diff line number Diff line change
@@ -1,15 +1,39 @@
package com.strayge;

import static java.util.Objects.requireNonNull;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

import javax.inject.Inject;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.floreysoft.jmte.Engine;
import com.google.common.collect.ImmutableList;

import org.graylog.events.notifications.EventNotification;
import org.graylog.events.notifications.EventNotificationContext;
// import org.graylog.events.notifications.EventNotificationModelData;
// import org.graylog.events.notifications.EventNotificationService;
import org.graylog.events.notifications.EventNotificationModelData;
import org.graylog.events.notifications.EventNotificationService;
import org.graylog.events.notifications.PermanentEventNotificationException;
import org.graylog.events.notifications.TemporaryEventNotificationException;
// import org.graylog2.plugin.MessageSummary;
import org.graylog2.jackson.TypeReferences;
import org.graylog2.notifications.NotificationService;
import org.graylog2.plugin.MessageSummary;
import org.graylog2.plugin.system.NodeId;
import org.graylog2.streams.StreamService;
import org.joda.time.DateTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
// import com.google.common.collect.ImmutableList;

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

private static final Logger LOG = LoggerFactory.getLogger(AlertManagerNotify.class);
// private final EventNotificationService notificationCallbackService;
private final EventNotificationService notificationCallbackService;
private final NodeId nodeId;
private final ObjectMapper objectMapper;

@Inject
public AlertManagerNotify(
EventNotificationService notificationCallbackService,
StreamService streamService,
NotificationService notificationService,
NodeId nodeId,
ObjectMapper objectMapper
) {
this.notificationCallbackService = notificationCallbackService;
this.nodeId = requireNonNull(nodeId, "nodeId");
this.objectMapper = requireNonNull(objectMapper, "objectMapper");
}

@Override
public void execute(EventNotificationContext ctx) throws TemporaryEventNotificationException, PermanentEventNotificationException {
LOG.info("AlertManagerNotify.execute called");

final AlertManagerNotifyConfig config = (AlertManagerNotifyConfig) ctx.notificationConfig();
// config.url()

// ImmutableList<MessageSummary> backlog = notificationCallbackService.getBacklogForEvent(ctx);
// final EventNotificationModelData model = EventNotificationModelData.of(ctx, backlog);

// final EventNotificationModelData model = getModel(ctx, backlog);
// model.eventDefinitionTitle()
// ctx.notificationId()

// final Request request = new Request.Builder()
// .url(httpUrl)
// .post(RequestBody.create(CONTENT_TYPE, body))
// .build();

// try (final Response r = httpClient.newCall(request).execute()) {
// if (!r.isSuccessful()) {
// throw new PermanentEventNotificationException(
// "Expected successful HTTP response [2xx] but got [" + r.code() + "]. " + config.url());
// }
// } catch (IOException e) {
// throw new PermanentEventNotificationException(e.getMessage());
// }
AlertManagerNotifyConfig config = (AlertManagerNotifyConfig) ctx.notificationConfig();
ImmutableList<MessageSummary> backlog = notificationCallbackService.getBacklogForEvent(ctx);
EventNotificationModelData model = EventNotificationModelData.of(ctx, backlog);

Engine templateEngine = Engine.createEngine();
Map<String, Object> templateModel = createTemplateModel(config, backlog, model);

Map<String, String> annotations = extractKeyValuePairsFromField(config.annotations());
Map<String, Object> resolvedAnnotations = transformTemplateValues(templateEngine, templateModel, annotations);

Map<String, String> labels = extractKeyValuePairsFromField(config.labels());
labels.put("alertname", config.alertName());
Map<String, Object> resolvedLabels = transformTemplateValues(templateEngine, templateModel, labels);

int grace_period = 1;
if (!"".equals(config.grace())) {
try {
grace_period = Integer.parseInt(config.grace());
} catch (NumberFormatException e) {
LOG.error("AlertManagerNotify: invalid grace period");
}
}
DateTime startAt = new DateTime();
DateTime endsAt = startAt.plusMinutes(grace_period).plusSeconds(20);

AlertManagerPayload payloadObject = new AlertManagerPayload();
payloadObject.annotations = resolvedAnnotations;
payloadObject.labels = resolvedLabels;
payloadObject.generatorURL = model.eventDefinitionId();
payloadObject.startsAt = startAt.toString();
payloadObject.endsAt = endsAt.toString();

try {
String payload = this.objectMapper.writeValueAsString(payloadObject);
sendToAlertManager(config.apiUrl(), payload);
} catch (JsonProcessingException e) {
throw new PermanentEventNotificationException(e.getMessage());
}
}

// private EventNotificationModelData getModel(EventNotificationContext ctx, ImmutableList<MessageSummary> backlog) {
// final Optional<EventDefinitionDto> definitionDto = ctx.eventDefinition();
// final Optional<JobTriggerDto> jobTriggerDto = ctx.jobTrigger();
// return EventNotificationModelData.builder()
// // .eventDefinition(definitionDto)
// .eventDefinitionId(definitionDto.map(EventDefinitionDto::id).orElse(UNKNOWN))
// .eventDefinitionType(definitionDto.map(d -> d.config().type()).orElse(UNKNOWN))
// .eventDefinitionTitle(definitionDto.map(EventDefinitionDto::title).orElse(UNKNOWN))
// .eventDefinitionDescription(definitionDto.map(EventDefinitionDto::description).orElse(UNKNOWN))
// .jobDefinitionId(jobTriggerDto.map(JobTriggerDto::jobDefinitionId).orElse(UNKNOWN))
// .jobTriggerId(jobTriggerDto.map(JobTriggerDto::id).orElse(UNKNOWN))
// .event(ctx.event())
// .backlog(backlog)
// // .backlogSize(backlog.size())
// .build();
// }
private Map<String, Object> createTemplateModel(
AlertManagerNotifyConfig config,
ImmutableList<MessageSummary> backlog,
EventNotificationModelData model
) {

Map<String, Object> templateModel = new HashMap<>();
templateModel.put("node_id", this.nodeId);
templateModel.put("config", objectToJson(config));
templateModel.put("backlog", backlog);
templateModel.put("backlog_size", backlog.size());
templateModel.put("context", objectToJson(model));
templateModel.put("event", objectToJson(model.event()));

String message = model.event().message();
String messagePrefix = model.eventDefinitionTitle() + ": ";
if (message.startsWith(messagePrefix)) {
message = message.substring(messagePrefix.length());
}
templateModel.put("message", message);
return templateModel;
}

private Map<String, Object> transformTemplateValues(
Engine templateEngine,
Map<String, Object> templateModel,
Map<String, String> customValueMap
) {
final Map<String, Object> transformedCustomValueMap = new HashMap<>();
customValueMap.forEach((key, value) -> {
if (value instanceof String) {
transformedCustomValueMap.put(key, templateEngine.transform((String) value, templateModel));
} else {
transformedCustomValueMap.put(key, value);
}
});
return transformedCustomValueMap;
}

private Map<String, String> extractKeyValuePairsFromField(String textFieldValue) {
Map<String, String> extractedPairs = new HashMap<>();

if (textFieldValue != null && !"".equals(textFieldValue)) {
final String preparedTextFieldValue = textFieldValue.replaceAll(";", "\n");
Properties properties = new Properties();
InputStream stringInputStream = new ByteArrayInputStream(preparedTextFieldValue.getBytes(StandardCharsets.UTF_8));
try {
properties.load(stringInputStream);
properties.forEach((key, value) -> extractedPairs.put((String) key, (String) value));
} catch (IOException e) {
LOG.error("AlertManagerNotify: parse property failed " + e.getMessage());
}
}
return extractedPairs;
}

private boolean sendToAlertManager(String url, String payload) {
try {
payload = "[" + payload + "]";
URL alertManagerUrl = new URL(url);
HttpURLConnection connection = (HttpURLConnection) alertManagerUrl.openConnection();
connection.setDoInput(true);
connection.setDoOutput(true);

connection.setRequestProperty("Content-Type", "application/json;");
connection.setRequestProperty("Accept", "application/json,text/plain");
connection.setRequestProperty("Method", "POST");
try (OutputStream os = connection.getOutputStream()) {
os.write(payload.getBytes(StandardCharsets.UTF_8));
}
int HttpResult = connection.getResponseCode();
connection.disconnect();
if (HttpResult != HttpURLConnection.HTTP_OK) {
LOG.error("AlertManagerNotify: AlertManager returned bad code: " + HttpResult);
}
return HttpResult == HttpURLConnection.HTTP_OK;
} catch (IOException e) {
LOG.error("AlertManagerNotify: request failed " + e.getMessage());
return false;
}
}

private Object objectToJson(Object object) {
return this.objectMapper.convertValue(object, TypeReferences.MAP_STRING_OBJECT);
}

}
1 change: 1 addition & 0 deletions src/main/java/com/strayge/AlertManagerNotifyConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import com.fasterxml.jackson.annotation.JsonTypeName;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.google.auto.value.AutoValue;

import org.graylog.events.event.EventDto;
import org.graylog.events.notifications.EventNotificationConfig;
import org.graylog.events.notifications.EventNotificationExecutionJob;
Expand Down
6 changes: 4 additions & 2 deletions src/main/java/com/strayge/AlertManagerNotifyConfigEntity.java
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
package com.strayge;

import java.util.Map;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonTypeName;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.google.auto.value.AutoValue;

import org.graylog.events.contentpack.entities.EventNotificationConfigEntity;
import org.graylog.events.notifications.EventNotificationConfig;
import org.graylog2.contentpacks.model.entities.references.ValueReference;
import org.graylog2.contentpacks.model.entities.EntityDescriptor;
import java.util.Map;
import org.graylog2.contentpacks.model.entities.references.ValueReference;

@AutoValue
@JsonTypeName(AlertManagerNotifyConfigEntity.TYPE_NAME)
Expand Down
8 changes: 4 additions & 4 deletions src/main/java/com/strayge/AlertManagerNotifyMetaData.java
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
package com.strayge;

import org.graylog2.plugin.PluginMetaData;
import org.graylog2.plugin.ServerStatus;
import org.graylog2.plugin.Version;

import java.net.URI;
import java.util.Collections;
import java.util.Set;

import org.graylog2.plugin.PluginMetaData;
import org.graylog2.plugin.ServerStatus;
import org.graylog2.plugin.Version;

/**
* Implement the PluginMetaData interface here.
*/
Expand Down
6 changes: 3 additions & 3 deletions src/main/java/com/strayge/AlertManagerNotifyModule.java
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
package com.strayge;

import org.graylog2.plugin.PluginConfigBean;
import org.graylog2.plugin.PluginModule;

import java.util.Collections;
import java.util.Set;

import org.graylog2.plugin.PluginConfigBean;
import org.graylog2.plugin.PluginModule;

/**
* Extend the PluginModule abstract class here to add you plugin to the system.
*/
Expand Down
22 changes: 22 additions & 0 deletions src/main/java/com/strayge/AlertManagerNotifyPayload.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.strayge;

import java.util.Map;

import com.fasterxml.jackson.annotation.JsonProperty;

class AlertManagerPayload {
@JsonProperty("labels")
public Map<String, Object> labels;

@JsonProperty("annotations")
public Map<String, Object> annotations;

@JsonProperty("generatorURL")
public String generatorURL;

@JsonProperty("startsAt")
public String startsAt;

@JsonProperty("endsAt")
public String endsAt;
}
Loading

0 comments on commit 24a5674

Please sign in to comment.