Alerting : better email formatting

This commit enables better email formatting.

Original commit: elastic/x-pack-elasticsearch@8be3e3b6d1
This commit is contained in:
Brian Murphy 2014-08-13 17:05:25 +01:00
parent e3250c0366
commit 0d9061b838
7 changed files with 164 additions and 67 deletions

View File

@ -153,51 +153,55 @@ public class AlertManager extends AbstractLifecycleComponent {
).setTypes(ALERT_TYPE).setIndices(ALERT_INDEX).execute().get(); ).setTypes(ALERT_TYPE).setIndices(ALERT_INDEX).execute().get();
for (SearchHit sh : searchResponse.getHits()) { for (SearchHit sh : searchResponse.getHits()) {
String alertId = sh.getId(); String alertId = sh.getId();
logger.warn("Found : [{}]", alertId); Alert alert = parseAlert(sh, alertId);
Map<String,Object> fields = sh.sourceAsMap();
//Map<String,SearchHitField> fields = sh.getFields();
for (String field : fields.keySet() ) {
logger.warn("Field : [{}]", field);
}
String query = fields.get(QUERY_FIELD.getPreferredName()).toString();
String schedule = fields.get(SCHEDULE_FIELD.getPreferredName()).toString();
Object triggerObj = fields.get(TRIGGER_FIELD.getPreferredName());
AlertTrigger trigger = null;
if (triggerObj instanceof Map) {
Map<String, Object> triggerMap = (Map<String, Object>) triggerObj;
trigger = TriggerManager.parseTriggerFromMap(triggerMap);
} else {
throw new ElasticsearchException("Unable to parse trigger [" + triggerObj + "]");
}
String timeString = fields.get(TIMEPERIOD_FIELD.getPreferredName()).toString();
TimeValue timePeriod = TimeValue.parseTimeValue(timeString, defaultTimePeriod);
Object actionObj = fields.get(ACTION_FIELD.getPreferredName());
List<AlertAction> actions = null;
if (actionObj instanceof Map) {
Map<String, Object> actionMap = (Map<String, Object>) actionObj;
actions = actionManager.parseActionsFromMap(actionMap);
} else {
throw new ElasticsearchException("Unable to parse actions [" + triggerObj + "]");
}
DateTime lastRan = new DateTime(fields.get("lastRan").toString());
List<String> indices = null;
if (fields.get(INDICES.getPreferredName()) != null && fields.get(INDICES.getPreferredName()) instanceof List){
indices = (List<String>)fields.get(INDICES.getPreferredName());
}
Alert alert = new Alert(alertId, query, trigger, timePeriod, actions, schedule, lastRan, indices);
alertMap.put(alertId, alert); alertMap.put(alertId, alert);
} }
logger.warn("Loaded [{}] alerts from the alert index.", alertMap.size()); logger.warn("Loaded [{}] alerts from the alert index.", alertMap.size());
} }
} }
private Alert parseAlert(SearchHit sh, String alertId) {
logger.warn("Found : [{}]", alertId);
Map<String,Object> fields = sh.sourceAsMap();
//Map<String,SearchHitField> fields = sh.getFields();
for (String field : fields.keySet() ) {
logger.warn("Field : [{}]", field);
}
String query = fields.get(QUERY_FIELD.getPreferredName()).toString();
String schedule = fields.get(SCHEDULE_FIELD.getPreferredName()).toString();
Object triggerObj = fields.get(TRIGGER_FIELD.getPreferredName());
AlertTrigger trigger = null;
if (triggerObj instanceof Map) {
Map<String, Object> triggerMap = (Map<String, Object>) triggerObj;
trigger = TriggerManager.parseTriggerFromMap(triggerMap);
} else {
throw new ElasticsearchException("Unable to parse trigger [" + triggerObj + "]");
}
String timeString = fields.get(TIMEPERIOD_FIELD.getPreferredName()).toString();
TimeValue timePeriod = TimeValue.parseTimeValue(timeString, defaultTimePeriod);
Object actionObj = fields.get(ACTION_FIELD.getPreferredName());
List<AlertAction> actions = null;
if (actionObj instanceof Map) {
Map<String, Object> actionMap = (Map<String, Object>) actionObj;
actions = actionManager.parseActionsFromMap(actionMap);
} else {
throw new ElasticsearchException("Unable to parse actions [" + triggerObj + "]");
}
DateTime lastRan = new DateTime(fields.get("lastRan").toString());
List<String> indices = null;
if (fields.get(INDICES.getPreferredName()) != null && fields.get(INDICES.getPreferredName()) instanceof List){
indices = (List<String>)fields.get(INDICES.getPreferredName());
}
return new Alert(alertId, query, trigger, timePeriod, actions, schedule, lastRan, indices);
}
public Alert getAlertForName(String alertName) { public Alert getAlertForName(String alertName) {
synchronized (alertMap) { synchronized (alertMap) {
return alertMap.get(alertName); return alertMap.get(alertName);

View File

@ -6,11 +6,16 @@
package org.elasticsearch.alerting; package org.elasticsearch.alerting;
import org.elasticsearch.action.search.SearchResponse; import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.common.xcontent.XContentBuilder;
import java.util.Arrays;
public class AlertResult { public class AlertResult {
public SearchResponse searchResponse; public SearchResponse searchResponse;
public AlertTrigger trigger; public AlertTrigger trigger;
public boolean isTriggered; public boolean isTriggered;
public XContentBuilder query;
public String[] indices;
@Override @Override
public boolean equals(Object o) { public boolean equals(Object o) {
@ -20,17 +25,23 @@ public class AlertResult {
AlertResult that = (AlertResult) o; AlertResult that = (AlertResult) o;
if (isTriggered != that.isTriggered) return false; if (isTriggered != that.isTriggered) return false;
if (!searchResponse.equals(that.searchResponse)) return false; if (!Arrays.equals(indices, that.indices)) return false;
if (!trigger.equals(that.trigger)) return false; if (query != null ? !query.equals(that.query) : that.query != null) return false;
if (searchResponse != null ? !searchResponse.equals(that.searchResponse) : that.searchResponse != null)
return false;
if (trigger != null ? !trigger.equals(that.trigger) : that.trigger != null) return false;
return true; return true;
} }
@Override @Override
public int hashCode() { public int hashCode() {
int result = searchResponse.hashCode(); int result = searchResponse != null ? searchResponse.hashCode() : 0;
result = 31 * result + trigger.hashCode(); result = 31 * result + (trigger != null ? trigger.hashCode() : 0);
result = 31 * result + (isTriggered ? 1 : 0); result = 31 * result + (isTriggered ? 1 : 0);
result = 31 * result + (query != null ? query.hashCode() : 0);
result = 31 * result + (indices != null ? Arrays.hashCode(indices) : 0);
return result; return result;
} }
} }

View File

@ -5,21 +5,15 @@
*/ */
package org.elasticsearch.alerting; package org.elasticsearch.alerting;
import org.apache.lucene.util.BytesRef;
import org.elasticsearch.ElasticsearchException; import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.action.indexedscripts.get.GetIndexedScriptResponse;
import org.elasticsearch.action.search.SearchRequestBuilder; import org.elasticsearch.action.search.SearchRequestBuilder;
import org.elasticsearch.action.search.SearchResponse; import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.Client; import org.elasticsearch.client.Client;
import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.component.AbstractLifecycleComponent; import org.elasticsearch.common.component.AbstractLifecycleComponent;
import org.elasticsearch.common.inject.Inject; import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.joda.time.DateTime; import org.elasticsearch.common.joda.time.DateTime;
import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.*; import org.elasticsearch.common.xcontent.*;
import org.elasticsearch.script.CompiledScript;
import org.elasticsearch.script.ExecutableScript;
import org.elasticsearch.script.ScriptService; import org.elasticsearch.script.ScriptService;
import org.quartz.*; import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory; import org.quartz.impl.StdSchedulerFactory;
@ -27,25 +21,21 @@ import org.quartz.simpl.SimpleJobFactory;
import java.io.IOException; import java.io.IOException;
import java.util.Date; import java.util.Date;
import java.util.HashMap;
import java.util.Map;
public class AlertScheduler extends AbstractLifecycleComponent { public class AlertScheduler extends AbstractLifecycleComponent {
Scheduler scheduler = null; Scheduler scheduler = null;
private final AlertManager alertManager; private final AlertManager alertManager;
private final Client client; private final Client client;
private final ScriptService scriptService;
private final TriggerManager triggerManager; private final TriggerManager triggerManager;
private final AlertActionManager actionManager; private final AlertActionManager actionManager;
@Inject @Inject
public AlertScheduler(Settings settings, AlertManager alertManager, ScriptService scriptService, Client client, public AlertScheduler(Settings settings, AlertManager alertManager, Client client,
TriggerManager triggerManager, AlertActionManager actionManager) { TriggerManager triggerManager, AlertActionManager actionManager) {
super(settings); super(settings);
this.alertManager = alertManager; this.alertManager = alertManager;
this.client = client; this.client = client;
this.scriptService = scriptService;
this.triggerManager = triggerManager; this.triggerManager = triggerManager;
this.actionManager = actionManager; this.actionManager = actionManager;
try { try {
@ -66,18 +56,24 @@ public class AlertScheduler extends AbstractLifecycleComponent {
logger.warn("Running the following query : [{}]", builder.string()); logger.warn("Running the following query : [{}]", builder.string());
SearchRequestBuilder srb = client.prepareSearch().setSource(builder); SearchRequestBuilder srb = client.prepareSearch().setSource(builder);
String[] indices = alert.indices().toArray(new String[0]);
if (alert.indices() != null ){ if (alert.indices() != null ){
srb.setIndices(alert.indices().toArray(new String[0])); logger.warn("Setting indices to : " + alert.indices());
srb.setIndices(indices);
} }
SearchResponse sr = srb.execute().get(); SearchResponse sr = srb.execute().get();
logger.warn("Got search response"); logger.warn("Got search response hits : [{}]", sr.getHits().getTotalHits() );
AlertResult result = new AlertResult(); AlertResult result = new AlertResult();
//TODO: move these to ctr
result.isTriggered = triggerManager.isTriggered(alertName,sr); result.isTriggered = triggerManager.isTriggered(alertName,sr);
result.searchResponse = sr; result.searchResponse = sr;
result.trigger = alert.trigger();
result.query = builder;
result.indices = indices;
if (result.isTriggered) { if (result.isTriggered) {
logger.warn("We have triggered"); logger.warn("We have triggered");
//actionManager.doAction(alertName,result); actionManager.doAction(alertName,result);
logger.warn("Did action !"); logger.warn("Did action !");
}else{ }else{
logger.warn("We didn't trigger"); logger.warn("We didn't trigger");
@ -94,7 +90,6 @@ public class AlertScheduler extends AbstractLifecycleComponent {
Date scheduledFireTime = jobExecutionContext.getScheduledFireTime(); Date scheduledFireTime = jobExecutionContext.getScheduledFireTime();
DateTime clampEnd = new DateTime(scheduledFireTime); DateTime clampEnd = new DateTime(scheduledFireTime);
DateTime clampStart = clampEnd.minusSeconds((int)alert.timePeriod().seconds()); DateTime clampStart = clampEnd.minusSeconds((int)alert.timePeriod().seconds());
logger.error("Subtracting : [{}] seconds from [{}] = [{}]", (int)alert.timePeriod().seconds(), clampEnd, clampStart );
XContentBuilder builder = XContentFactory.contentBuilder(XContentType.JSON); XContentBuilder builder = XContentFactory.contentBuilder(XContentType.JSON);
builder.startObject(); builder.startObject();

View File

@ -46,6 +46,10 @@ public class AlertTrigger {
this.value = value; this.value = value;
} }
public String toString(){
return triggerType + " " + trigger + " " + value;
}
public static enum SimpleTrigger { public static enum SimpleTrigger {
EQUAL, EQUAL,
NOT_EQUAL, NOT_EQUAL,
@ -54,8 +58,8 @@ public class AlertTrigger {
RISES_BY, RISES_BY,
FALLS_BY; FALLS_BY;
public static SimpleTrigger fromString(final String sAction) { public static SimpleTrigger fromString(final String sTrigger) {
switch (sAction) { switch (sTrigger) {
case ">": case ">":
return GREATER_THAN; return GREATER_THAN;
case "<": case "<":
@ -70,9 +74,32 @@ public class AlertTrigger {
case "<-": case "<-":
return FALLS_BY; return FALLS_BY;
default: default:
throw new ElasticsearchIllegalArgumentException("Unknown AlertAction:SimpleAction [" + sAction + "]"); throw new ElasticsearchIllegalArgumentException("Unknown AlertAction:SimpleAction [" + sTrigger + "]");
} }
} }
public static String asString(final SimpleTrigger trigger){
switch (trigger) {
case GREATER_THAN:
return ">";
case LESS_THAN:
return "<";
case EQUAL:
return "==";
case NOT_EQUAL:
return "!=";
case RISES_BY:
return "->";
case FALLS_BY:
return "<-";
default:
return "?";
}
}
public String toString(){
return asString(this);
}
} }
public static enum TriggerType { public static enum TriggerType {
@ -86,6 +113,19 @@ public class AlertTrigger {
throw new ElasticsearchIllegalArgumentException("Unknown AlertTrigger:TriggerType [" + sTriggerType + "]"); throw new ElasticsearchIllegalArgumentException("Unknown AlertTrigger:TriggerType [" + sTriggerType + "]");
} }
} }
public static String asString(final TriggerType triggerType){
switch (triggerType) {
case NUMBER_OF_EVENTS:
return "numberOfEvents";
default:
return "unknown";
}
}
public String toString(){
return asString(this);
}
} }
} }

View File

@ -6,10 +6,13 @@
package org.elasticsearch.alerting; package org.elasticsearch.alerting;
import org.elasticsearch.ElasticsearchException; import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.common.xcontent.XContentHelper;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHitField; import org.elasticsearch.search.SearchHitField;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Locale;
import java.util.Properties; import java.util.Properties;
import javax.mail.*; import javax.mail.*;
import javax.mail.internet.AddressException; import javax.mail.internet.AddressException;
@ -18,6 +21,7 @@ import javax.mail.internet.MimeMessage;
public class EmailAlertAction implements AlertAction { public class EmailAlertAction implements AlertAction {
List<Address> emailAddresses = new ArrayList<>(); List<Address> emailAddresses = new ArrayList<>();
String displayField = null;
String from = "esalertingtest@gmail.com"; String from = "esalertingtest@gmail.com";
String passwd = "elasticsearchforthewin"; String passwd = "elasticsearchforthewin";
@ -39,6 +43,10 @@ public class EmailAlertAction implements AlertAction {
} }
} }
public void displayField(String displayField){
this.displayField = displayField;
}
@Override @Override
public boolean doAction(String alertName, AlertResult result) { public boolean doAction(String alertName, AlertResult result) {
Properties props = new Properties(); Properties props = new Properties();
@ -57,13 +65,36 @@ public class EmailAlertAction implements AlertAction {
message.setFrom(new InternetAddress(from)); message.setFrom(new InternetAddress(from));
message.setRecipients(Message.RecipientType.TO, message.setRecipients(Message.RecipientType.TO,
emailAddresses.toArray(new Address[1])); emailAddresses.toArray(new Address[1]));
message.setSubject("Elasticsearch Alert from " + alertName); message.setSubject("Elasticsearch Alert " + alertName + " triggered");
message.setText(result.searchResponse.toString()); StringBuffer output = new StringBuffer();
output.append("The following query triggered because " + result.trigger.toString() + "\n");
output.append("The total number of hits returned : " + result.searchResponse.getHits().getTotalHits() + "\n");
output.append("For query : " + XContentHelper.convertToJson(result.query.bytes(),true,true) + "\n");
output.append("\n");
output.append("Indices : ");
for (String index : result.indices) {
output.append(index);
output.append("/");
}
output.append("\n");
output.append("\n");
if (displayField != null) {
for (SearchHit sh : result.searchResponse.getHits().getHits()) {
if (sh.sourceAsMap().containsKey(displayField)) {
output.append(sh.sourceAsMap().get(displayField).toString());
} else {
output.append(new String(sh.source()));
}
output.append("\n");
}
} else {
output.append(result.searchResponse.toString());
}
message.setText(output.toString());
Transport.send(message); Transport.send(message);
} catch (Exception e){ } catch (Exception e){
throw new ElasticsearchException("Failed to send mail", e); throw new ElasticsearchException("Failed to send mail", e);
} }
return true; return true;
} }
} }

View File

@ -5,7 +5,10 @@
*/ */
package org.elasticsearch.alerting; package org.elasticsearch.alerting;
import org.elasticsearch.ElasticsearchException;
import java.util.List; import java.util.List;
import java.util.Map;
public class EmailAlertActionFactory implements AlertActionFactory{ public class EmailAlertActionFactory implements AlertActionFactory{
@ -16,6 +19,19 @@ public class EmailAlertActionFactory implements AlertActionFactory{
for (String emailAddress : (List<String>)parameters) { for (String emailAddress : (List<String>)parameters) {
action.addEmailAddress(emailAddress); action.addEmailAddress(emailAddress);
} }
} else if (parameters instanceof Map) {
Map<String,Object> paramMap = (Map<String,Object>)parameters;
Object addresses = paramMap.get("addresses");
if (addresses == null){
throw new ElasticsearchException("Unable to parse email addresses from : " + parameters);
}
for (String emailAddress : (List<String>)addresses) {
action.addEmailAddress(emailAddress);
}
Object displayField = paramMap.get("display");
if (displayField != null){
action.displayField(displayField.toString());
}
} }
return action; return action;
} }

View File

@ -41,10 +41,10 @@ public class TriggerManager extends AbstractComponent {
logger.warn("Could not find alert named [{}] in alert manager perhaps it has been deleted.", alertName); logger.warn("Could not find alert named [{}] in alert manager perhaps it has been deleted.", alertName);
return false; return false;
} }
int testValue; long testValue;
switch (alert.trigger().triggerType()) { switch (alert.trigger().triggerType()) {
case NUMBER_OF_EVENTS: case NUMBER_OF_EVENTS:
testValue = response.getHits().getHits().length; testValue = response.getHits().getTotalHits();
break; break;
default: default:
throw new ElasticsearchIllegalArgumentException("Bad value for trigger.triggerType [" + alert.trigger().triggerType() + "]"); throw new ElasticsearchIllegalArgumentException("Bad value for trigger.triggerType [" + alert.trigger().triggerType() + "]");