Name
sn_em_ai.EvtMgmtProcessLogAnalyticsJson
Description
No description available
Script
var EvtMgmtProcessLogAnalyticsJson = Class.create();
EvtMgmtProcessLogAnalyticsJson.prototype = {
initialize: function() {
// Severities map
this.severityMap = {
LOW: '4',
MEDIUM: '3',
HIGH: '2',
CRITICAL: '1'
};
this.GROUP_TYPES = {
INCIDENT_GROUP_TYPE: 9,
TOP_ALERT_GROUP_TYPE: 10,
};
this.ALERT_CATEGORY = {
LOG_ANALYTICS_GROUP: "1",
LOG_ANALYTICS_ALERT: "2",
LOG_ANALYTICS_READ_ONLY: "3"
};
this.ALLOW_USER_TO_SET_STATE = {
ANY: "1",
CLOSE: "2",
NONE: "3"
};
this.TAGS = {
HIGHLIGHT_OPEN: "<highlight>",
HIGHLIGHT_CLOSE: "</highlight>",
STACKTRACE_OPEN: "<stacktrace>",
STACKTRACE_CLOSE: "</stacktrace>"
};
this.APP_SERVICE_TABLE = "cmdb_ci_service_auto";
this.EXTRA_DATA_TABLE = "em_alert_extra_data";
this.properties = {
LA_PREVENT_GROUPING_IDENTICAL_HOSTS: 'evt_mgmt.log_analytics.prevent_grouping_identical_hosts',
LA_PREVENT_GROUPING_IDENTICAL_HOSTS_DEFAULT_VALUE: 'false',
};
},
type: 'EvtMgmtProcessLogAnalyticsJson',
process: function(json) {
var services = "";
var servicesMap = {};
this.correlatorsMap = {};
var minSeverity = 4;
var topLevelExtraData = {};
var topLevelNode = "";
var differentHosts = false;
if (json && this.isValidTag(json, 'incident') && this.isValidTag(json.incident, 'topAlerts') && json.incident.topAlerts instanceof Array) {
var incident = json.incident;
var alertData;
var children = [];
for (i = 0; i < incident.topAlerts.length; i++) {
alertData = incident.topAlerts[i];
var child = {
additional_info: {}
};
var extraData = {};
var appService = this.getApplicationService(alertData);
var occultusProperties = this.getOccultusProperties(alertData);
extraData.component = this.setComponent(appService.service);
extraData.application_service = this.findAppService(appService.application);
var entitiesData = this.getEntities(alertData);
extraData.meaningful_entities = entitiesData ? JSON.stringify(entitiesData) : "";
extraData.anomaly = this.buildAnomalyText(alertData, occultusProperties);
extraData.explanation = this.getIdentifiedIssueHint(alertData);
extraData.correlators = JSON.stringify(this.getCorrelators(alertData));
child.extraData = extraData;
child.sn_component = extraData.component;
child.sn_fallback_binding_ci_id = extraData.application_service;
child.sn_application_service_name = appService.application;
child.sn_metric_type = alertData.metric.type;
child.sn_metric_query = alertData.metric.luceneQuery;
if (alertData.indexPatternId) {
child.sn_index_pattern_id = alertData.indexPatternId;
}
child.id = alertData.id;
child.metric_name = alertData.name;
child.resource = appService.application + "-" + appService.service;
child.type = alertData.type;
child.time_of_event = alertData.occurrenceTime;
// Pattern, if exists
if (alertData.hasOwnProperty("pattern") && alertData.pattern) {
child.sn_pattern_text = alertData.pattern.patternText;
child.sn_pattern_external_id = alertData.pattern.patternExternalId;
child.sn_pattern_id_per_source = alertData.pattern.idPerSource;
} else if (alertData.metric.type == "KeywordMetric" || alertData.metric.type == "PatternSeverityMetric") {
var decomposedPattern = this.getHighestFreqDecomposedPattern(alertData);
if (decomposedPattern != null && decomposedPattern.pattern != null) {
child.sn_pattern_text = decomposedPattern.pattern.patternText;
child.sn_pattern_id_per_source = decomposedPattern.pattern.idPerSource;
child.sn_pattern_external_id = decomposedPattern.pattern.patternExternalId;
}
}
// severity handling
child.severity = this.severityMap[alertData.severity];
if (parseInt(child.severity, 10) < minSeverity) {
minSeverity = parseInt(child.severity, 10);
}
// application service for incident level
var app = alertData.application;
if (app && !servicesMap.hasOwnProperty(app)) {
// new service
servicesMap[app] = 1;
if (i == 0) {
services += app;
} else {
services += ", " + app;
}
}
// description calculation
var desc_obj = this.getDescriptions(alertData, child);
child.description = desc_obj.description;
// we would save the tagged description according to the property
var saveInTable = gs.getProperty('evt_mgmt.save_formatted_description_in_extra_data_table', "false");
if (saveInTable == 'true') {
// save it in the em_alert_extra_data table
extraData.tagged_description = desc_obj.tagged_description;
} else {
// save it in as part of the additional_info field
child.sn_tagged_description = desc_obj.tagged_description;
}
// special case for log analytics short description
child.additional_info.sn_short_desc_override =
this.getKBSearchInfo(alertData, child.sn_pattern_text, child.sn_pattern_external_id, true, child.description);
child.additional_info.sn_kb_meta_data =
this.getKBSearchInfo(alertData, child.sn_pattern_text, child.sn_pattern_external_id, false, child.description);
// Anomaly data
if (alertData.metric && alertData.metric.properties) {
child.sn_metric_subject = alertData.metric.properties.subject;
child.sn_metric_dimension = alertData.metric.properties.dimension;
child.sn_metric_specific_type = alertData.metric.properties.type;
}
child.sn_metric_based_alert = "true";
child.sn_detection_type = alertData.detectionType;
child.sn_anomaly_current = alertData.currentValue;
child.sn_anomaly_expected = alertData.expectedValue;
child.sn_anomaly_change_percentage = alertData.changePercentage;
child.sn_points_in_timeless_trend = occultusProperties.points_in_timeless_trend;
child.sn_recent_events_period_seconds = occultusProperties.recent_events_period_seconds;
child.sn_custom_time = occultusProperties.customTime;
child.sn_custom_threshold = occultusProperties.customThreshold;
child.sn_custom_number = occultusProperties.customNumber;
child.sn_operator = occultusProperties.operator;
// hosts data
var hostsData = this.getHostsData(alertData);
var grandchildren = [];
// check special case when there is only 1 CI in this alert
if (Object.keys(hostsData).length == 1) {
child.node = this.findFqdnCi(Object.keys(hostsData)[0]);
// now, let's check if this is the same host across all alerts in this incident
if (!differentHosts) {
if (topLevelNode == "") {
topLevelNode = child.node; // assigning topLevelNode first time
} else {
if (topLevelNode != child.node) {
topLevelNode = ""; // different hosts in alerts, no host will be in the incident level
differentHosts = true;
}
}
}
} else { // multiple hosts in this alert
differentHosts = true;
topLevelNode = ""; // different hosts in alerts, no host will be in the incident level
for (h = 0; h < Object.keys(hostsData).length; h++) {
var grandchild = JSON.parse(JSON.stringify(child));
grandchild.node = Object.keys(hostsData)[h];
if (!grandchild.node.localeCompare("Others"))
continue; // ignore "Others"
grandchild.node = this.findFqdnCi(grandchild.node);
grandchild.id = child.id + "_" + grandchild.node;
grandchildren.push(grandchild);
}
child.children = grandchildren;
}
children.push(child);
}
var description = gs.getMessage("Group of alerts, issue identified in {0}", [services]);
var severity; // for incident level
if (this.isValidTag(incident, "severity")) {
severity = this.severityMap[incident.severity];
} else {
severity = minSeverity.toString();
}
if (services && !services.includes(",")) {
topLevelExtraData.application_service = this.findAppService(services);
}
var updatedCorrelatorsMap = this.removeSingleAlertCorrelators();
topLevelExtraData.correlators = JSON.stringify(updatedCorrelatorsMap);
return {
tree: {
id: incident.sysId,
description: description,
time_of_event: incident.occurrenceTime,
severity: severity,
node: topLevelNode,
extraData: topLevelExtraData,
children: children,
additional_info: {
sn_short_desc_override: '', // Always empty string for the incident alert
sn_fallback_binding_ci_id: topLevelExtraData.application_service
}
},
};
} else {
return {};
}
},
// find FQDN non-Retired CI for this host
findFqdnCi: function(host) {
if (gs.getProperty('evt_mgmt.log_analytics.look_for_fqdn_ci', 'true') == 'false') // option to disable this feature
return host;
if (!host || host.indexOf(".") >= 0)
return host; // host is empty or is already in FQDN format
var checkRetired = gs.getProperty('evt_mgmt.ignore_retired_cis_in_binding', 'true');
if (checkRetired === 'true') {
var ignoredStatuses = gs.getProperty('evt_mgmt.install_status_list_to_ignore_in_binding');
var ignoredStatusesList = ignoredStatuses.split(',');
}
var gr = new GlideRecord("cmdb_ci_hardware");
gr.addQuery('name', 'STARTSWITH', host + ".");
gr.query();
var count = 0;
var name = "";
while (gr.next()) {
var state = gr.getValue("install_status");
if (ignoredStatusesList.indexOf(state) >= 0)
continue; // ignore Retired CI
count++;
if (count > 1)
break; // many results - no FQDN fix, returning original host
name = gr.getValue("name");
}
if (count == 1)
return name;
return host; // no FQDN results or many results
},
setComponent: function(service) {
if (service && service != 'all services') {
return service;
}
//special case, when there is all services there is no need to filter the surrounding logs, so there is need to put All components here.
return "All components";
},
concatenateParts: function(parts) {
var result = "",
hl_message = "";
for (h = 0; h < parts.length; h++) {
if (parts[h].value) {
result += parts[h].value;
if (parts[h].examples && parts[h].examples.length > 0) {
hl_message += this.highlightTag(parts[h].value);
} else {
hl_message += parts[h].value;
}
}
}
return {
description: result,
tagged_description: hl_message
};
},
// find decomposed pattern with the highest "frequency" and return the relevant json element
getHighestFreqDecomposedPattern: function(alertData) {
if (this.isValidTag(alertData, "decomposedFrequentPatterns") && alertData.decomposedFrequentPatterns.length > 0) {
var patterns = alertData.decomposedFrequentPatterns;
var maxFreq = 0;
var maxIndex = 0;
for (h = 0; h < patterns.length; h++) {
if (patterns[h].frequency > maxFreq) {
maxFreq = patterns[h].frequency;
maxIndex = h;
}
}
return patterns[maxIndex].decomposedPattern;
}
return null;
},
// find decomposed pattern with the highest "frequency" and return the relevant message
highestFreqPattern: function(alertData) {
var decomposedPattern = this.getHighestFreqDecomposedPattern(alertData);
if (decomposedPattern != null && decomposedPattern.parts && decomposedPattern.parts.length > 0) {
parts = decomposedPattern.parts;
//TODO: Should be concatenated like a HOLMES message to set a limit on the length of the message (see getDescription)
return this.concatenateParts(parts);
} else {
var service = alertData.metric.properties.service;
//trend description can be "above normal / below normal / anomalous"
var trendDescription = this.getAnomalyTrendDescription(alertData.detectionType);
var message = '',
hl_message = '';
switch (alertData.metric.type) {
case "KeywordMetric":
var keyword = alertData.metric.properties.keyword;
message = gs.getMessage("The total volume of Keyword '{0}' from '{1}' is {2}", [keyword, service, trendDescription]);
hl_message = gs.getMessage("The total volume of Keyword {0}{2}{1} from '{3}' is {4}", [this.TAGS.HIGHLIGHT_OPEN, this.TAGS.HIGHLIGHT_CLOSE, keyword, service, trendDescription]);
break;
case "PatternSeverityMetric":
var severity = alertData.metric.properties.severity;
message = gs.getMessage("The total volume of {0} level logs from '{1}' is {2}", [severity, service, trendDescription]);
hl_message = gs.getMessage("The total volume of {0}{2}{1} level logs from '{3}' is {4}", [this.TAGS.HIGHLIGHT_OPEN, this.TAGS.HIGHLIGHT_CLOSE, severity, service, trendDescription]);
break;
}
return {
description: message,
tagged_description: hl_message
};
}
},
// find application service by name
findAppService: function(name) {
var gr = new GlideRecord(this.APP_SERVICE_TABLE);
gr.addQuery("name", name);
gr.query();
if (gr.next()) {
return gr.getValue("sys_id"); // GetValue()
}
return null;
},
// check if the JSON tag/key is valid
isValidTag: function(element, tag) {
return element.hasOwnProperty(tag) && element[tag];
},
// get hostsData tag
getHostsData: function(alertData) {
var hostsData = {};
entitiesData = this.getEntities(alertData);
if (entitiesData) {
for (h = 0; h < entitiesData.length; h++) {
var entity = entitiesData[h];
if (entity.entity.toLowerCase() == "host") {
hostsData = alertData.rootCauseAnalysis.entities.entities[h].distribution.relativeParts;
break;
}
}
}
return hostsData;
},
// get entities
getEntities: function(alertData) {
if (this.isValidTag(alertData, "rootCauseAnalysis") && this.isValidTag(alertData.rootCauseAnalysis, "entities") && this.isValidTag(alertData.rootCauseAnalysis.entities, "entities")) {
return alertData.rootCauseAnalysis.entities.entities;
}
return null;
},
getApplicationService: function(alertData) {
var res = {};
if (alertData.metric.type == 'CustomMetric') {
res['application'] = alertData.manualAlert.query.application ? alertData.manualAlert.query.application : "All components";
if (res['application'] != 'All components') {
res['service'] = alertData.manualAlert.query.service ? alertData.manualAlert.query.service : "all services";
} else {
res['service'] = "";
}
} else {
res['application'] = alertData.application;
res['service'] = alertData.service;
}
return res;
},
// construct both event descriptions: regular and hilghlited according to use case
getDescriptions: function(alertData, child) {
var description, tag_description;
var desc_obj = {};
var descriptionMaxSize = gs.getProperty('evt_mgmt.description_to_highlight_max_size', 3000);
if (alertData.detectionType == 'SIGNAL_DEAD') {
description = gs.getMessage("No data is streaming from '{0}'", [alertData.metric.properties.service]);
} else {
//trend description can be "above normal / below normal / anomalous"
var trendDescription = this.getAnomalyTrendDescription(alertData.detectionType);
switch (alertData.metric.type) {
case "PatternIdMetric":
var parts = alertData.decomposedPattern.parts;
desc_obj = this.concatenateParts(parts);
description = desc_obj.description;
tag_description = desc_obj.tagged_description;
break;
case "RawMetric":
var props = alertData.metric.properties;
switch (props.type) {
case "METER":
description = gs.getMessage("The volume of events with '{0} - {1}' is {2}", [props.tag, props.value, trendDescription]);
tag_description = gs.getMessage("The volume of events with {0}{2}{1} - {0}{3}{1} is {4}", [this.TAGS.HIGHLIGHT_OPEN, this.TAGS.HIGHLIGHT_CLOSE, props.tag, props.value, trendDescription]);
break;
case "GAUGE":
description = gs.getMessage("The value of '{0}' is {1}", [props.tag, trendDescription]);
tag_description = gs.getMessage("The value of {0}{2}{1} is {3}", [this.TAGS.HIGHLIGHT_OPEN, this.TAGS.HIGHLIGHT_CLOSE, props.tag, trendDescription]);
break;
case "TIMELESSGAUGE":
description = gs.getMessage("The value of '{0}' is {1}", [props.tag, trendDescription]);
tag_description = gs.getMessage("The value of {0}{2}{1} is {3}", [this.TAGS.HIGHLIGHT_OPEN, this.TAGS.HIGHLIGHT_CLOSE, props.tag, trendDescription]);
break;
}
break;
case "HistogramMetric":
description = gs.getMessage("The relative propotion of the following value is {0}", [trendDescription]);
break;
case "CustomMetric":
description = alertData.manualAlert.name;
break;
case "AllEventsMetric":
switch (alertData.detectionType) {
case "SIGNAL_DEAD":
description = gs.getMessage("No data is streaming from '{0}'", [alertData.metric.properties.service]);
break;
case "ANOMALY_BASELINE_REFERENCE_INCREASE":
case "ANOMALY_ABOVE_AVERAGE":
description = gs.getMessage("The total volume of events from '{0}' is above normal", [alertData.metric.properties.service]);
tag_description = gs.getMessage("The total volume of events from {0}{2}{1} is above normal", [this.TAGS.HIGHLIGHT_OPEN, this.TAGS.HIGHLIGHT_CLOSE, alertData.metric.properties.service]);
break;
case "ANOMALY_BASELINE_REFERENCE_DECREASE":
case "ANOMALY_BELOW_AVERAGE":
description = gs.getMessage("The total volume of events from '{0}' is below normal", [alertData.metric.properties.service]);
tag_description = gs.getMessage("The total volume of events from {0}{2}{1} is below normal", [this.TAGS.HIGHLIGHT_OPEN, this.TAGS.HIGHLIGHT_CLOSE, alertData.metric.properties.service]);
break;
default:
description = gs.getMessage("The total volume of events from '{0}' is anomalous", [alertData.metric.properties.service]);
tag_description = gs.getMessage("The total volume of events from {0}{2}{1} is anomalous", [this.TAGS.HIGHLIGHT_OPEN, this.TAGS.HIGHLIGHT_CLOSE, alertData.metric.properties.service]);
}
break;
case "KeywordMetric":
case "PatternSeverityMetric":
if (this.hasHolmesProperty(alertData)) {
var holmes = alertData.holmes;
var activePart;
description = "";
tag_description = "";
var before = "",
after = "";
for (h = 0; h < holmes.length; h++) {
if (holmes[h].holmesPart) {
// found the active part
if (h > 0) {
var beforeChar = holmes[h - 1].partText.slice(-46, -45);
before = holmes[h - 1].partText.slice(-45);
if (beforeChar != " " && before.length > 34) {
// we are in the middle of a word. Detect the next whole word, don't show part of a word
var spaceIndex = before.indexOf(' ');
if (spaceIndex < 10) {
before = before.slice(spaceIndex + 1);
}
}
}
if (h + 1 < holmes.length) {
after = holmes[h + 1].partText.slice(0, 45);
}
description += "..." + before + holmes[h].partText + after;
tag_description += '...' + before + this.highlightTag(holmes[h].partText) + after;
}
}
description += "...";
tag_description += "...";
} else {
desc_obj = this.highestFreqPattern(alertData);
description = desc_obj.description;
tag_description = desc_obj.tagged_description;
}
break;
}
if (alertData.hasOwnProperty("properties") && alertData.properties &&
alertData.properties.hasOwnProperty("stacktrace") && alertData.properties.stacktrace) {
var stacktrace = alertData.properties.stacktrace;
if (tag_description == "")
tag_description = description;
description += "\n\nStacktrace: \n" + this.TAGS.STACKTRACE_OPEN + stacktrace + this.TAGS.STACKTRACE_CLOSE;
// in case tag_description is empty, only in stacktrace cases we will copy the description to the tag_description and add the stacktrace tage
// in the future - stacktrace tags will appeare only in tag_description
// we want to verify that the tag_description field will not exceed the max size (default 3000) once the stacktrace is added
// so we calculate its length plus the added tags length and slice the stacktrace to fit the size
var tag_desc_length = tag_description.length + 50;
if (stacktrace.length > descriptionMaxSize - tag_desc_length)
stacktrace = stacktrace.slice(0, descriptionMaxSize - tag_desc_length);
tag_description += "\n\nStacktrace: \n" + this.TAGS.STACKTRACE_OPEN + stacktrace + this.TAGS.STACKTRACE_CLOSE;
}
}
// In order to determin whether to highlight the description or not
// we need to verify the decription's length is not higher than 3000
// or, if set in property: evt_mgmt_description_to_highlight_max_size not higher than this number
if (tag_description && tag_description.length > descriptionMaxSize)
tag_description = "";
return {
description: description,
tagged_description: tag_description
};
},
highlightTag: function(content) {
return this.TAGS.HIGHLIGHT_OPEN + content + this.TAGS.HIGHLIGHT_CLOSE;
},
buildAnomalyText: function(alertData, occultusProperties) {
var str;
// we need to present the percentage as a positive value
var changePercentage = this.percentageAbsoluteVal(alertData.changePercentage);
//set maximum 2 digits after the point to numerival vals
changePercentage = changePercentage <= 1000 ? gs.getMessage("{0}", [this.formatTwo(changePercentage)]) : gs.getMessage("above 1000");
var currentValue = this.formatTwo(alertData.currentValue);
var expectedValue = this.formatTwo(alertData.expectedValue);
var metricType = alertData.metric.properties.type;
switch (alertData.detectionType) {
case ('SIGNAL_ALIVE'):
str = gs.getMessage("{0} Events per minute, Typically inactive", currentValue);
break;
case ('ANOMALY_BASELINE_REFERENCE_INCREASE'):
currentValue = this.formatTwo(parseFloat(alertData.currentValue));
var anomalyIncreasedDiffValue = alertData.currentValue - alertData.expectedValue;
expectedValue = this.formatTwo(parseFloat(anomalyIncreasedDiffValue));
changePercentage = this.formatTwo(((alertData.currentValue / anomalyIncreasedDiffValue) * 100) - 100);
if (metricType == 'GAUGE') {
str = gs.getMessage("Avg. baseline value: {0} Same hour - one week earlier: {1} ({2}% increase)", [currentValue, this.formatTwo(anomalyIncreasedDiffValue), changePercentage]);
} else {
str = gs.getMessage("{0} Events per minute, Same hour - one week earlier: {1}. ({2}% increase)", [currentValue, expectedValue, changePercentage]);
}
break;
case ('ANOMALY_BASELINE_REFERENCE_DECREASE'):
currentValue = this.formatTwo(parseFloat(alertData.currentValue));
var anomalyDecreasedDiffValue = alertData.currentValue - alertData.expectedValue;
expectedValue = this.formatTwo(parseFloat(anomalyDecreasedDiffValue));
changePercentage = this.formatTwo(((anomalyDecreasedDiffValue - alertData.currentValue) / anomalyDecreasedDiffValue) * 100);
if (metricType == 'GAUGE') {
str = gs.getMessage("{0} Avg. baseline value, Same hour - one week earlier: {1} ({2}% decrease)", [currentValue, this.formatTwo(anomalyDecreasedDiffValue), changePercentage]);
} else {
str = gs.getMessage("{0} Events per minute, Same hour - one week earlier: {1}. ({2}% decrease)", [currentValue, expectedValue, changePercentage]);
}
break;
case ('ANOMALY_STEEP_INCREASE'):
case ('ANOMALY_ABOVE_AVERAGE'):
if (metricType == 'TIMELESSGAUGE') {
currentValue = this.formatTwo(parseFloat(alertData.currentValue));
expectedValue = this.formatTwo(parseFloat(alertData.expectedValue));
str = gs.getMessage("{0} Value of anomaly. Avg. of last {1} measurements: ({2}% increase)", [currentValue, expectedValue, changePercentage]);
} else if (metricType == 'GAUGE') {
currentValue = this.formatTwo(parseFloat(alertData.currentValue) * 60);
expectedValue = this.formatTwo(parseFloat(alertData.expectedValue) * 60);
str = gs.getMessage("{0} Value of anomaly. Avg. of last hour: {1}. ({2}% increase)", [currentValue, expectedValue, changePercentage]);
} else {
currentValue = this.formatTwo(parseFloat(alertData.currentValue) * 60);
expectedValue = this.formatTwo(parseFloat(alertData.expectedValue) * 60);
str = gs.getMessage("{0} Events per minute Same hour - one day earlier: {1}. ({2}% increase)", [currentValue, expectedValue, changePercentage]);
}
break;
case ('ANOMALY_STEEP_DECREASE'):
case ('ANOMALY_BELOW_AVERAGE'):
if (metricType == 'TIMELESSGAUGE') {
currentValue = this.formatTwo(parseFloat(alertData.currentValue));
expectedValue = this.formatTwo(parseFloat(alertData.expectedValue));
str = gs.getMessage("{0} Value of anomaly. Avg. of last {1} measurements: ({2}% decrease)", [currentValue, expectedValue, changePercentage]);
} else if (metricType == 'GAUGE') {
currentValue = this.formatTwo(parseFloat(alertData.currentValue) * 60);
expectedValue = this.formatTwo(parseFloat(alertData.expectedValue) * 60);
str = gs.getMessage("{0} Value of anomaly. Avg. of last hour: {1}. ({2}% decrease)", [currentValue, expectedValue, changePercentage]);
} else {
currentValue = this.formatTwo(parseFloat(alertData.currentValue) * 60);
expectedValue = this.formatTwo(parseFloat(alertData.expectedValue) * 60);
str = gs.getMessage("{0} Events per minute Same hour - one day earlier: {1}. ({2}% decrease)", [currentValue, expectedValue, changePercentage]);
}
break;
case ('TIMELESS_TREND_INCREASE'):
str = gs.getMessage("{0} Value of anomaly. Avg. of last {1} measurements: {2}({3}% Increase) ", [currentValue, occultusProperties.points_in_timeless_trend, expectedValue, changePercentage]);
break;
case ('TIMELESS_TREND_DECREASE'):
str = gs.getMessage("{0} Value of anomaly. Avg. of last {1} measurements: {2}({3}% Decrease) ", [currentValue, occultusProperties.points_in_timeless_trend, expectedValue, changePercentage]);
break;
case ('CUSTOM'):
str = gs.getMessage("Log Entries in the course of {0} {1}: {2} {3} {4} (defined threshold)", [occultusProperties.customNumber, occultusProperties.customTime, currentValue, occultusProperties.operator, occultusProperties.customThreshold]);
break;
case ('NEW_SIGNAL'):
str = gs.getMessage("New behaviour, No data to display at the moment");
break;
case ('SIGNAL_DEAD'):
str = gs.getMessage("No data is streaming from {0}", alertData.service);
break;
default:
str = gs.getMessage("No data, Unrecognized detection type");
}
return str;
},
getOccultusProperties: function(alertData) {
var points_in_timeless_trend;
var recent_events_period_seconds;
var time;
var threshold;
var number;
var operator;
if (alertData.detectionType == 'TIMELESS_TREND_DECREASE' ||
alertData.detectionType == 'TIMELESS_TREND_INCREASE') {
var gr = new GlideRecord("sn_occ_system_settings");
gr.addQuery("name", "detective.points_in_timeless_trend");
gr.query();
if (gr.next())
points_in_timeless_trend = gr.getValue("value");
gr.initialize();
gr.addQuery("name", "alerts.recent_events_for_timeless_gauge_period_seconds");
gr.query();
if (gr.next())
recent_events_period_seconds = gr.getValue("value");
}
if (alertData.detectionType == "CUSTOM") {
var sysId = alertData.metric.properties.manualAlertSysID;
var operatorVal;
var customGr = new GlideRecord("sn_occ_custom_alert");
customGr.addQuery("sys_id", sysId);
customGr.query();
if (customGr.next()) {
var period = parseInt(customGr.getValue("period"), 10);
threshold = customGr.getValue("value_count");
number = customGr.getValue("repetitions_in_period");
operatorVal = parseInt(customGr.getValue("comparison_operator"), 10);
if (period == 0) {
if (number == 1)
time = gs.getMessage("Minute");
else
time = gs.getMessage("Minutes");
} else {
if (number == 1)
time = gs.getMessage("Hour");
else
time = gs.getMessage("Hours");
}
switch (operatorVal) {
case 0:
operator = ">";
break;
case 1:
operator = "<";
break;
case 2:
operator = "=";
break;
case 3:
operator = ">=";
break;
case 4:
operator = "<=";
break;
default:
operator = ">";
break;
}
}
}
var occultusProperties = {};
occultusProperties.points_in_timeless_trend = points_in_timeless_trend;
occultusProperties.recent_events_period_seconds = recent_events_period_seconds;
occultusProperties.customTime = time;
occultusProperties.customThreshold = threshold;
occultusProperties.customNumber = number;
occultusProperties.operator = operator;
return occultusProperties;
},
getAllowUserToSetState: function(isGog, parentGroupType, myGroupType) {
// set the sn_allow_setting_state property based on the parent group type
if (!isGog) {
return this.ALLOW_USER_TO_SET_STATE.ANY;
}
switch (parentGroupType) {
case this.GROUP_TYPES.INCIDENT_GROUP_TYPE:
// parent level, means my rights are "any"
return this.ALLOW_USER_TO_SET_STATE.ANY;
case this.GROUP_TYPES.TOP_ALERT_GROUP_TYPE:
// middle level, means my rights are "none"
return this.ALLOW_USER_TO_SET_STATE.NONE;
default:
// I don't have a parent (null); now it depends on my own group type
if (myGroupType == this.GROUP_TYPES.INCIDENT_GROUP_TYPE) {
return this.ALLOW_USER_TO_SET_STATE.CLOSE; // close only
} else {
return this.ALLOW_USER_TO_SET_STATE.ANY; // My group type is top level, so allow "any"
}
}
},
/**
* Applies modifications on the finalized tree - i.e., after it was processed to its final structure.
*
* @param tree - the tree that was processed by this LogAnalytics processor
* @returns
*/
modifyTreeBeforeCreatingEvents: function(tree) {
if (!tree) {
return;
}
// The following checks the values of instance level properties and modifies the given tree accordingly.
// The default behavior of log analytics groups is to group HLA alerts with parent and children,
// regardless and independent of their hosts (CIs), in a way it doesn't matter if they have the same host or different hosts.
//
// Check the value of the 'evt_mgmt.log_analytics.prevent_grouping_identical_hosts' property,
// by which treenodes with the same host will not be grouped, so they will be created as standalone alerts.
//
// TreeNodes with different hosts will maintain the default behavior and create a group.
//
// TreeNodes with the same host are identified by the "node" field of their parent.
// - An empty "node" field for the parent treenode (the root node) indicates the tree nodes don't share the same host.
// - A non-empty "node" field for the parent event (the root node) indicates the tree nodes share the host that the parent "node" field is set to.
//
// To prevent HLA alerts from grouping we'll set GOG flag (additional_info key) to false for this treeNode
// so this will affect the result of the event that's returned from 'getCustomizedEvent(treeNode)' in a way that:
// 1. sn_partofGOGgroup - will be set to false, to prevent grouping.
// 2. getManipulatedTimeOfEvent - event time won't be manipulated, as it depends on the sn_partofGOGgroup value.
// 3. event.sn_allow_setting_state - state could be changed to any, as it depends on the sn_partofGOGgroup value.
var preventGroupingIdenticalHosts = gs.getProperty(this.properties.LA_PREVENT_GROUPING_IDENTICAL_HOSTS, this.properties.LA_PREVENT_GROUPING_IDENTICAL_HOSTS_DEFAULT_VALUE);
if (preventGroupingIdenticalHosts === 'true') {
// If the parent treeNode has a "node" value then its children are sharing the same host.
// In this case:
// 1. Don't create the parent node, by setting its createAsEvent flag to false.
// 2. Set the child tree nodes so they won't be GoG.
var treeRootNode = tree.getRoot();
if (treeRootNode.isGog() && treeRootNode.getEventNode()) {
// Skip on the creation of the tree node
treeRootNode.setCreateAsEvent(false);
// Set all children of treeNode as not GoG
var allTreeNodesArray = tree.toArray();
for (var i = 0; i < allTreeNodesArray.length; i++) {
allTreeNodesArray[i].setGog(false);
}
}
}
return; // Return, assuming that checking the next properties is an irrelevant check
},
getGroupType: function(treeNode) {
var treeLevel = treeNode.getActualTreeNodeLevel();
treeLevel = parseInt(treeLevel, 10);
switch (treeLevel) {
case 1:
return this.GROUP_TYPES.INCIDENT_GROUP_TYPE;
case 2:
return this.GROUP_TYPES.TOP_ALERT_GROUP_TYPE;
default:
return '';
}
},
getManipulatedTimeOfEvent: function(treeNode) {
// Don't manipulate the time of an alert that its category is "Log Analytics Alert"
var category = !treeNode.isGog() ? this.ALERT_CATEGORY.LOG_ANALYTICS_ALERT : this.getCategoryByLevel(treeNode.getActualTreeNodeLevel());
// If the cateogry is not Log Analytics Alert
switch (category) {
case this.ALERT_CATEGORY.LOG_ANALYTICS_GROUP:
var nodeTime = treeNode.getOriginalTime();
var childTime = '';
// If there are children then get the minimum time of all children and subtract a second from it
if (treeNode.hasChildren()) {
childTime = treeNode.getChildAt(0).getOriginalTime(); // init with the first child time
for (var i = 0; i < treeNode.getChildCount(); i++) {
if (treeNode.getChildAt(i).getOriginalTime() < childTime) {
childTime = treeNode.getChildAt(i).getOriginalTime();
}
}
}
if (childTime && (childTime <= nodeTime)) {
return this.manipulateTime(childTime, 'sub', 1);
}
// If there are no children, or the parent already has the earliest time then return time as it
else {
return this.manipulateTime(nodeTime, '', 0);
}
case this.ALERT_CATEGORY.LOG_ANALYTICS_ALERT:
return this.manipulateTime(treeNode.getOriginalTime(), '', 0);
case this.ALERT_CATEGORY.LOG_ANALYTICS_READ_ONLY:
// Return the parent time of event plus an addition
return this.manipulateTime(treeNode.getParent().getOriginalTime(), 'add', 1);
default:
return new GlideDateTime().getValue();
}
},
/**
* Returns date in format "YYYY-MM-DD HH:MM:SS", for given milliseconds
* @eventTime - number: milliseconds to get date for
* @action - str: the action to apply on the given ms, 'add' to add, 'sub' to subtract or '' to leave as is just for the conversion of the format
* @seconds - number: number of seconds to add or subtract from the given ms
**/
manipulateTime: function(eventTime, action, seconds) {
seconds = parseInt(Number(seconds), 10);
var isValidSeconds = !isNaN(seconds);
if (eventTime && isValidSeconds) {
var milliseconds = seconds * 1000;
var gdt = new GlideDateTime();
gdt.setValue(eventTime);
switch (action) {
case 'add':
gdt.add(milliseconds);
break;
case 'sub':
gdt.subtract(milliseconds);
break;
default:
break;
}
return gdt.getValue();
}
return new GlideDateTime().getValue();
},
/*
* Adds data to the event that will be created:
* - Keys that will be named as the event table columns, will override existing values.
* - Keys that are not part of the event table will be inserted to the event's additional_info field.
*/
addToEvent: function(event, treeNode) {
event.sn_allow_setting_state = this.getAllowUserToSetState(treeNode.isGog(), treeNode.getParentGroupType(this), treeNode.getMyGroupType(this));
},
/*
* Adds data to the em_alert_extra_data table.
* - Keys must be named as one of the fields in em_alert_extra_data.
*/
manipulateExtraData: function(treeNode) {
// Add alert category to extra data table only if part of GOG
var category = !treeNode.isGog() ? this.ALERT_CATEGORY.LOG_ANALYTICS_ALERT : this.getCategoryByLevel(treeNode.getActualTreeNodeLevel());
treeNode.addToExtraData('category', category);
// Remove data from extra data of read only alerts
if (category == this.ALERT_CATEGORY.LOG_ANALYTICS_READ_ONLY) {
treeNode.removeFromExtraData(['meaningful_entities', 'correlators']);
}
},
getCategoryByLevel: function(treeLevel) {
treeLevel = parseInt(treeLevel, 10);
switch (treeLevel) {
case 1:
return this.ALERT_CATEGORY.LOG_ANALYTICS_GROUP;
case 2:
return this.ALERT_CATEGORY.LOG_ANALYTICS_ALERT;
default:
return this.ALERT_CATEGORY.LOG_ANALYTICS_READ_ONLY;
}
},
// construct identified issue hint according to use case
getIdentifiedIssueHint: function(alertData) {
var result = "";
if (alertData.detectionType == "SIGNAL_DEAD") {
result = gs.getMessage("No data is streaming");
} else {
switch (alertData.metric.type) {
case "PatternIdMetric":
if (alertData.detectionType == "NEW_SIGNAL") {
var component = alertData.service;
result = gs.getMessage("This is the first time this pattern appears in '{0}'", component);
} else {
result = gs.getMessage("The following pattern is anomalous");
}
break;
case "RawMetric":
result = gs.getMessage("The following metric is anomalous");
break;
case "CustomMetric":
result = gs.getMessage("A manually defined threshold has been crossed");
break;
case "AllEventsMetric":
result = gs.getMessage("There is a spike in the total volume of events");
break;
case "KeywordMetric":
var keyword = alertData.metric.properties.keyword;
if (this.hasHolmesProperty(alertData)) {
result = gs.getMessage("This is the dominant contributing pattern for the anomaly. The alert was triggered by a surge in the keyword '{0}'", keyword);
} else {
result = gs.getMessage("The alert was triggered by a surge in the keyword '{0}'", keyword);
}
break;
case "PatternSeverityMetric":
var severity = alertData.metric.properties.severity;
if (this.hasHolmesProperty(alertData)) {
result = gs.getMessage("This is the dominant contributing pattern for the anomaly. The alert was triggered by a surge in the log severity level '{0}'", severity);
} else {
result = gs.getMessage("The alert was triggered by a surge in the log severity level '{0}'", severity);
}
break;
case "HistogramMetric":
result = gs.getMessage("The relative propotion of the following value is anomalous");
break;
default:
result = gs.getMessage("The following metric is anomalous");
}
}
return result;
},
getCorrelators: function(alertData) {
var result = "";
if (alertData.hasOwnProperty("correlatingTags") && alertData.correlatingTags) {
var data = alertData.correlatingTags;
for (var h = 0; h < data.length; h++) {
var key = Object.keys(data[h])[0];
var value = data[h][key];
key = key.replace("__loom_pattern_text", "Log Pattern");
key = key.replace("__loom_detection_time", "Occurrence Time");
key = key.replace("__loom_detection_type", "Anomaly Type");
key = key.replace("__loom_detection_trend_up", "Anomaly Trend Up");
key = key.replace("__loom_detection_trend_down", "Anomaly Trend Down");
key = key.replace("__loom_application", "Application service");
key = key.replace("__loom_service", "Component");
key = key.replace("__loom_host", "Assigned Host");
if (key == "__loom_in_text") // this is deprecated
continue;
if (key == "Log Pattern") {
value = "Correlation based on similar text patterns '" + value.slice(0, 20) + "...'";
}
var pair = key + ": " + value;
// for top level alert level
result += pair + ",";
// for incident level
if (!this.correlatorsMap.hasOwnProperty(pair)) {
this.correlatorsMap[pair] = 1;
} else {
this.correlatorsMap[pair]++;
}
}
return result;
}
},
removeSingleAlertCorrelators: function() {
var updatedCorrelatorsMap = {};
var map = this.correlatorsMap;
Object.keys(this.correlatorsMap).forEach(function(key) {
if (map[key] > 1) {
updatedCorrelatorsMap[key] = map[key];
}
});
return updatedCorrelatorsMap;
},
percentageAbsoluteVal: function(changePercentage) {
if (!changePercentage)
return "";
var changePercentageVal = Number(changePercentage);
if (changePercentageVal < 0)
changePercentageVal *= -1;
// if there is only 0 after the point we want to present a whole number
if (changePercentageVal - parseInt(changePercentageVal) == 0)
return changePercentageVal.toFixed();
return changePercentageVal;
},
formatTwo: function(val) {
if (!val)
return "";
var x = parseInt(Math.round(val * 100), 10);
return (x / 100).toString();
},
// find patternText from decomposed frequent patterns
getDecomposedPatternsText: function(alertData, patternTextArr, kbSearchDelimiter, forShortDescription) {
if (this.isValidTag(alertData, "decomposedFrequentPatterns") && alertData.decomposedFrequentPatterns.length > 0) {
var dfp = alertData.decomposedFrequentPatterns;
for (var idx = 0; idx < dfp.length; idx++) {
if (this.isValidTag(dfp[idx], "decomposedPattern")) {
var dp = dfp[idx].decomposedPattern;
if (this.isValidTag(dp, "pattern")) {
var pattern = dp.pattern;
if (this.isValidTag(pattern, "patternText")) {
var patternTextWithQuotes = "\"" + pattern.patternText + "\"";
if (patternTextArr.indexOf(patternTextWithQuotes) < 0) {
this.addDelimiter(kbSearchDelimiter, patternTextArr, forShortDescription);
patternTextArr.push(patternTextWithQuotes);
}
}
if (this.isValidTag(pattern, "patternExternalId")) {
if (patternTextArr.indexOf(pattern.patternExternalId) < 0) {
this.addDelimiter(kbSearchDelimiter, patternTextArr, forShortDescription);
patternTextArr.push(pattern.patternExternalId);
}
}
}
}
}
}
},
getKBSearchInfo: function(alertData, patternText, patternExternalId, forShortDescription, description) {
//Check in the JSON for pattern alerts, search for "patternExternalId" property.
//This property should always work
//assuming the user labeled the external ID label in the source type structure
//If it's not a pattern alert we should search for "event_id".
//We should make sure that we also catch all the combination
//(phases) of event_id ,i.e: winlog.event_id
//pattern text matching - if we didn't find any matching based on reference ID
//we should search the pattern text property value ("patternText")
//also directly under the alertData and inside the decomposedFrequentPatterns
var resultArr = [];
var result = "";
var kbSearchDelimiter = gs.getProperty('evt_mgmt.log_analytics.kb_search_demiliter', "OR"); // set this property to false to disable adding delimiter
if (kbSearchDelimiter && kbSearchDelimiter != "false")
kbSearchDelimiter = " " + kbSearchDelimiter + " ";
else
kbSearchDelimiter = "";
try {
if (alertData.properties) {
if (this.isValidTag(alertData.properties, "ExternalId")) {
resultArr.push(alertData.properties.ExternalId);
}
if (patternExternalId) {
if (resultArr.indexOf(patternExternalId) < 0) {
this.addDelimiter(kbSearchDelimiter, resultArr, forShortDescription);
resultArr.push(patternExternalId);
}
}
//event id
for (var k in alertData.properties) {
if (k.includes("event_id")) {
if (resultArr.indexOf(alertData.properties[k]) < 0) {
this.addDelimiter(kbSearchDelimiter, resultArr, forShortDescription);
resultArr.push(alertData.properties[k]);
}
}
}
if (patternText) {
var patternTextWithQuotes = "\"" + patternText + "\"";
if (resultArr.indexOf(patternTextWithQuotes) < 0) {
this.addDelimiter(kbSearchDelimiter, resultArr, forShortDescription);
resultArr.push(patternTextWithQuotes);
}
}
if (forShortDescription) {
//expand data from decomposed frequent patterns
this.getDecomposedPatternsText(alertData, resultArr, kbSearchDelimiter, forShortDescription);
}
result = resultArr.join(" ");
if (!result)
result = "\"" + description + "\""; // We didnt found any field that helps us with KB search so we set the description
}
} catch (err) {
gs.error("EvtMgmtProcessLogAnalyticsJson.getKBSearchInfo failed: " + err);
}
return result;
},
addDelimiter: function(delimiter, resultArr, forShortDescription) {
if (forShortDescription && delimiter && resultArr.length > 0) {
resultArr.push(delimiter);
}
},
hasHolmesProperty: function(alertData) {
return (alertData.hasOwnProperty("holmes") && alertData.holmes && alertData.holmes.length > 0);
},
getAnomalyTrendDescription: function(detectionType) {
var trendDescription = gs.getMessage("anomalous");
if (detectionType) {
if (detectionType.indexOf('_INCREASE') > 0 || detectionType.indexOf('_ABOVE') > 0 || detectionType.indexOf('_ALIVE') > 0) {
trendDescription = gs.getMessage("above normal");
} else if (detectionType.indexOf('_DECREASE') > 0 || detectionType.indexOf('_BELOW') > 0 || detectionType.indexOf('_DEAD') > 0) {
trendDescription = gs.getMessage("below normal");
}
}
return trendDescription;
},
};
Sys ID
de00a514b74310107c038229ce11a923