diff --git a/README.md b/README.md index f7537bfb..4808ddfb 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Jenkins Logstash Plugin Travis: [![Build Status](https://travis-ci.org/jenkinsci/logstash-plugin.svg?branch=master)](https://travis-ci.org/jenkinsci/logstash-plugin) Jenkins: [![Build Status](https://ci.jenkins.io/job/Plugins/job/logstash-plugin/job/master/badge/icon)](https://ci.jenkins.io/job/Plugins/job/logstash-plugin/job/master/) -This plugin adds support for sending a job's console log to Logstash indexers such as ElasticSearch, RabbitMQ, or Redis. +This plugin adds support for sending a job's console log to Logstash indexers such as [Elastic Search](https://www.elastic.co/products/elasticsearch), [Logstash](https://www.elastic.co/de/products/logstash), [RabbitMQ](https://www.rabbitmq.com), [Redis](https://redis.io/) or to Syslog. * see [Jenkins wiki](https://wiki.jenkins-ci.org/display/JENKINS/Logstash+Plugin) for detailed feature descriptions * use [JIRA](https://issues.jenkins-ci.org) to report issues / feature requests @@ -23,6 +23,7 @@ Configure Currently supported methods of input/output: * ElasticSearch {REST API} +* Logstash TCP input * Redis {format => 'json_event'} * RabbitMQ {mechanism => PLAIN} * Syslog {format => cee/json ([RFC-5424](https://tools.ietf.org/html/rfc5424),[RFC-3164](https://tools.ietf.org/html/rfc3164)), protocol => UDP} @@ -73,6 +74,6 @@ Adding support for new indexers ------------------------------- * Implement the extension point `jenkins.plugins.logstash.configuration.LogstashIndexer` that will take your configuration. -Override the method `shouldRefreshInstance()` where you decide if a new dao instance must be created because the configuration has changed in the meantime. +* Implement `equals()` and `hashCode()`so the plugin can compare new configuration with existing configuration. * Create a `configure-advanced.jelly` for the UI part of your configuration. -* Create a new class that extends `jenkins.plugins.logstash.persistence.AbstractLogstashIndexerDao`. This class will do the actual work of pushing the logs to the indexer. +* Create a new class that extends `jenkins.plugins.logstash.persistence.AbstractLogstashIndexerDao` or `jenkins.plugins.logstash.persistence.HostBasedLogstashIndexer`. This class will do the actual work of pushing the logs to the indexer. diff --git a/src/main/java/jenkins/plugins/logstash/configuration/Logstash.java b/src/main/java/jenkins/plugins/logstash/configuration/Logstash.java new file mode 100644 index 00000000..150e74a5 --- /dev/null +++ b/src/main/java/jenkins/plugins/logstash/configuration/Logstash.java @@ -0,0 +1,39 @@ +package jenkins.plugins.logstash.configuration; + +import org.kohsuke.stapler.DataBoundConstructor; + +import hudson.Extension; +import jenkins.plugins.logstash.persistence.LogstashDao; + +public class Logstash extends HostBasedLogstashIndexer +{ + + @DataBoundConstructor + public Logstash() + { + } + + @Override + protected LogstashDao createIndexerInstance() + { + return new LogstashDao(getHost(), getPort()); + } + + @Extension + public static class Descriptor extends LogstashIndexerDescriptor + { + + @Override + public String getDisplayName() + { + return "Logstash TCP"; + } + + @Override + public int getDefaultPort() + { + return 9000; + } + + } +} diff --git a/src/main/java/jenkins/plugins/logstash/persistence/LogstashDao.java b/src/main/java/jenkins/plugins/logstash/persistence/LogstashDao.java new file mode 100644 index 00000000..09c20df4 --- /dev/null +++ b/src/main/java/jenkins/plugins/logstash/persistence/LogstashDao.java @@ -0,0 +1,26 @@ +package jenkins.plugins.logstash.persistence; + +import java.io.IOException; +import java.io.OutputStream; +import java.net.Socket; +import java.nio.charset.StandardCharsets; + +public class LogstashDao extends HostBasedLogstashIndexerDao { + + public LogstashDao(String logstashHostString, int logstashPortInt) { + super(logstashHostString, logstashPortInt); + } + + @Override + public void push(String data) throws IOException { + + try (Socket logstashClientSocket = new Socket(getHost(), getPort())) + { + OutputStream out = logstashClientSocket.getOutputStream(); + out.write(data.getBytes(StandardCharsets.UTF_8)); + out.write(10); + out.flush(); + out.close(); + } + } +} \ No newline at end of file diff --git a/src/test/java/jenkins/plugins/logstash/persistence/LogstashDaoTest.java b/src/test/java/jenkins/plugins/logstash/persistence/LogstashDaoTest.java new file mode 100644 index 00000000..31db8ff7 --- /dev/null +++ b/src/test/java/jenkins/plugins/logstash/persistence/LogstashDaoTest.java @@ -0,0 +1,65 @@ +package jenkins.plugins.logstash.persistence; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.*; + +import java.io.IOException; +import java.net.SocketException; +import java.nio.charset.Charset; + +import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang.exception.ExceptionUtils; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +import com.rabbitmq.client.AuthenticationFailureException; +import com.rabbitmq.client.Channel; +import com.rabbitmq.client.Connection; +import com.rabbitmq.client.ConnectionFactory; + +@RunWith(MockitoJUnitRunner.class) +public class LogstashDaoTest { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + LogstashDao dao; + + LogstashDao createDao(String host, int port) { + LogstashDao factory = new LogstashDao(host, port); + + factory.setCharset(Charset.defaultCharset()); + + return factory; + } + + @Test + public void constructorFailNullHost() throws Exception { + thrown.expect(IllegalArgumentException.class); + thrown.expectMessage("host name is required"); + createDao(null, 9000); + } + + @Test + public void constructorFailEmptyHost() throws Exception { + thrown.expect(IllegalArgumentException.class); + thrown.expectMessage("host name is required"); + createDao(" ", 9000); + } + + @Test + public void constructorSuccess() throws Exception { + // Unit under test + dao = createDao("localhost", 5672); + + // Verify results + assertEquals("Wrong host name", "localhost", dao.getHost()); + assertEquals("Wrong port", 5672, dao.getPort()); + } +}