diff --git a/client/pom.xml b/client/pom.xml
index 23892e14091..61cda76aa2e 100644
--- a/client/pom.xml
+++ b/client/pom.xml
@@ -65,6 +65,11 @@
cloud-plugin-network-nvp
${project.version}
+
+ org.apache.cloudstack
+ cloud-plugin-syslog-alerts
+ ${project.version}
+
org.apache.cloudstack
cloud-plugin-snmp-alerts
diff --git a/client/tomcatconf/log4j-cloud.xml.in b/client/tomcatconf/log4j-cloud.xml.in
index ce4079f9c96..0e7f004eb80 100755
--- a/client/tomcatconf/log4j-cloud.xml.in
+++ b/client/tomcatconf/log4j-cloud.xml.in
@@ -74,6 +74,20 @@ under the License.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -166,6 +180,7 @@ under the License.
+
diff --git a/plugins/alert-handlers/syslog-alerts/pom.xml b/plugins/alert-handlers/syslog-alerts/pom.xml
new file mode 100644
index 00000000000..21aa54a7be2
--- /dev/null
+++ b/plugins/alert-handlers/syslog-alerts/pom.xml
@@ -0,0 +1,40 @@
+
+
+
+ cloudstack-plugins
+ org.apache.cloudstack
+ 4.2.0-SNAPSHOT
+ ../../pom.xml
+
+ 4.0.0
+ Apache CloudStack Plugin - Syslog Alerts
+ cloud-plugin-syslog-alerts
+
+
+
+ log4j
+ log4j
+ ${cs.log4j.version}
+
+
+
+
\ No newline at end of file
diff --git a/plugins/alert-handlers/syslog-alerts/src/org/apache/cloudstack/syslog/AlertsSyslogAppender.java b/plugins/alert-handlers/syslog-alerts/src/org/apache/cloudstack/syslog/AlertsSyslogAppender.java
new file mode 100644
index 00000000000..d2f25654c7a
--- /dev/null
+++ b/plugins/alert-handlers/syslog-alerts/src/org/apache/cloudstack/syslog/AlertsSyslogAppender.java
@@ -0,0 +1,336 @@
+// 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.cloudstack.syslog;
+
+import com.cloud.utils.net.NetUtils;
+import org.apache.log4j.AppenderSkeleton;
+import org.apache.log4j.net.SyslogAppender;
+import org.apache.log4j.spi.LoggingEvent;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.StringTokenizer;
+
+public class AlertsSyslogAppender extends AppenderSkeleton {
+ String _syslogHosts = null;
+ String _delimiter = ",";
+ List _syslogHostsList = null;
+ List _syslogAppenders = null;
+ private String _facility;
+ private String _pairDelimiter = "//";
+ private String _keyValueDelimiter = "::";
+ private int alertType = -1;
+ private long dataCenterId = 0;
+ private long podId = 0;
+ private long clusterId = 0;
+ private String sysMessage = null;
+ public static final int LENGTH_OF_STRING_MESSAGE_AND_KEY_VALUE_DELIMITER = 9;
+ public static final int LENGTH_OF_STRING_MESSAGE = 8;
+ public static final String MESSAGE_DELIMITER_STRING = " ";
+ //add the alertType in this array it its level needs to be set to critical
+ private static final int[] criticalAlerts = {7, 8, 9, 10, 11, 12, 13, 15, 16, 19, 20};
+ private static final Map alertsMap;
+
+ static {
+ Map aMap = new HashMap(27);
+ aMap.put(0, "availableMemory");
+ aMap.put(1, "availableCpu");
+ aMap.put(2, "availableStorage");
+ aMap.put(3, "remainingStorageAllocated");
+ aMap.put(4, "unallocatedVirtualNetworkpublicIp");
+ aMap.put(5, "unallocatedPrivateIp");
+ aMap.put(6, "availableSecondaryStorage");
+ aMap.put(7, "host");
+ aMap.put(8, "userVmState");
+ aMap.put(9, "domainRouterVmState ");
+ aMap.put(10, "consoleProxyVmState");
+ aMap.put(11, "routingConnection");
+ aMap.put(12, "storageIssueSystemVms");
+ aMap.put(13, "usageServerStatus");
+ aMap.put(14, "managementNode");
+ aMap.put(15, "domainRouterMigrate");
+ aMap.put(16, "consoleProxyMigrate");
+ aMap.put(17, "userVmMigrate");
+ aMap.put(18, "unallocatedVlan");
+ aMap.put(19, "ssvmStopped");
+ aMap.put(20, "usageServerResult");
+ aMap.put(21, "storageDelete");
+ aMap.put(22, "updateResourceCount");
+ aMap.put(23, "usageSanityResult");
+ aMap.put(24, "unallocatedDirectAttachedPublicIp");
+ aMap.put(25, "unallocatedLocalStorage");
+ aMap.put(26, "resourceLimitExceeded");
+
+ alertsMap = Collections.unmodifiableMap(aMap);
+ }
+
+ @Override
+ protected void append(LoggingEvent event) {
+ if (!isAsSevereAsThreshold(event.getLevel())) {
+ return;
+ }
+
+ if (_syslogAppenders != null && !_syslogAppenders.isEmpty()) {
+ try {
+ String logMessage = event.getRenderedMessage();
+ if (logMessage.contains("alertType") && logMessage.contains("message")) {
+ parseMessage(logMessage);
+ String syslogMessage = createSyslogMessage();
+
+ LoggingEvent syslogEvent = new LoggingEvent(event.getFQNOfLoggerClass(), event.getLogger(),
+ event.getLevel(), syslogMessage, null);
+
+ for (SyslogAppender syslogAppender : _syslogAppenders) {
+ syslogAppender.append(syslogEvent);
+ }
+ }
+ } catch (Exception e) {
+ errorHandler.error(e.getMessage());
+ }
+ }
+ }
+
+ @Override
+ synchronized public void close() {
+ for (SyslogAppender syslogAppender : _syslogAppenders) {
+ syslogAppender.close();
+ }
+ }
+
+ @Override
+ public boolean requiresLayout() {
+ return true;
+ }
+
+ void setSyslogAppenders() {
+ if (_syslogAppenders == null) {
+ _syslogAppenders = new ArrayList();
+ }
+
+ if (_syslogHosts == null || _syslogHosts.trim().isEmpty()) {
+ reset();
+ return;
+ }
+
+ _syslogHostsList = parseSyslogHosts(_syslogHosts);
+
+ if (!validateIpAddresses()) {
+ reset();
+ errorHandler.error(" Invalid format for the IP Addresses parameter ");
+ return;
+ }
+
+ for (String syslogHost : _syslogHostsList) {
+ _syslogAppenders.add(new SyslogAppender(getLayout(), syslogHost, SyslogAppender.getFacility(_facility)));
+ }
+ }
+
+ private List parseSyslogHosts(String syslogHosts) {
+ List result = new ArrayList();
+
+ final StringTokenizer tokenizer = new StringTokenizer(syslogHosts, _delimiter);
+ while (tokenizer.hasMoreTokens()) {
+ result.add(tokenizer.nextToken().trim());
+ }
+ return result;
+ }
+
+ private boolean validateIpAddresses() {
+ for (String ipAddress : _syslogHostsList) {
+ if (ipAddress.trim().equalsIgnoreCase("localhost")) {
+ continue;
+ }
+ if (!NetUtils.isValidIp(ipAddress)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ void parseMessage(String logMessage) {
+ final StringTokenizer messageSplitter = new StringTokenizer(logMessage, _pairDelimiter);
+ while (messageSplitter.hasMoreTokens()) {
+ final String pairToken = messageSplitter.nextToken();
+ final StringTokenizer pairSplitter = new StringTokenizer(pairToken, _keyValueDelimiter);
+ String keyToken;
+ String valueToken;
+
+ if (pairSplitter.hasMoreTokens()) {
+ keyToken = pairSplitter.nextToken().trim();
+ } else {
+ break;
+ }
+
+ if (pairSplitter.hasMoreTokens()) {
+ valueToken = pairSplitter.nextToken().trim();
+ } else {
+ break;
+ }
+
+ if (keyToken.equalsIgnoreCase("alertType") && !valueToken.equalsIgnoreCase("null")) {
+ alertType = Short.parseShort(valueToken);
+ } else if (keyToken.equalsIgnoreCase("dataCenterId") && !valueToken.equalsIgnoreCase("null")) {
+ dataCenterId = Long.parseLong(valueToken);
+ } else if (keyToken.equalsIgnoreCase("podId") && !valueToken.equalsIgnoreCase("null")) {
+ podId = Long.parseLong(valueToken);
+ } else if (keyToken.equalsIgnoreCase("clusterId") && !valueToken.equalsIgnoreCase("null")) {
+ clusterId = Long.parseLong(valueToken);
+ } else if (keyToken.equalsIgnoreCase("message") && !valueToken.equalsIgnoreCase("null")) {
+ sysMessage = getSyslogMessage(logMessage);
+ }
+ }
+ }
+
+ String createSyslogMessage() {
+ StringBuilder message = new StringBuilder();
+ message.append(severityOfAlert(alertType)).append(MESSAGE_DELIMITER_STRING);
+ InetAddress ip;
+ try {
+ ip = InetAddress.getLocalHost();
+ } catch (UnknownHostException e) {
+ ip = null;
+ }
+
+ if (ip != null) {
+ message.append(ip.getHostName()).append(MESSAGE_DELIMITER_STRING);
+ } else {
+ message.append("unknown" + MESSAGE_DELIMITER_STRING);
+ }
+
+ if (alertType > 0) {
+ message.append("alertType").append(_keyValueDelimiter).append(" ").append(alertsMap.get(alertType))
+ .append(MESSAGE_DELIMITER_STRING);
+ if (dataCenterId != 0) {
+ message.append("dataCenterId").append(_keyValueDelimiter).append(" ").append(dataCenterId)
+ .append(MESSAGE_DELIMITER_STRING);
+ }
+
+ if (podId != 0) {
+ message.append("podId").append(_keyValueDelimiter).append(" ").append(podId)
+ .append(MESSAGE_DELIMITER_STRING);
+ }
+
+ if (clusterId != 0) {
+ message.append("clusterId").append(_keyValueDelimiter).append(" ").append(clusterId)
+ .append(MESSAGE_DELIMITER_STRING);
+ }
+
+ if (sysMessage != null) {
+ message.append("message").append(_keyValueDelimiter).append(" ").append(sysMessage);
+ } else {
+ errorHandler.error(" What is the use of alert without message ");
+ }
+ } else {
+ errorHandler.error(" Invalid alert Type ");
+ }
+
+ return message.toString();
+ }
+
+ private String getSyslogMessage(String message) {
+ int lastIndexOfKeyValueDelimiter = message.lastIndexOf(_keyValueDelimiter);
+ int lastIndexOfMessageInString = message.lastIndexOf("message");
+
+ if (lastIndexOfKeyValueDelimiter - lastIndexOfMessageInString <=
+ LENGTH_OF_STRING_MESSAGE_AND_KEY_VALUE_DELIMITER) {
+ return message.substring(lastIndexOfKeyValueDelimiter + _keyValueDelimiter.length()).trim();
+ } else if (lastIndexOfMessageInString < lastIndexOfKeyValueDelimiter) {
+ return message.substring(
+ lastIndexOfMessageInString + _keyValueDelimiter.length() + LENGTH_OF_STRING_MESSAGE).trim();
+ }
+
+ return message.substring(message.lastIndexOf("message" + _keyValueDelimiter) +
+ LENGTH_OF_STRING_MESSAGE_AND_KEY_VALUE_DELIMITER).trim();
+ }
+
+ private void reset() {
+ _syslogAppenders.clear();
+ }
+
+ public void setFacility(String facility) {
+ if (facility == null) {
+ return;
+ }
+
+ this._facility = facility;
+ if (_syslogAppenders != null && !_syslogAppenders.isEmpty()) {
+ for (SyslogAppender syslogAppender : _syslogAppenders) {
+ syslogAppender.setFacility(facility);
+ }
+ }
+ }
+
+ private String severityOfAlert(int alertType) {
+ if (isCritical(alertType)) {
+ return "CRITICAL";
+ } else {
+ return "WARN";
+ }
+ }
+
+ private boolean isCritical(int alertType) {
+ for (int type : criticalAlerts) {
+ if (type == alertType) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public String getFacility() {
+ return _facility;
+ }
+
+ public String getSyslogHosts() {
+ return _syslogHosts;
+ }
+
+ public void setSyslogHosts(String syslogHosts) {
+ this._syslogHosts = syslogHosts;
+ this.setSyslogAppenders();
+ }
+
+ public String getDelimiter() {
+ return _delimiter;
+ }
+
+ public void setDelimiter(String delimiter) {
+ this._delimiter = delimiter;
+ }
+
+ public String getPairDelimiter() {
+ return _pairDelimiter;
+ }
+
+ public void setPairDelimiter(String pairDelimiter) {
+ this._pairDelimiter = pairDelimiter;
+ }
+
+ public String getKeyValueDelimiter() {
+ return _keyValueDelimiter;
+ }
+
+ public void setKeyValueDelimiter(String keyValueDelimiter) {
+ this._keyValueDelimiter = keyValueDelimiter;
+ }
+}
\ No newline at end of file
diff --git a/plugins/alert-handlers/syslog-alerts/test/org/apache/cloudstack/syslog/AlertsSyslogAppenderTest.java b/plugins/alert-handlers/syslog-alerts/test/org/apache/cloudstack/syslog/AlertsSyslogAppenderTest.java
new file mode 100644
index 00000000000..68585ee6055
--- /dev/null
+++ b/plugins/alert-handlers/syslog-alerts/test/org/apache/cloudstack/syslog/AlertsSyslogAppenderTest.java
@@ -0,0 +1,61 @@
+// 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.cloudstack.syslog;
+
+import org.apache.log4j.PatternLayout;
+import org.junit.Before;
+import org.junit.Test;
+
+import javax.naming.ConfigurationException;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertTrue;
+
+public class AlertsSyslogAppenderTest {
+ AlertsSyslogAppender _appender = new AlertsSyslogAppender();
+
+ @Before
+ public void setUp() throws ConfigurationException {
+ _appender.setLayout(new PatternLayout("%-5p [%c{3}] (%t:%x) %m%n"));
+ _appender.setFacility("LOCAL6");
+ }
+
+ @Test
+ public void setSyslogAppendersTest() {
+ _appender.setSyslogHosts("10.1.1.1,10.1.1.2");
+ assertEquals(" error Syslog Appenders list size not as expected ", 2, _appender._syslogAppenders.size());
+ }
+
+ @Test
+ public void setSyslogAppendersNegativeTest() {
+ //setting invalid IP for Syslog Hosts
+ _appender.setSyslogHosts("10.1.1.");
+ assertTrue(" list was expected to be empty", _appender._syslogAppenders.isEmpty());
+ }
+
+ @Test
+ public void appendTest() {
+ String message = "alertType:: 14 // dataCenterId:: 0 // podId:: 0 // clusterId:: null // message:: Management" +
+ " server node 127.0.0.1 is up";
+ _appender.parseMessage(message);
+ String createdMessage = _appender.createSyslogMessage();
+ assertTrue(" message is not as expected ", createdMessage.contains("alertType:: managementNode" +
+ AlertsSyslogAppender.MESSAGE_DELIMITER_STRING + "message:: Management server node 127.0.0.1 is up"));
+ assertTrue("severity level not as expected ", createdMessage.contains("WARN"));
+ }
+}
\ No newline at end of file
diff --git a/plugins/pom.xml b/plugins/pom.xml
index 39d99073f09..d7e8debbf02 100755
--- a/plugins/pom.xml
+++ b/plugins/pom.xml
@@ -59,6 +59,7 @@
storage/volume/solidfire
storage/volume/default
alert-handlers/snmp-alerts
+ alert-handlers/syslog-alerts