├── .github ├── CODEOWNERS ├── dependabot.yml └── workflows │ ├── cd.yaml │ └── jenkins-security-scan.yml ├── .gitignore ├── .mvn ├── extensions.xml └── maven.config ├── .travis.yml ├── CHANGELOG.md ├── Jenkinsfile ├── LICENSE ├── README.md ├── pom.xml └── src ├── main ├── java │ └── jenkins │ │ └── plugins │ │ └── logstash │ │ ├── LogstashBuildWrapper.java │ │ ├── LogstashConfiguration.java │ │ ├── LogstashConsoleLogFilter.java │ │ ├── LogstashInstallation.java │ │ ├── LogstashItemListener.java │ │ ├── LogstashJobProperty.java │ │ ├── LogstashNotifier.java │ │ ├── LogstashOutputStream.java │ │ ├── LogstashSaveableListener.java │ │ ├── LogstashWriter.java │ │ ├── PluginImpl.java │ │ ├── configuration │ │ ├── ElasticSearch.java │ │ ├── HostBasedLogstashIndexer.java │ │ ├── Logstash.java │ │ ├── LogstashIndexer.java │ │ ├── RabbitMq.java │ │ ├── Redis.java │ │ └── Syslog.java │ │ ├── persistence │ │ ├── AbstractLogstashIndexerDao.java │ │ ├── BuildData.java │ │ ├── ElasticSearchDao.java │ │ ├── HostBasedLogstashIndexerDao.java │ │ ├── LogstashDao.java │ │ ├── LogstashIndexerDao.java │ │ ├── RabbitMqDao.java │ │ ├── RedisDao.java │ │ └── SyslogDao.java │ │ ├── pipeline │ │ ├── GlobalDecorator.java │ │ ├── LogstashSendStep.java │ │ └── LogstashStep.java │ │ └── utils │ │ ├── SSLHelper.java │ │ └── URIConverter.java ├── resources │ ├── index.jelly │ └── jenkins │ │ └── plugins │ │ └── logstash │ │ ├── LogstashConfiguration │ │ ├── config.jelly │ │ ├── help-enableGlobally.html │ │ ├── help-enabled.html │ │ ├── help-logstashIndexer.jelly │ │ └── help-milliSecondTimestamps.html │ │ ├── LogstashInstallation │ │ └── global.jelly │ │ ├── LogstashJobProperty │ │ └── config.jelly │ │ ├── LogstashNotifier │ │ ├── config.jelly │ │ └── help.html │ │ ├── Messages.properties │ │ ├── configuration │ │ ├── ElasticSearch │ │ │ ├── config.jelly │ │ │ ├── help-mimeType.html │ │ │ ├── help-password.html │ │ │ ├── help-uri.html │ │ │ ├── help-username.html │ │ │ └── help.jelly │ │ ├── HostBasedLogstashIndexer │ │ │ ├── config.jelly │ │ │ ├── help-host.html │ │ │ └── help-port.html │ │ ├── Logstash │ │ │ └── help.jelly │ │ ├── RabbitMq │ │ │ ├── configure-advanced.jelly │ │ │ ├── help-charset.html │ │ │ ├── help-password.html │ │ │ ├── help-queue.html │ │ │ ├── help-username.html │ │ │ ├── help-virtualHost.html │ │ │ └── help.jelly │ │ ├── Redis │ │ │ ├── configure-advanced.jelly │ │ │ ├── help-key.html │ │ │ ├── help-password.html │ │ │ └── help.jelly │ │ └── Syslog │ │ │ ├── configure-advanced.jelly │ │ │ ├── help-messageFormat.html │ │ │ ├── help-syslogProtocol.html │ │ │ └── help.jelly │ │ └── pipeline │ │ ├── LogstashSendStep │ │ ├── config.jelly │ │ └── help.html │ │ └── LogstashStep │ │ ├── config.jelly │ │ └── help.html └── webapp │ └── help │ ├── help-failBuild.html │ ├── help-maxLines.html │ └── help.html └── test ├── java └── jenkins │ └── plugins │ └── logstash │ ├── ConfigAsCodeTest.java │ ├── LogstashBuildWrapperConversionTest.java │ ├── LogstashConfigurationMigrationTest.java │ ├── LogstashConfigurationTest.java │ ├── LogstashConfigurationTestBase.java │ ├── LogstashConsoleLogFilterTest.java │ ├── LogstashIntegrationTest.java │ ├── LogstashNotifierTest.java │ ├── LogstashOutputStreamTest.java │ ├── LogstashWriterTest.java │ ├── PipelineTest.java │ ├── configuration │ ├── ElasticSearchTest.java │ ├── HostBasedLogstashIndexerTest.java │ ├── MemoryIndexer.java │ ├── RabbitMqTest.java │ ├── RedisTest.java │ └── SyslogTest.java │ └── persistence │ ├── AbstractLogstashIndexerDaoTest.java │ ├── BuildDataTest.java │ ├── ElasticSearchDaoTest.java │ ├── ElasticSearchSSLCertsTest.java │ ├── LogstashDaoTest.java │ ├── MemoryDao.java │ ├── RabbitMqDaoTest.java │ ├── RedisDaoTest.java │ ├── SyslogDaoTest.java │ └── SyslogDaoTestIT.java └── resources ├── buildWrapperConfig.xml ├── disabled.xml ├── elasticSearch.xml ├── elasticsearch-sslcerts ├── cert.pem ├── cert.pkcs12 ├── key.pem ├── keystore.ks └── truststore.ks ├── home └── jobs │ └── test │ └── config.xml ├── jcasc ├── elasticSearch.yaml ├── logstash.yaml ├── rabbitmq.yaml └── redis.yaml ├── logs └── syslog-test.log.sample ├── logstash └── pipeline │ └── syslog.yml ├── mockito-extensions └── org.mockito.plugins.MockMaker ├── rabbitmq.xml ├── rabbitmq_brokenCharset.xml ├── redis.xml └── syslog.xml /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @jenkinsci/logstash-plugin-developers 2 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 2 | 3 | version: 2 4 | updates: 5 | - package-ecosystem: "maven" 6 | directory: "/" 7 | schedule: 8 | interval: "weekly" 9 | - package-ecosystem: "github-actions" 10 | directory: "/" 11 | schedule: 12 | interval: "weekly" 13 | -------------------------------------------------------------------------------- /.github/workflows/cd.yaml: -------------------------------------------------------------------------------- 1 | # Note: additional setup is required, see https://www.jenkins.io/redirect/continuous-delivery-of-plugins 2 | 3 | name: cd 4 | on: 5 | workflow_dispatch: 6 | check_run: 7 | types: 8 | - completed 9 | 10 | jobs: 11 | maven-cd: 12 | uses: jenkins-infra/github-reusable-workflows/.github/workflows/maven-cd.yml@v1 13 | secrets: 14 | MAVEN_USERNAME: ${{ secrets.MAVEN_USERNAME }} 15 | MAVEN_TOKEN: ${{ secrets.MAVEN_TOKEN }} 16 | 17 | -------------------------------------------------------------------------------- /.github/workflows/jenkins-security-scan.yml: -------------------------------------------------------------------------------- 1 | # Jenkins Security Scan 2 | # For more information, see: https://www.jenkins.io/doc/developer/security/scan/ 3 | 4 | name: Jenkins Security Scan 5 | 6 | on: 7 | push: 8 | branches: 9 | - master 10 | pull_request: 11 | types: [opened, synchronize, reopened] 12 | workflow_dispatch: 13 | 14 | permissions: 15 | security-events: write 16 | contents: read 17 | actions: read 18 | 19 | jobs: 20 | security-scan: 21 | uses: jenkins-infra/jenkins-security-scan/.github/workflows/jenkins-security-scan.yaml@v2 22 | with: 23 | java-cache: 'maven' # Optionally enable use of a build dependency cache. Specify 'maven' or 'gradle' as appropriate. 24 | # java-version: 21 # Optionally specify what version of Java to set up for the build, or remove to use a recent default. 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | work 3 | 4 | # Vim junk files 5 | .*.swp 6 | *~ 7 | 8 | # IntelliJ project files 9 | .idea/ 10 | *.iml 11 | 12 | # Eclipse IDE project files 13 | .classpath 14 | .project 15 | .settings/ 16 | -------------------------------------------------------------------------------- /.mvn/extensions.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | io.jenkins.tools.incrementals 4 | git-changelist-maven-extension 5 | 1.7 6 | 7 | 8 | -------------------------------------------------------------------------------- /.mvn/maven.config: -------------------------------------------------------------------------------- 1 | -Pconsume-incrementals 2 | -Pmight-produce-incrementals 3 | -Dchangelist.format=%d.v%s 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | env: 2 | global: 3 | - LOGSTASH_DOCKER_IMAGE="logstash/logstash" 4 | - LOGSTASH_DOCKER_REGISTRY="docker.elastic.co" 5 | - LOGSTASH_DOCKER_VERSION="7.6.1" 6 | - LOGSTASH_DOCKER_ENV="XPACK_MONITORING_ENABLED=false" 7 | - LOGSTASH_PIPELINE_DIR="src/test/resources/logstash/pipeline" 8 | - LOGSTASH_LOGS_DIR="target/logs" 9 | - LOGSTASH_LOGFILE="syslog-test.log" 10 | - JENKINS_SERVER_COOKIE="true" 11 | 12 | sudo: required 13 | 14 | services: 15 | - docker 16 | 17 | before_install: 18 | - "mvn clean package --quiet" 19 | - "mkdir -p ${TRAVIS_BUILD_DIR}/${LOGSTASH_LOGS_DIR}" 20 | - "touch ${TRAVIS_BUILD_DIR}/${LOGSTASH_LOGS_DIR}/${LOGSTASH_LOGFILE}" 21 | - "sudo chown 1000 ${TRAVIS_BUILD_DIR}/${LOGSTASH_LOGS_DIR}/${LOGSTASH_LOGFILE}" 22 | - "sudo chmod g+w ${TRAVIS_BUILD_DIR}/${LOGSTASH_LOGS_DIR}/${LOGSTASH_LOGFILE}" 23 | - "docker pull ${LOGSTASH_DOCKER_REGISTRY}/${LOGSTASH_DOCKER_IMAGE}:${LOGSTASH_DOCKER_VERSION}" 24 | - docker run -d 25 | -v ${TRAVIS_BUILD_DIR}/${LOGSTASH_PIPELINE_DIR}:/usr/share/logstash/pipeline 26 | -v ${TRAVIS_BUILD_DIR}/${LOGSTASH_LOGS_DIR}:/tmp/logs 27 | -p 127.0.0.1:514:5555 28 | -p 127.0.0.1:514:5555/udp 29 | -e ${LOGSTASH_DOCKER_ENV} 30 | ${LOGSTASH_DOCKER_REGISTRY}/${LOGSTASH_DOCKER_IMAGE}:${LOGSTASH_DOCKER_VERSION} 31 | 32 | language: java 33 | dist: trusty 34 | jdk: 35 | - oraclejdk8 36 | - openjdk8 37 | branches: 38 | except: 39 | - /^test.*/i 40 | 41 | script: "mvn verify -DskipIntegrationTests=false" 42 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 2.3.1 2 | ----- 3 | * [JENKINS-52697] Configuration as Code (#87) 4 | * Add license info to pom (#86) 5 | * `logstashSend` step (#84) 6 | * Code Improvements (#82) 7 | * [JENKINS-52696] Incrementalify (#83) 8 | 9 | 2.3.0 10 | ----- 11 | * [JENKINS-52643] Allow programmatic configuration (#77) 12 | 13 | 2.2.0 14 | ----- 15 | * [JENKINS-51793] Failed to save when not configured (#76) 16 | * [JENKINS-52712] Make compatible with JEP-200 (#79) 17 | * [JENKINS-51029] Added support for custom ElasticSearch SSL certificate (#75) 18 | * Upgrade pom to use Jenkins 2.60.3 (#70) 19 | * Add defaultGoal to pom.xml 20 | 21 | 2.1.0 22 | ----- 23 | * [JENKINS-51793] explicit enable (#68) 24 | * [JENKINS-49114] evaluate result and TestResultAction late (#52) 25 | * url has to point to wiki (#65, #69) 26 | * add vhost support for rabbitmq (#64) 27 | * [JENKINS-42536] fix ansi console note (#62) 28 | * improve help (#61) 29 | * code cleanup (#60) 30 | * charset for rabbitMQ (#58, #63) 31 | * add dao to work with logstash tcp (#59) 32 | * Use mime type from config field in Elasticsearch indexer while posting HTTP request (#41) 33 | * millisecond timestamps (#57) 34 | 35 | 2.0.0 36 | ----- 37 | * [JENKINS-49960] use a Jobproperty instead of a BuildWrapper (#55) :warning: 38 | * [JENKINS-33635] option to enable logstash globally (#54) 39 | * remove dependency to maskpasswords (#48) 40 | * [JENKINS-49451] Fix logstash Notifier step (#53) 41 | * add a pipeline step with block (#51) 42 | * move configuration from ToolInstallation to GlobalConfiguration (#43) :warning: 43 | 44 | 1.4.0 45 | ----- 46 | * reduce visibility of fields (#46) 47 | * findbugs: fix default encoding issues (#45) 48 | * findbugs: fix issues in BuildData (#44) 49 | * properly get displayname of node (#38) 50 | * Full project name (#37) 51 | * JENKINS-41324 Append `build` envVars for build and post-build injected envVars (#33) 52 | 53 | 1.3.0 54 | ----- 55 | * Add test error details for failed test cases (#30) 56 | * Log exceptions in BuildData (#35) 57 | * Syslog persistence integration test with docker and travis. (#27) 58 | * Change logstash notifier to fit pipeline jobs (#28) 59 | * Update ElasticSearchDao to support LogStash 60 | * Updating the global.jelly to take work for Jenkins 2.0 and work for Jenkins with set URL and without 61 | 62 | 1.2.0 63 | ----- 64 | * Respect Mask Password plugin configuration 65 | * Update timestamps generated by build wrapper to reflect current time 66 | * Add @buildTimestamp to record start time 67 | 68 | 1.1.1 69 | ----- 70 | * Remove redundant timestamp from payload 71 | * Don't try to create RabbitMQ queue if it already exists 72 | * Remove unnecessary external synchronization between builds 73 | 74 | 1.1.0 75 | ----- 76 | * Add Syslog support 77 | * Requires Java runtime 7 or newer 78 | 79 | 1.0.4 80 | ----- 81 | * Add support for pushing directly to Elasticsearch 82 | 83 | 1.0.3 84 | ----- 85 | * Fix incompatibility with Jenkins core 1.577 and later 86 | 87 | 1.0.2 88 | ----- 89 | * Add test results to the payload 90 | * Removed redundant field "version" from payload 91 | * Fixed build duration (build duration was always 0) 92 | 93 | 1.0.1 94 | ----- 95 | * Return Jedis connection to pool immediately after use 96 | 97 | 1.0.0 98 | ----- 99 | * Use JedisPool to fix concurrency issue with multiple running jobs 100 | * Update Logstash event schema (https://logstash.jira.com/browse/LOGSTASH-675) 101 | * Add more build data to the payload (build parameters, Jenkins ID, etc.) 102 | * Move connection info into a global config shared between all jobs 103 | * Add a post-build action to send multiple log lines as a single event 104 | * Add support for RabbitMQ 105 | 106 | 0.7.4 107 | ----- 108 | 109 | * Flush downstream OutputStream when we flush. 110 | * If the connection to redis fails, stop using redis. 111 | * Log the activation of Logstash. 112 | 113 | 0.7.3 114 | ----- 115 | 116 | * Continue to output to the console log when redis is down. 117 | * Add support for more types of builds. 118 | 119 | 0.7.2 120 | ----- 121 | 122 | * No longer duplicating every line in metadata. 123 | * Fix bug when no password is used for redis. 124 | 125 | 0.7.1 126 | ----- 127 | 128 | * Remove data marked by ansi-conceal escape sequence. 129 | 130 | 0.7.0 131 | ----- 132 | * Initial working release. 133 | -------------------------------------------------------------------------------- /Jenkinsfile: -------------------------------------------------------------------------------- 1 | // Builds a module using https://github.com/jenkins-infra/pipeline-library 2 | buildPlugin(useContainerAgent: true, configurations: [ 3 | [platform: 'linux', jdk: 21], 4 | [platform: 'windows', jdk: 17], 5 | ]) 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright 2013 Hewlett-Packard Development Company, L.P. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Jenkins Logstash Plugin 2 | ======================= 3 | 4 | Travis: [![Build Status](https://travis-ci.org/jenkinsci/logstash-plugin.svg?branch=master)](https://travis-ci.org/jenkinsci/logstash-plugin) 5 | 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/) 6 | 7 | This plugin adds support for sending a job's console log to Logstash indexers such as [Elastic Search](https://www.elastic.co/products/elasticsearch), 8 | [Logstash](https://www.elastic.co/de/products/logstash), [RabbitMQ](https://www.rabbitmq.com), 9 | [Redis](https://redis.io/) or to Syslog. 10 | 11 | * use [Jira](https://issues.jenkins.io) to report issues / feature requests 12 | 13 | Install 14 | ======= 15 | 16 | * Search for 'Logstash' in your Jenkins plugin manager 17 | 18 | Configure 19 | ========= 20 | 21 | Supported methods of input/output: 22 | 23 | * ElasticSearch (REST API) 24 | * Logstash TCP input 25 | * Redis (format => 'json_event') 26 | * RabbitMQ (mechanism => PLAIN) 27 | * Syslog (format => cee/json ([RFC-5424](https://tools.ietf.org/html/rfc5424),[RFC-3164](https://tools.ietf.org/html/rfc3164)), protocol => UDP) 28 | 29 | Pipeline 30 | ========= 31 | 32 | Publisher 33 | --------- 34 | 35 | Logstash plugin can be used as a publisher in pipeline jobs to send the 36 | tail of the log as a single document. 37 | 38 | **Example for publisher in pipeline** 39 | 40 | ```Groovy 41 | node('master') { 42 | sh''' 43 | echo 'Hello, world!' 44 | ''' 45 | logstashSend failBuild: true, maxLines: 1000 46 | } 47 | ``` 48 | 49 | Note: Due to the way logging works in pipeline currently, the 50 | logstashSend step might not transfer the lines logged directly before 51 | the step is called. Adding a sleep of 1 second might help here. 52 | 53 | Note: In order to get the the result set in pipeline it must be [set 54 | before the logstashSend 55 | step](https://support.cloudbees.com/hc/en-us/articles/218554077-How-to-set-current-build-result-in-Pipeline-). 56 | 57 | Note: the `logstashSend` step requires a node to run. 58 | 59 | Step with Block 60 | --------------- 61 | 62 | It can be used as a wrapper step to send each log line separately. 63 | 64 | Once the result is set, it will appear in the data sent to the indexer. 65 | 66 | Note: when you combine with timestamps step, you should make the 67 | timestamps the outermost block. Otherwise you get the timestamps as 68 | part of the log lines, basically duplicating the timestamp information. 69 | 70 | **Example for pipeline step** 71 | 72 | ```Groovy 73 | timestamps { 74 | logstash { 75 | node('somelabel') { 76 | sh''' 77 | echo 'Hello, World!' 78 | ''' 79 | try { 80 | // do something that fails 81 | sh "exit 1" 82 | currentBuild.result = 'SUCCESS' 83 | } catch (Exception err) { 84 | currentBuild.result = 'FAILURE' 85 | } 86 | } 87 | } 88 | } 89 | ``` 90 | 91 | Note: Information on which agent the steps are executed is not available 92 | at the moment. 93 | 94 | Enable Globally 95 | ======= 96 | 97 | You can enable this plugin globally in the Jenkins system configuration page, 98 | or with the [configuration as code](https://plugins.jenkins.io/configuration-as-code/) plugin: 99 | 100 | ```yaml 101 | unclassified: 102 | logstashConfiguration: 103 | enableGlobally: true 104 | enabled: true 105 | logstashIndexer: 106 | logstash: 107 | host: "localhost" 108 | port: 9200 109 | ``` 110 | 111 | JobProperty 112 | ======= 113 | 114 | This component streams individual log lines to the indexer for post-processing, 115 | along with any build data that is available at the start (some information such as the build status is unavailable or incomplete). 116 | 117 | Post-Build Publisher 118 | ======= 119 | 120 | This component pushes the tail of the job's log to the indexer for post-processing, 121 | along with all build data at the time the post-build action had started (if any post-build actions are scheduled after this plugin they will not be recorded). 122 | 123 | JSON Payload Format 124 | ======= 125 | 126 | Example payload sent to the indexer (e.g. RabbitMQ) using the post-build action component. 127 | 128 | _Note 1: when the build wrapper is used, some information such as the build result will be missing or incomplete, 129 | and the "message" array will contain a single log line._ 130 | 131 | _Note 2: `data.testResults` will only be present if a publisher records your test results in the build, 132 | for example by using the [JUnit Plugin](https://plugins.jenkins.io/junit/)._ 133 | 134 |
135 | 136 | Click to expand the JSON payload 137 | 138 | ```json 139 | { 140 | "data":{ 141 | "id":"2014-10-13_19-51-29", 142 | "result":"SUCCESS", 143 | "projectName":"my_example_job", 144 | "fullProjectName":"folder/my_example_job", 145 | "displayName":"#1", 146 | "fullDisplayName":"My Example Job #1", 147 | "url":"job/my_example_job/1/", 148 | "buildHost":"Jenkins", 149 | "buildLabel":"", 150 | "buildNum":1, 151 | "buildDuration":0, 152 | "rootProjectName":"my_example_job", 153 | "rootFullProjectName":"folder/my_example_job", 154 | "rootProjectDisplayName":"#1", 155 | "rootBuildNum":1, 156 | "buildVariables":{ 157 | "PARAM1":"VALUE1", 158 | "PARAM2":"VALUE2" 159 | }, 160 | "testResults":{ 161 | "totalCount":45, 162 | "skipCount":0, 163 | "failCount":0, 164 | "failedTests":[] 165 | } 166 | }, 167 | "message":[ 168 | "Started by user anonymous", 169 | "Building in workspace /var/lib/jenkins/jobs/my_example_job/workspace", 170 | "Hello, World!" 171 | ], 172 | "source":"jenkins", 173 | "source_host":"http://localhost:8080/jenkins/", 174 | "@timestamp":"2014-10-13T19:51:29-0700", 175 | "@version":1 176 | } 177 | ``` 178 | 179 |
180 | 181 | Changelog 182 | ======= 183 | 184 | See [Changelog](./CHANGELOG.md). 185 | 186 | License 187 | ======= 188 | 189 | The Logstash Plugin is licensed under the MIT License. 190 | 191 | Contributing 192 | ============ 193 | 194 | * Fork the project on [Github](https://github.com/jenkinsci/logstash-plugin) 195 | * Make your feature addition or bug fix, write tests, commit. 196 | * Send me a pull request. Bonus points for topic branches. 197 | 198 | Adding support for new indexers 199 | ------------------------------- 200 | 201 | * Implement the extension point `jenkins.plugins.logstash.configuration.LogstashIndexer` that will take your configuration. 202 | * Implement `equals()` and `hashCode()`so the plugin can compare new configuration with existing configuration. 203 | * Create a `configure-advanced.jelly` for the UI part of your configuration. 204 | * Create a `help.jelly` with more details about indexer. 205 | * 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. 206 | -------------------------------------------------------------------------------- /src/main/java/jenkins/plugins/logstash/LogstashBuildWrapper.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright 2013 Hewlett-Packard Development Company, L.P. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | 25 | package jenkins.plugins.logstash; 26 | 27 | import java.io.IOException; 28 | 29 | import org.kohsuke.stapler.DataBoundConstructor; 30 | 31 | import hudson.Extension; 32 | import hudson.Launcher; 33 | import hudson.model.AbstractBuild; 34 | import hudson.model.AbstractProject; 35 | import hudson.model.BuildListener; 36 | import hudson.tasks.BuildWrapper; 37 | import hudson.tasks.BuildWrapperDescriptor; 38 | 39 | /** 40 | * 41 | * This BuildWrapper is not used anymore. 42 | * We just keep it to be able to convert projects that have the BuildWrapper configured at startup or when posting the xml via the rest api 43 | * to the JobProperty. 44 | * 45 | * @author K Jonathan Harker 46 | */ 47 | @Deprecated 48 | public class LogstashBuildWrapper extends BuildWrapper 49 | { 50 | 51 | /** 52 | * Create a new {@link LogstashBuildWrapper}. 53 | */ 54 | @DataBoundConstructor 55 | public LogstashBuildWrapper() 56 | {} 57 | 58 | /** 59 | * {@inheritDoc} 60 | */ 61 | @Override 62 | public Environment setUp(AbstractBuild build, Launcher launcher, BuildListener listener) 63 | throws IOException, InterruptedException 64 | { 65 | return new Environment() 66 | { 67 | }; 68 | } 69 | 70 | @Override 71 | public DescriptorImpl getDescriptor() 72 | { 73 | return (DescriptorImpl)super.getDescriptor(); 74 | } 75 | 76 | /** 77 | * Registers {@link LogstashBuildWrapper} as a {@link BuildWrapper}. 78 | */ 79 | @Extension 80 | public static class DescriptorImpl extends BuildWrapperDescriptor 81 | { 82 | 83 | public DescriptorImpl() 84 | { 85 | super(LogstashBuildWrapper.class); 86 | load(); 87 | } 88 | 89 | /** 90 | * {@inheritDoc} 91 | */ 92 | @Override 93 | public String getDisplayName() 94 | { 95 | return Messages.DisplayName(); 96 | } 97 | 98 | /** 99 | * {@inheritDoc} 100 | */ 101 | @Override 102 | public boolean isApplicable(AbstractProject item) 103 | { 104 | return false; 105 | } 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/main/java/jenkins/plugins/logstash/LogstashConsoleLogFilter.java: -------------------------------------------------------------------------------- 1 | package jenkins.plugins.logstash; 2 | 3 | import java.io.IOException; 4 | import java.io.OutputStream; 5 | import java.io.Serializable; 6 | import java.util.logging.Level; 7 | import java.util.logging.Logger; 8 | 9 | import hudson.Extension; 10 | import hudson.console.ConsoleLogFilter; 11 | import hudson.model.AbstractBuild; 12 | import hudson.model.AbstractProject; 13 | import hudson.model.Run; 14 | 15 | @Extension(ordinal = 1000) 16 | public class LogstashConsoleLogFilter extends ConsoleLogFilter implements Serializable 17 | { 18 | 19 | private static final Logger LOGGER = Logger.getLogger(LogstashConsoleLogFilter.class.getName()); 20 | 21 | public LogstashConsoleLogFilter() {} 22 | 23 | private static final long serialVersionUID = 1L; 24 | 25 | @Override 26 | public OutputStream decorateLogger(Run build, OutputStream logger) throws IOException, InterruptedException 27 | { 28 | LogstashConfiguration configuration = LogstashConfiguration.getInstance(); 29 | if (!configuration.isEnabled()) 30 | { 31 | LOGGER.log(Level.FINE, "Logstash is disabled. Logs will not be forwarded."); 32 | return logger; 33 | } 34 | 35 | if (build != null && build instanceof AbstractBuild) 36 | { 37 | if (isLogstashEnabled(build)) 38 | { 39 | LogstashWriter logstash = getLogStashWriter(build, logger); 40 | return new LogstashOutputStream(logger, logstash); 41 | } 42 | else 43 | { 44 | return logger; 45 | } 46 | } 47 | return logger; 48 | } 49 | 50 | LogstashWriter getLogStashWriter(Run build, OutputStream errorStream) 51 | { 52 | return new LogstashWriter(build, errorStream, null, build.getCharset()); 53 | } 54 | 55 | private boolean isLogstashEnabled(Run build) 56 | { 57 | LogstashConfiguration configuration = LogstashConfiguration.getInstance(); 58 | if (configuration.isEnableGlobally()) 59 | { 60 | return true; 61 | } 62 | 63 | if (build.getParent() instanceof AbstractProject) 64 | { 65 | AbstractProject project = (AbstractProject)build.getParent(); 66 | if (project.getProperty(LogstashJobProperty.class) != null) 67 | { 68 | return true; 69 | } 70 | } 71 | return false; 72 | } 73 | 74 | } 75 | -------------------------------------------------------------------------------- /src/main/java/jenkins/plugins/logstash/LogstashInstallation.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright 2014 Rusty Gerard 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | 25 | package jenkins.plugins.logstash; 26 | 27 | import hudson.Extension; 28 | import hudson.tools.ToolDescriptor; 29 | import hudson.tools.ToolProperty; 30 | import hudson.tools.ToolInstallation; 31 | 32 | import java.util.List; 33 | 34 | import jenkins.model.Jenkins; 35 | import jenkins.plugins.logstash.persistence.LogstashIndexerDao.IndexerType; 36 | import jenkins.plugins.logstash.persistence.LogstashIndexerDao.SyslogFormat; 37 | import jenkins.plugins.logstash.persistence.LogstashIndexerDao.SyslogProtocol; 38 | 39 | import org.kohsuke.stapler.DataBoundConstructor; 40 | 41 | /** 42 | * POJO for storing global configurations shared between components. 43 | * 44 | * @author Rusty Gerard 45 | * @since 1.0.0 46 | */ 47 | public class LogstashInstallation extends ToolInstallation { 48 | private static final long serialVersionUID = -5730780734005293851L; 49 | 50 | @DataBoundConstructor 51 | public LogstashInstallation(String name, String home, List> properties) { 52 | super(name, home, properties); 53 | } 54 | 55 | public static Descriptor getLogstashDescriptor() { 56 | return (Descriptor) Jenkins.getInstance().getDescriptor(LogstashInstallation.class); 57 | } 58 | 59 | @Extension 60 | public static final class Descriptor extends ToolDescriptor { 61 | private transient IndexerType type; 62 | private transient SyslogFormat syslogFormat; 63 | private transient SyslogProtocol syslogProtocol; 64 | private transient String host; 65 | private transient Integer port = -1; 66 | private transient String username; 67 | private transient String password; 68 | private transient String key; 69 | 70 | public Descriptor() { 71 | super(); 72 | load(); 73 | } 74 | 75 | 76 | @Override 77 | public String getDisplayName() { 78 | return Messages.DisplayName(); 79 | } 80 | 81 | 82 | public IndexerType getType() 83 | { 84 | return type; 85 | } 86 | 87 | 88 | public SyslogFormat getSyslogFormat() 89 | { 90 | return syslogFormat; 91 | } 92 | 93 | 94 | public SyslogProtocol getSyslogProtocol() 95 | { 96 | return syslogProtocol; 97 | } 98 | 99 | 100 | public String getHost() 101 | { 102 | return host; 103 | } 104 | 105 | 106 | public Integer getPort() 107 | { 108 | return port; 109 | } 110 | 111 | 112 | public String getUsername() 113 | { 114 | return username; 115 | } 116 | 117 | 118 | public String getPassword() 119 | { 120 | return password; 121 | } 122 | 123 | 124 | public String getKey() 125 | { 126 | return key; 127 | } 128 | 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /src/main/java/jenkins/plugins/logstash/LogstashItemListener.java: -------------------------------------------------------------------------------- 1 | package jenkins.plugins.logstash; 2 | 3 | import java.io.IOException; 4 | import java.util.logging.Level; 5 | import java.util.logging.Logger; 6 | 7 | import hudson.BulkChange; 8 | import hudson.Extension; 9 | import hudson.model.AbstractProject; 10 | import hudson.model.BuildableItemWithBuildWrappers; 11 | import hudson.model.Descriptor; 12 | import hudson.model.Item; 13 | import hudson.model.listeners.ItemListener; 14 | import hudson.tasks.BuildWrapper; 15 | import hudson.util.DescribableList; 16 | import jenkins.model.Jenkins; 17 | 18 | @Extension 19 | public class LogstashItemListener extends ItemListener 20 | { 21 | 22 | private static final Logger LOGGER = Logger.getLogger(LogstashItemListener.class.getName()); 23 | 24 | @Override 25 | public void onCreated(Item item) 26 | { 27 | if (item instanceof BuildableItemWithBuildWrappers) 28 | { 29 | convertBuildWrapperToJobProperty((BuildableItemWithBuildWrappers)item); 30 | } 31 | } 32 | 33 | @Override 34 | public void onLoaded() 35 | { 36 | for (BuildableItemWithBuildWrappers item : Jenkins.getInstance().getAllItems(BuildableItemWithBuildWrappers.class)) 37 | { 38 | convertBuildWrapperToJobProperty(item); 39 | } 40 | } 41 | 42 | static void convertBuildWrapperToJobProperty(BuildableItemWithBuildWrappers item) 43 | { 44 | DescribableList> wrappers = item.getBuildWrappersList(); 45 | @SuppressWarnings("deprecation") 46 | LogstashBuildWrapper logstashBuildWrapper = wrappers.get(LogstashBuildWrapper.class); 47 | if (logstashBuildWrapper != null && item instanceof AbstractProject) 48 | { 49 | AbstractProject project = (AbstractProject)item; 50 | BulkChange bc = new BulkChange(project); 51 | try 52 | { 53 | project.addProperty(new LogstashJobProperty()); 54 | wrappers.remove(logstashBuildWrapper); 55 | bc.commit(); 56 | } 57 | catch (IOException e) 58 | { 59 | LOGGER.log(Level.SEVERE, 60 | "Failed to convert LogstashBuildWrapper to LogstashJobProperty for project " + project.getFullName(), e); 61 | } 62 | finally 63 | { 64 | bc.abort(); 65 | } 66 | } 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /src/main/java/jenkins/plugins/logstash/LogstashJobProperty.java: -------------------------------------------------------------------------------- 1 | package jenkins.plugins.logstash; 2 | 3 | import org.kohsuke.stapler.DataBoundConstructor; 4 | import org.kohsuke.stapler.StaplerRequest; 5 | 6 | import hudson.Extension; 7 | import hudson.model.AbstractProject; 8 | import hudson.model.Job; 9 | import hudson.model.JobProperty; 10 | import hudson.model.JobPropertyDescriptor; 11 | import net.sf.json.JSONObject; 12 | 13 | /** 14 | * This JobProperty is a marker to decide if logs should be sent to an indexer. 15 | * 16 | */ 17 | public class LogstashJobProperty extends JobProperty> 18 | { 19 | 20 | @DataBoundConstructor 21 | public LogstashJobProperty() 22 | {} 23 | 24 | @Extension 25 | public static class DescriptorImpl extends JobPropertyDescriptor 26 | { 27 | @Override 28 | public JobProperty newInstance(StaplerRequest req, JSONObject formData) throws FormException 29 | { 30 | if (formData.containsKey("enable")) 31 | { 32 | return new LogstashJobProperty(); 33 | } 34 | return null; 35 | } 36 | 37 | @Override 38 | public String getDisplayName() 39 | { 40 | return Messages.DisplayName(); 41 | } 42 | 43 | @Override 44 | public boolean isApplicable(Class jobType) 45 | { 46 | return AbstractProject.class.isAssignableFrom(jobType); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/jenkins/plugins/logstash/LogstashNotifier.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright 2014 Rusty Gerard 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | 25 | package jenkins.plugins.logstash; 26 | 27 | import hudson.Extension; 28 | import hudson.Launcher; 29 | import hudson.FilePath; 30 | import hudson.model.BuildListener; 31 | import hudson.model.TaskListener; 32 | import hudson.model.AbstractBuild; 33 | import hudson.model.Run; 34 | import hudson.model.AbstractProject; 35 | import hudson.model.Result; 36 | import hudson.tasks.BuildStepDescriptor; 37 | import hudson.tasks.BuildStepMonitor; 38 | import hudson.tasks.Notifier; 39 | import hudson.tasks.Publisher; 40 | import hudson.util.FormValidation; 41 | import jenkins.tasks.SimpleBuildStep; 42 | import java.io.OutputStream; 43 | import java.io.PrintStream; 44 | import java.util.logging.Level; 45 | import java.util.logging.Logger; 46 | import java.io.IOException; 47 | 48 | import org.kohsuke.stapler.DataBoundConstructor; 49 | import org.kohsuke.stapler.QueryParameter; 50 | import org.jenkinsci.Symbol; 51 | 52 | /** 53 | * Post-build action to push build log to Logstash. 54 | * 55 | * @author Rusty Gerard 56 | * @since 1.0.0 57 | */ 58 | public class LogstashNotifier extends Notifier implements SimpleBuildStep { 59 | 60 | private static final Logger LOGGER = Logger.getLogger(LogstashNotifier.class.getName()); 61 | 62 | private final int maxLines; 63 | private final boolean failBuild; 64 | 65 | @DataBoundConstructor 66 | public LogstashNotifier(int maxLines, boolean failBuild) { 67 | this.maxLines = maxLines; 68 | this.failBuild = failBuild; 69 | } 70 | 71 | public int getMaxLines() 72 | { 73 | return maxLines; 74 | } 75 | 76 | public boolean isFailBuild() 77 | { 78 | return failBuild; 79 | } 80 | 81 | @Override 82 | public boolean perform(AbstractBuild build, Launcher launcher, BuildListener listener) { 83 | return perform(build, listener); 84 | } 85 | 86 | @Override 87 | public void perform(Run run, FilePath workspace, Launcher launcher, 88 | TaskListener listener) throws InterruptedException, IOException { 89 | if (!perform(run, listener)) { 90 | run.setResult(Result.FAILURE); 91 | } 92 | } 93 | 94 | private boolean perform(Run run, TaskListener listener) { 95 | LogstashConfiguration configuration = LogstashConfiguration.getInstance(); 96 | if (!configuration.isEnabled()) 97 | { 98 | LOGGER.log(Level.FINE, "Logstash is disabled. Logs will not be forwarded."); 99 | return true; 100 | } 101 | 102 | PrintStream errorPrintStream = listener.getLogger(); 103 | LogstashWriter logstash = getLogStashWriter(run, errorPrintStream, listener); 104 | logstash.writeBuildLog(maxLines); 105 | return !(failBuild && logstash.isConnectionBroken()); 106 | } 107 | 108 | // Method to encapsulate calls for unit-testing 109 | LogstashWriter getLogStashWriter(Run run, OutputStream errorStream, TaskListener listener) { 110 | return new LogstashWriter(run, errorStream, listener, run.getCharset()); 111 | } 112 | 113 | @Override 114 | public BuildStepMonitor getRequiredMonitorService() { 115 | // We don't call Run#getPreviousBuild() so no external synchronization between builds is required 116 | return BuildStepMonitor.NONE; 117 | } 118 | 119 | @Override 120 | public Descriptor getDescriptor() { 121 | return (Descriptor) super.getDescriptor(); 122 | } 123 | 124 | @Extension 125 | public static class Descriptor extends BuildStepDescriptor { 126 | 127 | @Override 128 | public boolean isApplicable(@SuppressWarnings("rawtypes") Class jobType) { 129 | return true; 130 | } 131 | 132 | @Override 133 | public String getDisplayName() { 134 | return Messages.DisplayName(); 135 | } 136 | 137 | /* 138 | * Form validation methods 139 | */ 140 | public FormValidation doCheckMaxLines(@QueryParameter("value") String value) { 141 | try { 142 | Integer.parseInt(value); 143 | } catch (NumberFormatException e) { 144 | return FormValidation.error(Messages.ValueIsInt()); 145 | } 146 | 147 | return FormValidation.ok(); 148 | } 149 | 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /src/main/java/jenkins/plugins/logstash/LogstashOutputStream.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright 2014 K Jonathan Harker & Rusty Gerard 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | 25 | package jenkins.plugins.logstash; 26 | 27 | import hudson.console.ConsoleNote; 28 | import hudson.console.LineTransformationOutputStream; 29 | 30 | import java.io.IOException; 31 | import java.io.OutputStream; 32 | 33 | /** 34 | * Output stream that writes each line to the provided delegate output stream 35 | * and also sends it to an indexer for logstash to consume. 36 | * 37 | * @author K Jonathan Harker 38 | * @author Rusty Gerard 39 | */ 40 | public class LogstashOutputStream extends LineTransformationOutputStream { 41 | private final OutputStream delegate; 42 | private final LogstashWriter logstash; 43 | 44 | public LogstashOutputStream(OutputStream delegate, LogstashWriter logstash) { 45 | super(); 46 | this.delegate = delegate; 47 | this.logstash = logstash; 48 | } 49 | 50 | // for testing purposes 51 | LogstashWriter getLogstashWriter() 52 | { 53 | return logstash; 54 | } 55 | 56 | @Override 57 | protected void eol(byte[] b, int len) throws IOException { 58 | delegate.write(b, 0, len); 59 | this.flush(); 60 | 61 | if(!logstash.isConnectionBroken()) { 62 | String line = new String(b, 0, len, logstash.getCharset()); 63 | line = ConsoleNote.removeNotes(line).trim(); 64 | logstash.write(line); 65 | } 66 | } 67 | 68 | /** 69 | * {@inheritDoc} 70 | */ 71 | @Override 72 | public void flush() throws IOException { 73 | delegate.flush(); 74 | super.flush(); 75 | } 76 | 77 | /** 78 | * {@inheritDoc} 79 | */ 80 | @Override 81 | public void close() throws IOException { 82 | delegate.close(); 83 | super.close(); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/main/java/jenkins/plugins/logstash/LogstashSaveableListener.java: -------------------------------------------------------------------------------- 1 | package jenkins.plugins.logstash; 2 | 3 | import hudson.Extension; 4 | import hudson.XmlFile; 5 | import hudson.model.BuildableItemWithBuildWrappers; 6 | import hudson.model.Saveable; 7 | import hudson.model.listeners.SaveableListener; 8 | 9 | @Extension 10 | public class LogstashSaveableListener extends SaveableListener 11 | { 12 | 13 | @Override 14 | public void onChange(Saveable o, XmlFile file) 15 | { 16 | if (o instanceof BuildableItemWithBuildWrappers) 17 | { 18 | LogstashItemListener.convertBuildWrapperToJobProperty((BuildableItemWithBuildWrappers)o); 19 | } 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/jenkins/plugins/logstash/LogstashWriter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright 2014 Rusty Gerard and Liam Newman 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | 25 | package jenkins.plugins.logstash; 26 | 27 | 28 | import hudson.model.AbstractBuild; 29 | import hudson.model.TaskListener; 30 | import hudson.model.Run; 31 | import jenkins.model.Jenkins; 32 | import jenkins.plugins.logstash.persistence.BuildData; 33 | import jenkins.plugins.logstash.persistence.LogstashIndexerDao; 34 | import net.sf.json.JSONObject; 35 | import org.apache.commons.lang.StringUtils; 36 | import org.apache.commons.lang.exception.ExceptionUtils; 37 | 38 | import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 39 | 40 | import java.io.IOException; 41 | import java.io.OutputStream; 42 | import java.io.Serializable; 43 | import java.nio.charset.Charset; 44 | import java.util.Arrays; 45 | import java.util.Date; 46 | import java.util.List; 47 | 48 | /** 49 | * A writer that wraps all Logstash DAOs. Handles error reporting and per build connection state. 50 | * Each call to write (one line or multiple lines) sends a Logstash payload to the DAO. 51 | * If any write fails, writer will not attempt to send any further messages to logstash during this build. 52 | * 53 | * @author Rusty Gerard 54 | * @author Liam Newman 55 | * @since 1.0.5 56 | */ 57 | @SuppressFBWarnings(value="SE_NO_SERIALVERSIONID") 58 | public class LogstashWriter implements Serializable { 59 | 60 | private final OutputStream errorStream; 61 | private final transient Run build; 62 | private final TaskListener listener; 63 | private final BuildData buildData; 64 | private final String jenkinsUrl; 65 | private final LogstashIndexerDao dao; 66 | private boolean connectionBroken; 67 | private final String charset; 68 | private final String stageName; 69 | private final String agentName; 70 | 71 | public LogstashWriter(Run run, OutputStream error, TaskListener listener, Charset charset) { 72 | this(run, error, listener, charset, null, null); 73 | } 74 | 75 | public LogstashWriter(Run run, OutputStream error, TaskListener listener, Charset charset, String stageName, String agentName) { 76 | this.errorStream = error != null ? error : System.err; 77 | this.stageName = stageName; 78 | this.agentName = agentName; 79 | this.build = run; 80 | this.listener = listener; 81 | this.charset = charset.toString(); 82 | this.dao = this.getDaoOrNull(); 83 | if (this.dao == null) { 84 | this.jenkinsUrl = ""; 85 | this.buildData = null; 86 | } else { 87 | this.jenkinsUrl = getJenkinsUrl(); 88 | this.buildData = getBuildData(); 89 | } 90 | } 91 | 92 | /** 93 | * Gets the charset that Jenkins is using during this build. 94 | * 95 | * @return the charset 96 | */ 97 | public String getCharset() 98 | { 99 | return charset; 100 | } 101 | 102 | // for testing only 103 | LogstashIndexerDao getDao() 104 | { 105 | return dao; 106 | } 107 | 108 | /** 109 | * Sends a logstash payload for a single line to the indexer. 110 | * Call will be ignored if the line is empty or if the connection to the indexer is broken. 111 | * If write fails, errors will logged to errorStream and connectionBroken will be set to true. 112 | * 113 | * @param line 114 | * Message, not null 115 | */ 116 | public void write(String line) { 117 | if (!isConnectionBroken() && StringUtils.isNotEmpty(line)) { 118 | this.write(Arrays.asList(line)); 119 | } 120 | } 121 | 122 | /** 123 | * Sends a logstash payload containing log lines from the current build. 124 | * Call will be ignored if the connection to the indexer is broken. 125 | * If write fails, errors will logged to errorStream and connectionBroken will be set to true. 126 | * 127 | * @param maxLines 128 | * Maximum number of lines to be written. Negative numbers mean "all lines". 129 | */ 130 | public void writeBuildLog(int maxLines) { 131 | if (!isConnectionBroken()) { 132 | // FIXME: build.getLog() won't have the last few lines like "Finished: SUCCESS" because this hasn't returned yet... 133 | List logLines; 134 | try { 135 | if (maxLines < 0) { 136 | logLines = build.getLog(Integer.MAX_VALUE); 137 | } else { 138 | logLines = build.getLog(maxLines); 139 | } 140 | } catch (IOException e) { 141 | String msg = "[logstash-plugin]: Unable to serialize log data.\n" + 142 | ExceptionUtils.getStackTrace(e); 143 | logErrorMessage(msg); 144 | 145 | // Continue with error info as logstash payload 146 | logLines = Arrays.asList(msg.split("\n")); 147 | } 148 | 149 | write(logLines); 150 | } 151 | } 152 | 153 | /** 154 | * @return True if errors have occurred during initialization or write. 155 | */ 156 | public boolean isConnectionBroken() { 157 | return connectionBroken || build == null || dao == null || buildData == null; 158 | } 159 | 160 | // Method to encapsulate calls for unit-testing 161 | LogstashIndexerDao getIndexerDao() { 162 | return LogstashConfiguration.getInstance().getIndexerInstance(); 163 | } 164 | 165 | BuildData getBuildData() { 166 | if (build instanceof AbstractBuild) { 167 | return new BuildData((AbstractBuild) build, new Date(), listener); 168 | } else { 169 | return new BuildData(build, new Date(), listener, stageName, agentName); 170 | } 171 | } 172 | 173 | String getJenkinsUrl() { 174 | return Jenkins.get().getRootUrl(); 175 | } 176 | 177 | /** 178 | * Write a list of lines to the indexer as one Logstash payload. 179 | */ 180 | private void write(List lines) { 181 | buildData.updateResult(); 182 | JSONObject payload = dao.buildPayload(buildData, jenkinsUrl, lines); 183 | try { 184 | dao.push(payload.toString()); 185 | } catch (IOException e) { 186 | String msg = "[logstash-plugin]: Failed to send log data: " + dao.getDescription() + ".\n" + 187 | "[logstash-plugin]: No Further logs will be sent to " + dao.getDescription() + ".\n" + 188 | ExceptionUtils.getStackTrace(e); 189 | logErrorMessage(msg); 190 | } 191 | } 192 | 193 | /** 194 | * Construct a valid indexerDao or return null. 195 | * Writes errors to errorStream if dao constructor fails. 196 | * 197 | * @return valid {@link LogstashIndexerDao} or return null. 198 | */ 199 | private LogstashIndexerDao getDaoOrNull() { 200 | try { 201 | LogstashIndexerDao dao = getIndexerDao(); 202 | if (dao == null) 203 | { 204 | logErrorMessage("[logstash-plugin]: Unable to instantiate LogstashIndexerDao with current configuration.\n"); 205 | } 206 | return dao; 207 | } catch (IllegalArgumentException e) { 208 | String msg = ExceptionUtils.getMessage(e) + "\n" + 209 | "[logstash-plugin]: Unable to instantiate LogstashIndexerDao with current configuration.\n"; 210 | 211 | logErrorMessage(msg); 212 | } 213 | return null; 214 | } 215 | 216 | /** 217 | * Write error message to errorStream and set connectionBroken to true. 218 | */ 219 | private void logErrorMessage(String msg) { 220 | try { 221 | connectionBroken = true; 222 | if (errorStream != null) { 223 | errorStream.write(msg.getBytes(charset)); 224 | errorStream.flush(); 225 | } 226 | } catch (IOException ex) { 227 | // This should never happen, but if it does we just have to let it go. 228 | ex.printStackTrace(); 229 | } 230 | } 231 | 232 | } 233 | -------------------------------------------------------------------------------- /src/main/java/jenkins/plugins/logstash/PluginImpl.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright 2013 Hewlett-Packard Development Company, L.P. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package jenkins.plugins.logstash; 25 | 26 | import hudson.DescriptorExtensionList; 27 | import hudson.Plugin; 28 | import hudson.model.Descriptor; 29 | import jenkins.plugins.logstash.configuration.LogstashIndexer; 30 | import jenkins.plugins.logstash.utils.URIConverter; 31 | 32 | import java.net.URI; 33 | import java.util.logging.Logger; 34 | 35 | import org.kohsuke.stapler.Stapler; 36 | 37 | public class PluginImpl extends Plugin { 38 | private final static Logger LOG = Logger.getLogger(PluginImpl.class.getName()); 39 | 40 | /* 41 | * TODO: do we really need this method? 42 | * All it does is printing a message at startup of Jenkins. 43 | */ 44 | @Override 45 | public void start() throws Exception { 46 | LOG.info("Logstash: a logstash agent to send jenkins logs to a logstash indexer."); 47 | 48 | // Register a converter for URI, as nither Stapler nor apache commons beanutils have it 49 | Stapler.CONVERT_UTILS.register(new URIConverter(), URI.class); 50 | } 51 | 52 | public DescriptorExtensionList, Descriptor>> getAllIndexers() 53 | { 54 | return LogstashIndexer.all(); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/jenkins/plugins/logstash/configuration/HostBasedLogstashIndexer.java: -------------------------------------------------------------------------------- 1 | package jenkins.plugins.logstash.configuration; 2 | 3 | import org.kohsuke.stapler.DataBoundSetter; 4 | 5 | import jenkins.plugins.logstash.persistence.AbstractLogstashIndexerDao; 6 | 7 | public abstract class HostBasedLogstashIndexer extends LogstashIndexer 8 | { 9 | private String host; 10 | private int port; 11 | 12 | /** 13 | * Returns the host for connecting to the indexer. 14 | * 15 | * @return Host of the indexer 16 | */ 17 | public String getHost() 18 | { 19 | return host; 20 | } 21 | 22 | /** 23 | * Sets the host for connecting to the indexer. 24 | * 25 | * @param host 26 | * host to connect to. 27 | */ 28 | @DataBoundSetter 29 | public void setHost(String host) 30 | { 31 | this.host = host; 32 | } 33 | 34 | /** 35 | * Returns the port for connecting to the indexer. 36 | * 37 | * @return Port of the indexer 38 | */ 39 | public int getPort() 40 | { 41 | return port; 42 | } 43 | 44 | /** 45 | * Sets the port used for connecting to the indexer 46 | * 47 | * @param port 48 | * The port of the indexer 49 | */ 50 | @DataBoundSetter 51 | public void setPort(int port) 52 | { 53 | this.port = port; 54 | } 55 | 56 | @Override 57 | public int hashCode() { 58 | final int prime = 31; 59 | int result = 1; 60 | result = prime * result + ((host == null) ? 0 : host.hashCode()); 61 | result = prime * result + port; 62 | return result; 63 | } 64 | 65 | @Override 66 | public boolean equals(Object obj) { 67 | if (this == obj) 68 | return true; 69 | if (obj == null) 70 | return false; 71 | if (getClass() != obj.getClass()) 72 | return false; 73 | HostBasedLogstashIndexer other = (HostBasedLogstashIndexer) obj; 74 | if (host == null) { 75 | if (other.host != null) 76 | return false; 77 | } else if (!host.equals(other.host)) 78 | return false; 79 | if (port != other.port) 80 | return false; 81 | return true; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/main/java/jenkins/plugins/logstash/configuration/Logstash.java: -------------------------------------------------------------------------------- 1 | package jenkins.plugins.logstash.configuration; 2 | 3 | import org.jenkinsci.Symbol; 4 | import org.kohsuke.stapler.DataBoundConstructor; 5 | 6 | import hudson.Extension; 7 | import jenkins.plugins.logstash.persistence.LogstashDao; 8 | 9 | public class Logstash extends HostBasedLogstashIndexer 10 | { 11 | 12 | @DataBoundConstructor 13 | public Logstash() 14 | { 15 | } 16 | 17 | @Override 18 | protected LogstashDao createIndexerInstance() 19 | { 20 | return new LogstashDao(getHost(), getPort()); 21 | } 22 | 23 | @Extension 24 | @Symbol("logstash") 25 | public static class Descriptor extends LogstashIndexerDescriptor 26 | { 27 | 28 | @Override 29 | public String getDisplayName() 30 | { 31 | return "Logstash TCP"; 32 | } 33 | 34 | @Override 35 | public int getDefaultPort() 36 | { 37 | return 9000; 38 | } 39 | 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/jenkins/plugins/logstash/configuration/LogstashIndexer.java: -------------------------------------------------------------------------------- 1 | package jenkins.plugins.logstash.configuration; 2 | 3 | import edu.umd.cs.findbugs.annotations.NonNull; 4 | 5 | import org.apache.commons.lang.StringUtils; 6 | import org.kohsuke.stapler.QueryParameter; 7 | import org.kohsuke.stapler.StaplerRequest; 8 | 9 | import hudson.DescriptorExtensionList; 10 | import hudson.ExtensionPoint; 11 | import hudson.model.AbstractDescribableImpl; 12 | import hudson.model.Descriptor; 13 | import hudson.model.ReconfigurableDescribable; 14 | import hudson.model.Descriptor.FormException; 15 | import hudson.util.FormValidation; 16 | import jenkins.model.Jenkins; 17 | import jenkins.plugins.logstash.Messages; 18 | import jenkins.plugins.logstash.persistence.AbstractLogstashIndexerDao; 19 | import net.sf.json.JSONObject; 20 | 21 | /** 22 | * Extension point for logstash indexers. 23 | * This extension point provides the configuration for the indexer. You also have to implement the actual 24 | * indexer in a separate class extending {@link AbstractLogstashIndexerDao}. 25 | * 26 | * @param The class implementing the push to the indexer 27 | */ 28 | public abstract class LogstashIndexer 29 | extends AbstractDescribableImpl> 30 | implements ExtensionPoint, ReconfigurableDescribable> 31 | { 32 | protected transient T instance; 33 | 34 | /** 35 | * Gets the instance of the actual {@link AbstractLogstashIndexerDao} that is represented by this 36 | * configuration. 37 | * 38 | * @return {@link AbstractLogstashIndexerDao} instance 39 | */ 40 | @NonNull 41 | public synchronized T getInstance() 42 | { 43 | if (instance == null) 44 | { 45 | instance = createIndexerInstance(); 46 | } 47 | return instance; 48 | } 49 | 50 | /** 51 | * Purpose of this method is to validate the inputs (if required) and if found 52 | * erroneous throw an exception so that it will be bubbled up to the UI. 53 | * 54 | * @throws Exception on erroneous input 55 | */ 56 | public void validate() throws Exception { 57 | } 58 | 59 | 60 | 61 | /** 62 | * Creates a new {@link AbstractLogstashIndexerDao} instance corresponding to this configuration. 63 | * 64 | * @return {@link AbstractLogstashIndexerDao} instance 65 | */ 66 | protected abstract T createIndexerInstance(); 67 | 68 | 69 | @SuppressWarnings("unchecked") 70 | public static DescriptorExtensionList, Descriptor>> all() 71 | { 72 | return (DescriptorExtensionList, Descriptor>>) Jenkins.getInstance().getDescriptorList(LogstashIndexer.class); 73 | } 74 | 75 | public static abstract class LogstashIndexerDescriptor extends Descriptor> 76 | { 77 | /* 78 | * Form validation methods 79 | */ 80 | public FormValidation doCheckPort(@QueryParameter("value") String value) 81 | { 82 | try 83 | { 84 | Integer.parseInt(value); 85 | } 86 | catch (NumberFormatException e) 87 | { 88 | return FormValidation.error(Messages.ValueIsInt()); 89 | } 90 | 91 | return FormValidation.ok(); 92 | } 93 | 94 | public FormValidation doCheckHost(@QueryParameter("value") String value) 95 | { 96 | if (StringUtils.isBlank(value)) 97 | { 98 | return FormValidation.warning(Messages.PleaseProvideHost()); 99 | } 100 | 101 | return FormValidation.ok(); 102 | } 103 | 104 | public abstract int getDefaultPort(); 105 | } 106 | 107 | /** 108 | * {@inheritDoc} 109 | */ 110 | @Override 111 | public LogstashIndexer reconfigure(StaplerRequest req, JSONObject form) throws FormException 112 | { 113 | req.bindJSON(this, form); 114 | return this; 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/main/java/jenkins/plugins/logstash/configuration/RabbitMq.java: -------------------------------------------------------------------------------- 1 | package jenkins.plugins.logstash.configuration; 2 | 3 | import java.nio.charset.Charset; 4 | 5 | import org.apache.commons.lang.StringUtils; 6 | import org.jenkinsci.Symbol; 7 | import org.kohsuke.accmod.Restricted; 8 | import org.kohsuke.accmod.restrictions.NoExternalUse; 9 | import org.kohsuke.stapler.DataBoundConstructor; 10 | import org.kohsuke.stapler.DataBoundSetter; 11 | import org.kohsuke.stapler.QueryParameter; 12 | 13 | import hudson.Extension; 14 | import hudson.util.FormValidation; 15 | import hudson.util.Secret; 16 | import jenkins.plugins.logstash.Messages; 17 | import jenkins.plugins.logstash.persistence.RabbitMqDao; 18 | 19 | public class RabbitMq extends HostBasedLogstashIndexer 20 | { 21 | 22 | private String queue; 23 | private String username; 24 | private Secret password; 25 | private String charset; 26 | private String virtualHost; 27 | 28 | @DataBoundConstructor 29 | public RabbitMq(String charset) 30 | { 31 | if (charset == null || charset.isEmpty()) 32 | { 33 | this.charset = Charset.defaultCharset().toString(); 34 | } 35 | else 36 | { 37 | this.charset = Charset.forName(charset).toString(); 38 | } 39 | } 40 | 41 | protected Object readResolve() 42 | { 43 | if (charset == null) 44 | { 45 | charset = Charset.defaultCharset().toString(); 46 | } 47 | if (virtualHost == null) 48 | { 49 | virtualHost = "/"; 50 | } 51 | return this; 52 | } 53 | 54 | public String getCharset() 55 | { 56 | return charset; 57 | } 58 | 59 | // package visibility for testing only 60 | @Restricted(NoExternalUse.class) 61 | Charset getEffectiveCharset() 62 | { 63 | try 64 | { 65 | return Charset.forName(charset); 66 | 67 | } 68 | catch (IllegalArgumentException e) 69 | { 70 | return Charset.defaultCharset(); 71 | } 72 | } 73 | 74 | public String getVirtualHost() 75 | { 76 | return virtualHost; 77 | } 78 | 79 | @DataBoundSetter 80 | public void setVirtualHost(String virtualHost) 81 | { 82 | this.virtualHost = virtualHost; 83 | } 84 | 85 | public String getQueue() 86 | { 87 | return queue; 88 | } 89 | 90 | @DataBoundSetter 91 | public void setQueue(String queue) 92 | { 93 | this.queue = queue; 94 | } 95 | 96 | public String getUsername() 97 | { 98 | return username; 99 | } 100 | 101 | @DataBoundSetter 102 | public void setUsername(String username) 103 | { 104 | this.username = username; 105 | } 106 | 107 | public Secret getPassword() 108 | { 109 | return password; 110 | } 111 | 112 | @DataBoundSetter 113 | public void setPassword(Secret password) 114 | { 115 | this.password = password; 116 | } 117 | 118 | @Override 119 | public boolean equals(Object obj) 120 | { 121 | if (this == obj) 122 | return true; 123 | if (!super.equals(obj)) 124 | return false; 125 | if (getClass() != obj.getClass()) 126 | return false; 127 | RabbitMq other = (RabbitMq) obj; 128 | if (!Secret.toString(password).equals(Secret.toString(other.getPassword()))) 129 | { 130 | return false; 131 | } 132 | if (!StringUtils.equals(queue, other.queue)) 133 | { 134 | return false; 135 | } 136 | if (!StringUtils.equals(username, other.username)) 137 | { 138 | return false; 139 | } 140 | if (!StringUtils.equals(virtualHost, other.virtualHost)) 141 | { 142 | return false; 143 | } 144 | if (charset == null) 145 | { 146 | if (other.charset != null) 147 | { 148 | return false; 149 | } 150 | } else if (!charset.equals(other.charset)) 151 | { 152 | return false; 153 | } 154 | 155 | return true; 156 | } 157 | 158 | @Override 159 | public int hashCode() 160 | { 161 | final int prime = 31; 162 | int result = super.hashCode(); 163 | result = prime * result + ((queue == null) ? 0 : queue.hashCode()); 164 | result = prime * result + ((username == null) ? 0 : username.hashCode()); 165 | result = prime * result + ((charset == null) ? 0 : charset.hashCode()); 166 | result = prime * result + ((virtualHost == null) ? 0 : virtualHost.hashCode()); 167 | result = prime * result + Secret.toString(password).hashCode(); 168 | return result; 169 | } 170 | 171 | @Override 172 | public RabbitMqDao createIndexerInstance() 173 | { 174 | return new RabbitMqDao(getHost(), getPort(), queue, username, Secret.toString(password), getEffectiveCharset(), getVirtualHost()); 175 | } 176 | 177 | @Extension 178 | @Symbol("rabbitMq") 179 | public static class RabbitMqDescriptor extends LogstashIndexerDescriptor 180 | { 181 | @Override 182 | public String getDisplayName() 183 | { 184 | return "RabbitMQ"; 185 | } 186 | 187 | @Override 188 | public int getDefaultPort() 189 | { 190 | return 5672; 191 | } 192 | 193 | public FormValidation doCheckQueue(@QueryParameter("value") String value) 194 | { 195 | if (StringUtils.isBlank(value)) 196 | { 197 | return FormValidation.error(Messages.ValueIsRequired()); 198 | } 199 | 200 | return FormValidation.ok(); 201 | } 202 | 203 | } 204 | } 205 | -------------------------------------------------------------------------------- /src/main/java/jenkins/plugins/logstash/configuration/Redis.java: -------------------------------------------------------------------------------- 1 | package jenkins.plugins.logstash.configuration; 2 | 3 | import org.apache.commons.lang.StringUtils; 4 | import org.jenkinsci.Symbol; 5 | import org.kohsuke.stapler.DataBoundConstructor; 6 | import org.kohsuke.stapler.DataBoundSetter; 7 | import org.kohsuke.stapler.QueryParameter; 8 | 9 | import hudson.Extension; 10 | import hudson.util.FormValidation; 11 | import hudson.util.Secret; 12 | import jenkins.plugins.logstash.Messages; 13 | import jenkins.plugins.logstash.persistence.RedisDao; 14 | 15 | public class Redis extends HostBasedLogstashIndexer 16 | { 17 | 18 | protected String key; 19 | protected Secret password; 20 | 21 | @DataBoundConstructor 22 | public Redis() 23 | { 24 | } 25 | 26 | public String getKey() 27 | { 28 | return key; 29 | } 30 | 31 | @DataBoundSetter 32 | public void setKey(String key) 33 | { 34 | this.key = key; 35 | } 36 | 37 | public Secret getPassword() 38 | { 39 | return password; 40 | } 41 | 42 | @DataBoundSetter 43 | public void setPassword(Secret password) 44 | { 45 | this.password = password; 46 | } 47 | 48 | @Override 49 | public boolean equals(Object obj) 50 | { 51 | if (this == obj) 52 | return true; 53 | if (!super.equals(obj)) 54 | return false; 55 | if (getClass() != obj.getClass()) 56 | return false; 57 | Redis other = (Redis) obj; 58 | if (!Secret.toString(password).equals(Secret.toString(other.getPassword()))) 59 | { 60 | return false; 61 | } 62 | if (key == null) 63 | { 64 | if (other.key != null) 65 | return false; 66 | } 67 | else if (!key.equals(other.key)) 68 | { 69 | return false; 70 | } 71 | return true; 72 | } 73 | 74 | @Override 75 | public int hashCode() 76 | { 77 | final int prime = 31; 78 | int result = super.hashCode(); 79 | result = prime * result + ((key == null) ? 0 : key.hashCode()); 80 | result = prime * result + Secret.toString(password).hashCode(); 81 | return result; 82 | } 83 | 84 | 85 | @Override 86 | public RedisDao createIndexerInstance() 87 | { 88 | return new RedisDao(getHost(), getPort(), key, Secret.toString(password)); 89 | } 90 | 91 | @Extension 92 | @Symbol("redis") 93 | public static class RedisDescriptor extends LogstashIndexerDescriptor 94 | { 95 | 96 | @Override 97 | public String getDisplayName() 98 | { 99 | return "Redis"; 100 | } 101 | 102 | @Override 103 | public int getDefaultPort() 104 | { 105 | return 6379; 106 | } 107 | 108 | public FormValidation doCheckKey(@QueryParameter("value") String value) 109 | { 110 | if (StringUtils.isBlank(value)) 111 | { 112 | return FormValidation.error(Messages.ValueIsRequired()); 113 | } 114 | 115 | return FormValidation.ok(); 116 | } 117 | 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/main/java/jenkins/plugins/logstash/configuration/Syslog.java: -------------------------------------------------------------------------------- 1 | package jenkins.plugins.logstash.configuration; 2 | 3 | import org.jenkinsci.Symbol; 4 | import org.kohsuke.stapler.DataBoundConstructor; 5 | import org.kohsuke.stapler.DataBoundSetter; 6 | 7 | import com.cloudbees.syslog.MessageFormat; 8 | 9 | import hudson.Extension; 10 | import jenkins.plugins.logstash.persistence.LogstashIndexerDao.SyslogProtocol; 11 | import jenkins.plugins.logstash.persistence.SyslogDao; 12 | 13 | public class Syslog extends HostBasedLogstashIndexer 14 | { 15 | private MessageFormat messageFormat; 16 | private SyslogProtocol syslogProtocol; 17 | 18 | @DataBoundConstructor 19 | public Syslog() 20 | {} 21 | 22 | public MessageFormat getMessageFormat() 23 | { 24 | return messageFormat; 25 | } 26 | 27 | @DataBoundSetter 28 | public void setMessageFormat(MessageFormat messageFormat) 29 | { 30 | this.messageFormat = messageFormat; 31 | } 32 | 33 | public SyslogProtocol getSyslogProtocol() 34 | { 35 | return syslogProtocol; 36 | } 37 | 38 | @DataBoundSetter() 39 | public void setSyslogProtocol(SyslogProtocol syslogProtocol) 40 | { 41 | this.syslogProtocol = syslogProtocol; 42 | } 43 | 44 | @Override 45 | public int hashCode() 46 | { 47 | final int prime = 31; 48 | int result = super.hashCode(); 49 | result = prime * result + ((messageFormat == null) ? 0 : messageFormat.hashCode()); 50 | result = prime * result + ((syslogProtocol == null) ? 0 : syslogProtocol.hashCode()); 51 | return result; 52 | } 53 | 54 | @Override 55 | public boolean equals(Object obj) 56 | { 57 | if (this == obj) 58 | return true; 59 | if (!super.equals(obj)) 60 | return false; 61 | if (getClass() != obj.getClass()) 62 | return false; 63 | Syslog other = (Syslog)obj; 64 | if (messageFormat != other.messageFormat) 65 | return false; 66 | if (syslogProtocol != other.syslogProtocol) 67 | return false; 68 | return true; 69 | } 70 | 71 | @Override 72 | public SyslogDao createIndexerInstance() 73 | { 74 | SyslogDao syslogDao = new SyslogDao(getHost(), getPort()); 75 | syslogDao.setMessageFormat(messageFormat); 76 | return syslogDao; 77 | } 78 | 79 | @Extension 80 | @Symbol("syslog") 81 | public static class SyslogDescriptor extends LogstashIndexerDescriptor 82 | { 83 | 84 | @Override 85 | public String getDisplayName() 86 | { 87 | return "Syslog"; 88 | } 89 | 90 | @Override 91 | public int getDefaultPort() 92 | { 93 | return 519; 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/main/java/jenkins/plugins/logstash/persistence/AbstractLogstashIndexerDao.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright 2014 Rusty Gerard 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | 25 | package jenkins.plugins.logstash.persistence; 26 | 27 | import java.io.Serializable; 28 | import java.util.Calendar; 29 | import java.util.List; 30 | 31 | import jenkins.plugins.logstash.LogstashConfiguration; 32 | import net.sf.json.JSONObject; 33 | 34 | /** 35 | * Abstract data access object for Logstash indexers. 36 | * 37 | * @author Rusty Gerard 38 | * @since 1.0.0 39 | */ 40 | public abstract class AbstractLogstashIndexerDao implements LogstashIndexerDao, Serializable { 41 | 42 | @Override 43 | public JSONObject buildPayload(BuildData buildData, String jenkinsUrl, List logLines) { 44 | JSONObject payload = new JSONObject(); 45 | payload.put("data", buildData.toJson()); 46 | payload.put("message", logLines); 47 | payload.put("source", "jenkins"); 48 | payload.put("source_host", jenkinsUrl); 49 | payload.put("@buildTimestamp", buildData.getTimestamp()); 50 | payload.put("@timestamp", LogstashConfiguration.getInstance().getDateFormatter().format(Calendar.getInstance().getTime())); 51 | payload.put("@version", 1); 52 | 53 | return payload; 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/jenkins/plugins/logstash/persistence/HostBasedLogstashIndexerDao.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | package jenkins.plugins.logstash.persistence; 24 | 25 | 26 | import org.apache.commons.lang.StringUtils; 27 | 28 | /** 29 | * Abstract data access object for Logstash indexers. 30 | * 31 | * @since 2.0.0 32 | */ 33 | public abstract class HostBasedLogstashIndexerDao extends AbstractLogstashIndexerDao { 34 | 35 | private final String host; 36 | private final int port; 37 | 38 | public HostBasedLogstashIndexerDao(String host, int port) { 39 | this.host = host; 40 | this.port = port; 41 | if (StringUtils.isBlank(host)) { 42 | throw new IllegalArgumentException("host name is required"); 43 | } 44 | } 45 | 46 | public String getHost() { 47 | return host; 48 | } 49 | 50 | public int getPort() { 51 | return port; 52 | } 53 | 54 | @Override 55 | public String getDescription() { 56 | return this.host + ":" + this.port; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/jenkins/plugins/logstash/persistence/LogstashDao.java: -------------------------------------------------------------------------------- 1 | package jenkins.plugins.logstash.persistence; 2 | 3 | import java.io.IOException; 4 | import java.io.OutputStream; 5 | import java.net.Socket; 6 | import java.nio.charset.StandardCharsets; 7 | 8 | public class LogstashDao extends HostBasedLogstashIndexerDao { 9 | 10 | public LogstashDao(String logstashHostString, int logstashPortInt) { 11 | super(logstashHostString, logstashPortInt); 12 | } 13 | 14 | @Override 15 | public void push(String data) throws IOException { 16 | 17 | try (Socket logstashClientSocket = new Socket(getHost(), getPort())) 18 | { 19 | OutputStream out = logstashClientSocket.getOutputStream(); 20 | out.write(data.getBytes(StandardCharsets.UTF_8)); 21 | out.write(10); 22 | out.flush(); 23 | out.close(); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/jenkins/plugins/logstash/persistence/LogstashIndexerDao.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright 2014 Rusty Gerard 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | 25 | package jenkins.plugins.logstash.persistence; 26 | 27 | import java.io.IOException; 28 | import java.util.List; 29 | 30 | import net.sf.json.JSONObject; 31 | 32 | /** 33 | * Interface describing data access objects for Logstash indexers. 34 | * 35 | * @author Rusty Gerard 36 | * @since 1.0.0 37 | */ 38 | public interface LogstashIndexerDao { 39 | @Deprecated 40 | enum IndexerType { 41 | REDIS, 42 | RABBIT_MQ, 43 | ELASTICSEARCH, 44 | SYSLOG 45 | } 46 | 47 | @Deprecated 48 | enum SyslogFormat { 49 | RFC5424, 50 | RFC3164 51 | } 52 | 53 | enum SyslogProtocol { 54 | UDP 55 | } 56 | 57 | String getDescription(); 58 | 59 | /** 60 | * Sends the log data to the Logstash indexer. 61 | * 62 | * @param data 63 | * The serialized data, not null 64 | * @throws java.io.IOException 65 | * The data is not written to the server 66 | */ 67 | void push(String data) throws IOException; 68 | 69 | /** 70 | * Builds a JSON payload compatible with the Logstash schema. 71 | * 72 | * @param buildData 73 | * Metadata about the current build, not null 74 | * @param jenkinsUrl 75 | * The host name of the Jenkins instance, not null 76 | * @param logLines 77 | * The log data to transmit, not null 78 | * @return The formatted JSON object, never null 79 | */ 80 | JSONObject buildPayload(BuildData buildData, String jenkinsUrl, List logLines); 81 | } 82 | -------------------------------------------------------------------------------- /src/main/java/jenkins/plugins/logstash/persistence/RabbitMqDao.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright 2014 Rusty Gerard 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | 25 | package jenkins.plugins.logstash.persistence; 26 | 27 | import java.io.IOException; 28 | import java.nio.charset.Charset; 29 | 30 | import org.apache.commons.lang.StringUtils; 31 | 32 | import com.rabbitmq.client.Channel; 33 | import com.rabbitmq.client.Connection; 34 | import com.rabbitmq.client.ConnectionFactory; 35 | 36 | import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 37 | 38 | /** 39 | * RabbitMQ Data Access Object. 40 | * 41 | * TODO: support TLS 42 | * TODO: support vhost 43 | * 44 | * @author Rusty Gerard 45 | * @since 1.0.0 46 | */ 47 | @SuppressFBWarnings(value="SE_NO_SERIALVERSIONID") 48 | public class RabbitMqDao extends HostBasedLogstashIndexerDao { 49 | 50 | private transient ConnectionFactory pool; 51 | 52 | private final String queue; 53 | private final String username; 54 | private final String password; 55 | private final String charset; 56 | private final String virtualHost; 57 | 58 | 59 | //primary constructor used by indexer factory 60 | public RabbitMqDao(String host, int port, String key, String username, String password, Charset charset, String virtualHost) { 61 | this(null, host, port, key, username, password, charset, virtualHost); 62 | } 63 | 64 | /* 65 | * TODO: this constructor is only for testing so one can inject a mocked ConnectionFactory. 66 | * With Powermock we can intercept the creation of the ConnectionFactory and replace with a mock 67 | * making this constructor obsolete 68 | */ 69 | RabbitMqDao(ConnectionFactory factory, String host, int port, String queue, String username, String password, Charset charset, String vhost) { 70 | super(host, port); 71 | 72 | this.queue = queue; 73 | this.username = username; 74 | this.password = password; 75 | this.charset = charset.toString(); 76 | this.virtualHost = vhost; 77 | 78 | if (StringUtils.isBlank(queue)) { 79 | throw new IllegalArgumentException("rabbit queue name is required"); 80 | } 81 | 82 | // The ConnectionFactory must be a singleton 83 | // We assume this is used as a singleton as well 84 | // Calling this method means the configuration has changed and the pool must be re-initialized 85 | pool = factory; 86 | initPool(); 87 | } 88 | 89 | private synchronized ConnectionFactory getPool() { 90 | if (pool == null) { 91 | pool = new ConnectionFactory(); 92 | initPool(); 93 | } 94 | return pool; 95 | } 96 | 97 | private void initPool() { 98 | if (pool != null) 99 | { 100 | pool.setHost(getHost()); 101 | pool.setPort(getPort()); 102 | if (virtualHost != null) { 103 | pool.setVirtualHost(virtualHost); 104 | } 105 | 106 | if (!StringUtils.isBlank(username) && !StringUtils.isBlank(password)) { 107 | pool.setPassword(password); 108 | pool.setUsername(username); 109 | } 110 | } 111 | } 112 | 113 | public String getQueue() 114 | { 115 | return queue; 116 | } 117 | 118 | public String getUsername() 119 | { 120 | return username; 121 | } 122 | 123 | public String getPassword() 124 | { 125 | return password; 126 | } 127 | 128 | public String getVirtualHost() 129 | { 130 | return virtualHost; 131 | } 132 | 133 | /* 134 | * TODO: do we really need to open a connection each time? 135 | * channels are not thread-safe so we need a new channel each time but the connection 136 | * could be shared. Another idea would be to use one dao per build, reuse the channel and 137 | * synchronize the push on the channel (for pipeline builds where we can have multiple 138 | * threads writing, are freestyle projects guaranteed to be single threaded?). 139 | * (non-Javadoc) 140 | * @see jenkins.plugins.logstash.persistence.LogstashIndexerDao#push(java.lang.String) 141 | */ 142 | @Override 143 | public void push(String data) throws IOException { 144 | Connection connection = null; 145 | Channel channel = null; 146 | try { 147 | connection = getPool().newConnection(); 148 | channel = connection.createChannel(); 149 | // Ensure the queue exists 150 | 151 | try { 152 | channel.queueDeclarePassive(queue); 153 | } catch (IOException e) { 154 | // The queue does not exist and the channel has been closed 155 | finalizeChannel(channel); 156 | 157 | // Create the queue 158 | channel = connection.createChannel(); 159 | channel.queueDeclare(queue, true, false, false, null); 160 | } 161 | 162 | channel.basicPublish("", queue, null, data.getBytes(charset)); 163 | } finally { 164 | finalizeChannel(channel); 165 | finalizeConnection(connection); 166 | } 167 | } 168 | 169 | // TODO: connection.isOpen() should be avoided (see the rabbitmq doc) 170 | private void finalizeConnection(Connection connection) { 171 | if (connection != null && connection.isOpen()) { 172 | try { 173 | connection.close(); 174 | } catch (IOException e) { 175 | // This shouldn't happen but if it does there's nothing we can do 176 | e.printStackTrace(); 177 | } 178 | } 179 | } 180 | 181 | // TODO: connection.isOpen() should be avoided (see the rabbitmq doc) 182 | private void finalizeChannel(Channel channel) { 183 | if (channel != null && channel.isOpen()) { 184 | try { 185 | channel.close(); 186 | } catch (IOException e) { 187 | // This shouldn't happen but if it does there's nothing we can do 188 | e.printStackTrace(); 189 | } 190 | } 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /src/main/java/jenkins/plugins/logstash/persistence/RedisDao.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright 2014 Rusty Gerard 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | 25 | package jenkins.plugins.logstash.persistence; 26 | 27 | import java.io.IOException; 28 | 29 | import org.apache.commons.lang.StringUtils; 30 | 31 | import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 32 | import redis.clients.jedis.Jedis; 33 | import redis.clients.jedis.JedisPool; 34 | import redis.clients.jedis.JedisPoolConfig; 35 | import redis.clients.jedis.exceptions.JedisConnectionException; 36 | import redis.clients.jedis.exceptions.JedisException; 37 | 38 | /** 39 | * Redis Data Access Object. 40 | * 41 | * @author Rusty Gerard 42 | * @since 1.0.0 43 | */ 44 | @SuppressFBWarnings(value="SE_NO_SERIALVERSIONID") 45 | public class RedisDao extends HostBasedLogstashIndexerDao { 46 | 47 | private transient JedisPool pool; 48 | 49 | private final String password; 50 | private final String key; 51 | 52 | //primary constructor used by indexer factory 53 | public RedisDao(String host, int port, String key, String password) { 54 | this(null, host, port, key, password); 55 | } 56 | 57 | /* 58 | * TODO: this constructor is only for testing so one can inject a mocked JedisPool. 59 | * With Powermock we can intercept the creation of the JedisPool and replace with a mock 60 | * making this constructor obsolete 61 | */ 62 | RedisDao(JedisPool factory, String host, int port, String key, String password) { 63 | super(host, port); 64 | 65 | this.key = key; 66 | this.password = password; 67 | 68 | if (StringUtils.isBlank(key)) { 69 | throw new IllegalArgumentException("redis key is required"); 70 | } 71 | 72 | // The JedisPool must be a singleton 73 | // We assume this is used as a singleton as well 74 | pool = factory; 75 | } 76 | 77 | private synchronized void getJedisPool() { 78 | if (pool == null) { 79 | pool = new JedisPool(new JedisPoolConfig(), getHost(), getPort()); 80 | } 81 | } 82 | 83 | public String getPassword() 84 | { 85 | return password; 86 | } 87 | 88 | public String getKey() 89 | { 90 | return key; 91 | } 92 | 93 | @Override 94 | public void push(String data) throws IOException { 95 | Jedis jedis = null; 96 | boolean connectionBroken = false; 97 | try { 98 | getJedisPool(); 99 | jedis = pool.getResource(); 100 | if (!StringUtils.isBlank(password)) { 101 | jedis.auth(password); 102 | } 103 | 104 | jedis.connect(); 105 | long result = jedis.rpush(key, data); 106 | jedis.disconnect(); 107 | if (result <= 0) { 108 | throw new IOException("Failed to push results"); 109 | } 110 | } catch (JedisException e) { 111 | connectionBroken = (e instanceof JedisConnectionException); 112 | throw new IOException(e); 113 | } finally { 114 | if (jedis != null) { 115 | if (connectionBroken) { 116 | pool.returnBrokenResource(jedis); 117 | } else { 118 | pool.returnResource(jedis); 119 | } 120 | } 121 | } 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /src/main/java/jenkins/plugins/logstash/persistence/SyslogDao.java: -------------------------------------------------------------------------------- 1 | package jenkins.plugins.logstash.persistence; 2 | 3 | import java.io.IOException; 4 | 5 | import com.cloudbees.syslog.Facility; 6 | import com.cloudbees.syslog.MessageFormat; 7 | import com.cloudbees.syslog.Severity; 8 | import com.cloudbees.syslog.sender.UdpSyslogMessageSender; 9 | 10 | import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 11 | 12 | /* 13 | * TODO: add support for TcpSyslogMessageSender 14 | */ 15 | @SuppressFBWarnings(value="SE_NO_SERIALVERSIONID") 16 | public class SyslogDao extends HostBasedLogstashIndexerDao { 17 | 18 | private MessageFormat messageFormat = MessageFormat.RFC_3164; 19 | private transient UdpSyslogMessageSender messageSender; 20 | 21 | public SyslogDao(String host, int port) { 22 | this(null, host, port); 23 | } 24 | 25 | /* 26 | * TODO: this constructor is only for testing so one can inject a mocked UdpSyslogMessageSender. 27 | * With Powermock we can intercept the creation of the UdpSyslogMessageSender and replace with a mock 28 | * making this constructor obsolete 29 | */ 30 | 31 | public SyslogDao(UdpSyslogMessageSender udpSyslogMessageSender, String host, int port) { 32 | super(host, port); 33 | messageSender = udpSyslogMessageSender; 34 | } 35 | 36 | public void setMessageFormat(MessageFormat format) { 37 | messageFormat = format; 38 | } 39 | 40 | public MessageFormat getMessageFormat() { 41 | return messageFormat; 42 | } 43 | 44 | private synchronized void getMessageSender() { 45 | if (messageSender == null) { 46 | messageSender = new UdpSyslogMessageSender(); 47 | } 48 | } 49 | 50 | @Override 51 | public void push(String data) throws IOException { 52 | // Making the JSON document compliant to Common Event Expression (CEE) 53 | // Ref: http://www.rsyslog.com/json-elasticsearch/ 54 | data = " @cee: " + data; 55 | // SYSLOG Configuration 56 | getMessageSender(); 57 | messageSender.setDefaultMessageHostname(getHost()); 58 | messageSender.setDefaultAppName("jenkins:"); 59 | messageSender.setDefaultFacility(Facility.USER); 60 | messageSender.setDefaultSeverity(Severity.INFORMATIONAL); 61 | messageSender.setSyslogServerHostname(getHost()); 62 | messageSender.setSyslogServerPort(getPort()); 63 | // The Logstash syslog input module support only the RFC_3164 format 64 | // Ref: https://www.elastic.co/guide/en/logstash/current/plugins-inputs-syslog.html 65 | messageSender.setMessageFormat(messageFormat); 66 | // Sending the message 67 | messageSender.sendMessage(data); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/main/java/jenkins/plugins/logstash/pipeline/GlobalDecorator.java: -------------------------------------------------------------------------------- 1 | package jenkins.plugins.logstash.pipeline; 2 | 3 | import java.io.IOException; 4 | import java.io.OutputStream; 5 | import java.nio.charset.StandardCharsets; 6 | import java.util.logging.Level; 7 | import java.util.logging.Logger; 8 | 9 | import org.jenkinsci.plugins.workflow.flow.FlowExecutionOwner; 10 | import org.jenkinsci.plugins.workflow.job.WorkflowRun; 11 | import org.jenkinsci.plugins.workflow.log.TaskListenerDecorator; 12 | 13 | import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 14 | import hudson.Extension; 15 | import hudson.model.Queue; 16 | import hudson.model.Run; 17 | import jenkins.plugins.logstash.LogstashConfiguration; 18 | import jenkins.plugins.logstash.LogstashOutputStream; 19 | import jenkins.plugins.logstash.LogstashWriter; 20 | 21 | @SuppressFBWarnings(value="SE_NO_SERIALVERSIONID") 22 | public class GlobalDecorator extends TaskListenerDecorator { 23 | private static final Logger LOGGER = Logger.getLogger(GlobalDecorator.class.getName()); 24 | 25 | private transient Run run; 26 | private String stageName; 27 | private String agentName; 28 | 29 | public GlobalDecorator(WorkflowRun run) { 30 | this(run, null, null); 31 | } 32 | public GlobalDecorator(WorkflowRun run, String stageName, String agentName) { 33 | LOGGER.log(Level.INFO, "Creating decorator for {0}", run.toString()); 34 | this.run = run; 35 | this.stageName = stageName; 36 | this.agentName = agentName; 37 | } 38 | 39 | @Override 40 | public OutputStream decorate(OutputStream logger) throws IOException, InterruptedException { 41 | LogstashWriter writer = new LogstashWriter(run, logger, null, StandardCharsets.UTF_8, stageName, agentName); 42 | LogstashOutputStream out = new LogstashOutputStream(logger, writer); 43 | return out; 44 | } 45 | 46 | @Extension 47 | public static final class Factory implements TaskListenerDecorator.Factory { 48 | 49 | @Override 50 | public TaskListenerDecorator of(FlowExecutionOwner owner) { 51 | if (!LogstashConfiguration.getInstance().isEnableGlobally()) { 52 | return null; 53 | } 54 | try { 55 | Queue.Executable executable = owner.getExecutable(); 56 | if (executable instanceof WorkflowRun) { 57 | return new GlobalDecorator((WorkflowRun) executable); 58 | } 59 | } catch (IOException x) { 60 | LOGGER.log(Level.WARNING, null, x); 61 | } 62 | return null; 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/main/java/jenkins/plugins/logstash/pipeline/LogstashSendStep.java: -------------------------------------------------------------------------------- 1 | package jenkins.plugins.logstash.pipeline; 2 | 3 | import java.io.PrintStream; 4 | import java.util.HashSet; 5 | import java.util.Set; 6 | 7 | import org.jenkinsci.plugins.workflow.steps.Step; 8 | import org.jenkinsci.plugins.workflow.steps.StepContext; 9 | import org.jenkinsci.plugins.workflow.steps.StepDescriptor; 10 | import org.jenkinsci.plugins.workflow.steps.StepExecution; 11 | import org.jenkinsci.plugins.workflow.steps.SynchronousNonBlockingStepExecution; 12 | import org.kohsuke.stapler.DataBoundConstructor; 13 | 14 | import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 15 | import hudson.Extension; 16 | import hudson.model.Run; 17 | import hudson.model.TaskListener; 18 | import jenkins.plugins.logstash.LogstashWriter; 19 | import jenkins.plugins.logstash.Messages; 20 | 21 | /** 22 | * Sends the tail of the log in a single event to a logstash indexer. 23 | * Pipeline counterpart of the LogstashNotifier. 24 | */ 25 | public class LogstashSendStep extends Step 26 | { 27 | 28 | private int maxLines; 29 | private boolean failBuild; 30 | 31 | @DataBoundConstructor 32 | public LogstashSendStep(int maxLines, boolean failBuild) 33 | { 34 | this.maxLines = maxLines; 35 | this.failBuild = failBuild; 36 | } 37 | 38 | public int getMaxLines() 39 | { 40 | return maxLines; 41 | } 42 | 43 | public boolean isFailBuild() 44 | { 45 | return failBuild; 46 | } 47 | 48 | @Override 49 | public StepExecution start(StepContext context) throws Exception 50 | { 51 | return new Execution(context, maxLines, failBuild); 52 | } 53 | 54 | @SuppressFBWarnings(value="SE_TRANSIENT_FIELD_NOT_RESTORED", justification="Only used when starting.") 55 | private static class Execution extends SynchronousNonBlockingStepExecution 56 | { 57 | 58 | private static final long serialVersionUID = 1L; 59 | 60 | private transient final int maxLines; 61 | private transient final boolean failBuild; 62 | 63 | Execution(StepContext context, int maxLines, boolean failBuild) 64 | { 65 | super(context); 66 | this.maxLines = maxLines; 67 | this.failBuild = failBuild; 68 | } 69 | 70 | @Override 71 | protected Void run() throws Exception 72 | { 73 | Run run = getContext().get(Run.class); 74 | TaskListener listener = getContext().get(TaskListener.class); 75 | PrintStream errorStream = listener.getLogger(); 76 | LogstashWriter logstash = new LogstashWriter(run, errorStream, listener, run.getCharset()); 77 | logstash.writeBuildLog(maxLines); 78 | if (failBuild && logstash.isConnectionBroken()) 79 | { 80 | throw new Exception("Failed to send data to Indexer"); 81 | } 82 | return null; 83 | } 84 | 85 | } 86 | 87 | @Extension 88 | public static class DescriptorImpl extends StepDescriptor 89 | { 90 | 91 | /** {@inheritDoc} */ 92 | @Override 93 | public String getDisplayName() 94 | { 95 | return Messages.DisplayName(); 96 | } 97 | 98 | @Override 99 | public String getFunctionName() 100 | { 101 | return "logstashSend"; 102 | } 103 | 104 | @Override 105 | public Set> getRequiredContext() 106 | { 107 | Set> contexts = new HashSet<>(); 108 | contexts.add(TaskListener.class); 109 | contexts.add(Run.class); 110 | return contexts; 111 | } 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/main/java/jenkins/plugins/logstash/pipeline/LogstashStep.java: -------------------------------------------------------------------------------- 1 | package jenkins.plugins.logstash.pipeline; 2 | 3 | import java.io.IOException; 4 | import java.util.Collections; 5 | import java.util.HashSet; 6 | import java.util.Set; 7 | import java.util.logging.Level; 8 | import java.util.logging.Logger; 9 | 10 | import edu.umd.cs.findbugs.annotations.NonNull; 11 | 12 | import org.jenkinsci.plugins.workflow.actions.LabelAction; 13 | import org.jenkinsci.plugins.workflow.actions.WorkspaceAction; 14 | import org.jenkinsci.plugins.workflow.graph.BlockStartNode; 15 | import org.jenkinsci.plugins.workflow.graph.FlowNode; 16 | import org.jenkinsci.plugins.workflow.graph.StepNode; 17 | import org.jenkinsci.plugins.workflow.job.WorkflowRun; 18 | import org.jenkinsci.plugins.workflow.log.TaskListenerDecorator; 19 | import org.jenkinsci.plugins.workflow.steps.AbstractStepExecutionImpl; 20 | import org.jenkinsci.plugins.workflow.steps.BodyExecutionCallback; 21 | import org.jenkinsci.plugins.workflow.steps.BodyInvoker; 22 | import org.jenkinsci.plugins.workflow.steps.Step; 23 | import org.jenkinsci.plugins.workflow.steps.StepContext; 24 | import org.jenkinsci.plugins.workflow.steps.StepDescriptor; 25 | import org.jenkinsci.plugins.workflow.steps.StepExecution; 26 | import org.jenkinsci.plugins.workflow.support.steps.ExecutorStep; 27 | import org.jenkinsci.plugins.workflow.support.steps.StageStep; 28 | import org.kohsuke.stapler.DataBoundConstructor; 29 | 30 | import hudson.Extension; 31 | import hudson.model.Run; 32 | import hudson.model.TaskListener; 33 | import jenkins.YesNoMaybe; 34 | import jenkins.plugins.logstash.LogstashConfiguration; 35 | 36 | /** 37 | * This is the pipeline counterpart of the LogstashJobProperty. 38 | * This step will send the logs line by line to an indexer. 39 | */ 40 | public class LogstashStep extends Step { 41 | 42 | private static final Logger LOGGER = Logger.getLogger(LogstashStep.class.getName()); 43 | /** Constructor. */ 44 | @DataBoundConstructor 45 | public LogstashStep() {} 46 | 47 | @Override 48 | public StepExecution start(StepContext context) throws Exception 49 | { 50 | return new Execution(context); 51 | } 52 | 53 | /** Execution for {@link LogstashStep}. */ 54 | public static class Execution extends AbstractStepExecutionImpl { 55 | 56 | public Execution(StepContext context) 57 | { 58 | super(context); 59 | } 60 | 61 | private static final long serialVersionUID = 1L; 62 | 63 | @Override 64 | public void onResume() 65 | { 66 | } 67 | 68 | /** {@inheritDoc} */ 69 | @Override 70 | public boolean start() throws Exception { 71 | StepContext context = getContext(); 72 | BodyInvoker invoker = context.newBodyInvoker().withCallback(BodyExecutionCallback.wrap(context)); 73 | if (LogstashConfiguration.getInstance().isEnableGlobally()) { 74 | context.get(TaskListener.class).getLogger().println("The logstash step is unnecessary when logstash is enabled for all builds."); 75 | } else { 76 | invoker.withContext(getMergedDecorator(context)); 77 | } 78 | invoker.start(); 79 | return false; 80 | } 81 | 82 | private TaskListenerDecorator getMergedDecorator(StepContext context) 83 | throws IOException, InterruptedException { 84 | Run run = context.get(Run.class); 85 | FlowNode node = context.get(FlowNode.class); 86 | FlowNode stageNode = getStageNode(node); 87 | String stageName = null; 88 | if (stageNode != null) { 89 | LabelAction labelAction = stageNode.getAction(LabelAction.class); 90 | if (labelAction != null) { 91 | stageName = labelAction.getDisplayName(); 92 | } 93 | } 94 | String agentName = getAgentName(node); 95 | return TaskListenerDecorator.merge(context.get(TaskListenerDecorator.class), new GlobalDecorator((WorkflowRun) run, stageName, agentName)); 96 | } 97 | 98 | private String getAgentName(FlowNode node) { 99 | for (BlockStartNode bsn : node.iterateEnclosingBlocks()) { 100 | if (bsn instanceof StepNode) { 101 | StepDescriptor descriptor = ((StepNode) bsn).getDescriptor(); 102 | if (descriptor instanceof ExecutorStep.DescriptorImpl) { 103 | WorkspaceAction workspaceAction = bsn.getAction(WorkspaceAction.class); 104 | if (workspaceAction != null) { 105 | return workspaceAction.getNode(); 106 | } 107 | } 108 | } 109 | } 110 | 111 | return null; 112 | } 113 | 114 | private FlowNode getStageNode(FlowNode node) { 115 | for (BlockStartNode bsn : node.iterateEnclosingBlocks()) { 116 | if (isStageNode(bsn)) { 117 | return bsn; 118 | } 119 | } 120 | return null; 121 | } 122 | 123 | private boolean isStageNode(FlowNode node) { 124 | if (node instanceof StepNode) { 125 | StepDescriptor descriptor = ((StepNode) node).getDescriptor(); 126 | if (descriptor instanceof StageStep.DescriptorImpl) { 127 | LabelAction labelAction = node.getAction(LabelAction.class); 128 | if (labelAction != null) { 129 | return true; 130 | } 131 | } 132 | } 133 | 134 | return false; 135 | } 136 | 137 | /** {@inheritDoc} */ 138 | @Override 139 | public void stop(@NonNull Throwable cause) throws Exception { 140 | getContext().onFailure(cause); 141 | } 142 | } 143 | 144 | /** Descriptor for {@link LogstashStep}. */ 145 | @Extension(dynamicLoadable = YesNoMaybe.YES, optional = true) 146 | public static class DescriptorImpl extends StepDescriptor { 147 | 148 | /** {@inheritDoc} */ 149 | @Override 150 | public String getDisplayName() { 151 | return "Send individual log lines to Logstash"; 152 | } 153 | 154 | /** {@inheritDoc} */ 155 | @Override 156 | public String getFunctionName() { 157 | return "logstash"; 158 | } 159 | 160 | /** {@inheritDoc} */ 161 | @Override 162 | public boolean takesImplicitBlockArgument() { 163 | return true; 164 | } 165 | 166 | @Override 167 | public Set> getRequiredContext() { 168 | Set> context = new HashSet<>(); 169 | Collections.addAll(context, Run.class, FlowNode.class); 170 | return Collections.unmodifiableSet(context); 171 | } 172 | } 173 | 174 | } 175 | -------------------------------------------------------------------------------- /src/main/java/jenkins/plugins/logstash/utils/SSLHelper.java: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * The MIT License 4 | * 5 | * Copyright 2014 Barnes and Noble College 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a copy 8 | * of this software and associated documentation files (the "Software"), to deal 9 | * in the Software without restriction, including without limitation the rights 10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | * copies of the Software, and to permit persons to whom the Software is 12 | * furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in 15 | * all copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | * THE SOFTWARE. 24 | */ 25 | 26 | package jenkins.plugins.logstash.utils; 27 | 28 | import org.apache.http.impl.client.HttpClientBuilder; 29 | 30 | import java.io.IOException; 31 | import java.security.KeyManagementException; 32 | import java.security.KeyStoreException; 33 | import java.security.KeyStore; 34 | import java.security.NoSuchAlgorithmException; 35 | import java.security.cert.CertificateException; 36 | import java.security.cert.X509Certificate; 37 | import javax.net.ssl.SSLContext; 38 | import javax.net.ssl.TrustManager; 39 | import javax.net.ssl.TrustManagerFactory; 40 | import javax.net.ssl.X509TrustManager; 41 | 42 | 43 | public class SSLHelper { 44 | 45 | public static void setClientBuilderSSLContext(HttpClientBuilder clientBuilder, KeyStore customKeyStore) 46 | throws CertificateException, NoSuchAlgorithmException, 47 | IOException, KeyStoreException, KeyManagementException 48 | { 49 | if (customKeyStore == null) 50 | return; 51 | String alias = customKeyStore.aliases().nextElement(); 52 | X509Certificate certificate = (X509Certificate) customKeyStore.getCertificate(alias); 53 | if (certificate != null) 54 | clientBuilder.setSslcontext(SSLHelper.createSSLContext(alias, certificate)); 55 | } 56 | 57 | public static SSLContext createSSLContext(String alias, X509Certificate certificate) 58 | throws CertificateException, NoSuchAlgorithmException, 59 | IOException, KeyStoreException, KeyManagementException 60 | { 61 | // Step 1: Get all defaults 62 | TrustManagerFactory tmf = TrustManagerFactory 63 | .getInstance(TrustManagerFactory.getDefaultAlgorithm()); 64 | // Using null here initialises the TMF with the default trust store. 65 | tmf.init((KeyStore) null); 66 | 67 | // Get hold of the default trust manager 68 | X509TrustManager defaultTM = null; 69 | for (TrustManager tm : tmf.getTrustManagers()) { 70 | if (tm instanceof X509TrustManager) { 71 | defaultTM = (X509TrustManager) tm; 72 | break; 73 | } 74 | } 75 | 76 | // Step 2: Add custom cert to keystore 77 | KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType()); 78 | ks.load(null, null); 79 | ks.setEntry(alias, new KeyStore.TrustedCertificateEntry(certificate), null); 80 | 81 | // Create TMF with our custom cert's KS 82 | tmf = TrustManagerFactory 83 | .getInstance(TrustManagerFactory.getDefaultAlgorithm()); 84 | tmf.init(ks); 85 | 86 | // Get hold of the custom trust manager 87 | X509TrustManager customTM = null; 88 | for (TrustManager tm : tmf.getTrustManagers()) { 89 | if (tm instanceof X509TrustManager) { 90 | customTM = (X509TrustManager) tm; 91 | break; 92 | } 93 | } 94 | 95 | // Step 3: Wrap it in our own class. 96 | final X509TrustManager finalDefaultTM = defaultTM; 97 | final X509TrustManager finalCustomTM = customTM; 98 | X509TrustManager combinedTM = new X509TrustManager() { 99 | @Override 100 | public X509Certificate[] getAcceptedIssuers() { 101 | return finalDefaultTM.getAcceptedIssuers(); 102 | } 103 | 104 | @Override 105 | public void checkServerTrusted(X509Certificate[] chain, 106 | String authType) throws CertificateException { 107 | try { 108 | finalCustomTM.checkServerTrusted(chain, authType); 109 | } catch (CertificateException e) { 110 | // This will throw another CertificateException if this fails too. 111 | finalDefaultTM.checkServerTrusted(chain, authType); 112 | } 113 | } 114 | 115 | @Override 116 | public void checkClientTrusted(X509Certificate[] chain, 117 | String authType) throws CertificateException { 118 | finalDefaultTM.checkClientTrusted(chain, authType); 119 | } 120 | }; 121 | 122 | // Step 4: Finally, create SSLContext based off of this combined TM 123 | SSLContext sslContext = SSLContext.getInstance("TLS"); 124 | sslContext.init(null, new TrustManager[]{combinedTM}, null); 125 | 126 | return sslContext; 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /src/main/java/jenkins/plugins/logstash/utils/URIConverter.java: -------------------------------------------------------------------------------- 1 | package jenkins.plugins.logstash.utils; 2 | 3 | import java.net.URI; 4 | 5 | import org.apache.commons.beanutils.converters.AbstractConverter; 6 | 7 | public class URIConverter extends AbstractConverter 8 | { 9 | 10 | @Override 11 | protected Object convertToType(Class type, Object value) throws Throwable 12 | { 13 | return new URI(value.toString()); 14 | } 15 | 16 | @Override 17 | protected Class getDefaultType() 18 | { 19 | return URI.class; 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/main/resources/index.jelly: -------------------------------------------------------------------------------- 1 | 2 |
3 | Adds the possibility to push builds logs and build data to a Logstash indexer such as Redis, RabbitMQ, Elastic Search or to Syslog. 4 |
5 | -------------------------------------------------------------------------------- /src/main/resources/jenkins/plugins/logstash/LogstashConfiguration/config.jelly: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/main/resources/jenkins/plugins/logstash/LogstashConfiguration/help-enableGlobally.html: -------------------------------------------------------------------------------- 1 | Checking will make all builds forward their log to the above indexer. 2 | -------------------------------------------------------------------------------- /src/main/resources/jenkins/plugins/logstash/LogstashConfiguration/help-enabled.html: -------------------------------------------------------------------------------- 1 | By enabling logstash you can send the log output to various indexers like Elastic Search or RabbitMQ (See help of Indexer Type). 2 | -------------------------------------------------------------------------------- /src/main/resources/jenkins/plugins/logstash/LogstashConfiguration/help-logstashIndexer.jelly: -------------------------------------------------------------------------------- 1 | 2 | 3 |

Supported Indexers

4 |

5 | 6 |

7 |
${descriptor.displayName}
8 |
9 |
10 | 11 |

12 | -------------------------------------------------------------------------------- /src/main/resources/jenkins/plugins/logstash/LogstashConfiguration/help-milliSecondTimestamps.html: -------------------------------------------------------------------------------- 1 | Use time stamps that include the milliseconds of the event. This should ensure that the indexer will keep the order of events when 2 | they arrive in the same second. 3 | -------------------------------------------------------------------------------- /src/main/resources/jenkins/plugins/logstash/LogstashInstallation/global.jelly: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Logstash configuration has moved to the Global Configuration 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/main/resources/jenkins/plugins/logstash/LogstashJobProperty/config.jelly: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/main/resources/jenkins/plugins/logstash/LogstashNotifier/config.jelly: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/main/resources/jenkins/plugins/logstash/LogstashNotifier/help.html: -------------------------------------------------------------------------------- 1 |
2 |

Send the tail of the log to Logstash.

3 |
4 | -------------------------------------------------------------------------------- /src/main/resources/jenkins/plugins/logstash/Messages.properties: -------------------------------------------------------------------------------- 1 | # The MIT License 2 | # 3 | # Copyright 2013 Hewlett-Packard Development Company, L.P. 4 | # 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | # 12 | # The above copyright notice and this permission notice shall be included in 13 | # all copies or substantial portions of the Software. 14 | # 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | # THE SOFTWARE. 22 | 23 | DisplayName = Send console log to Logstash 24 | ValueIsInt = Value must be an integer 25 | ValueIsRequired = Value is required 26 | PleaseProvideHost = Please set a valid host name 27 | ProvideValidMimeType = Please provide a valid mime type 28 | -------------------------------------------------------------------------------- /src/main/resources/jenkins/plugins/logstash/configuration/ElasticSearch/config.jelly: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/main/resources/jenkins/plugins/logstash/configuration/ElasticSearch/help-mimeType.html: -------------------------------------------------------------------------------- 1 |
2 |

3 | MIME type of the request body that is sent to ELASTICSEARCH indexer. It should be of the form type/subtype e.g. application/json
4 | Since this is a field for MIME Type and not Content-Type we do not support additional content-type parameters like charset or boundary. 5 |

6 |
7 | -------------------------------------------------------------------------------- /src/main/resources/jenkins/plugins/logstash/configuration/ElasticSearch/help-password.html: -------------------------------------------------------------------------------- 1 |
2 |

The password to use when connecting to the remote indexing server.
3 | Leave this field blank if authentication is not enabled.

4 |
5 | -------------------------------------------------------------------------------- /src/main/resources/jenkins/plugins/logstash/configuration/ElasticSearch/help-uri.html: -------------------------------------------------------------------------------- 1 |
2 | The full url to the elastic search index including scheme, port, index and type (<scheme>://<host>:<port>/<index>/<type>).
3 | When pushing to a Logstash indexer with a http input you don't need index and type. 4 |
5 | -------------------------------------------------------------------------------- /src/main/resources/jenkins/plugins/logstash/configuration/ElasticSearch/help-username.html: -------------------------------------------------------------------------------- 1 |
2 |

The username to use when connecting to the remote indexing server.
3 | Leave this field blank if authentication is not enabled or a username is not required.

4 |
5 | -------------------------------------------------------------------------------- /src/main/resources/jenkins/plugins/logstash/configuration/ElasticSearch/help.jelly: -------------------------------------------------------------------------------- 1 | 2 | 3 | Allows to push to Elastic Search or any indexer that accepts POST requests, e.g. Valo.io or Logstash with an HTTP input. 4 | 5 | -------------------------------------------------------------------------------- /src/main/resources/jenkins/plugins/logstash/configuration/HostBasedLogstashIndexer/config.jelly: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/main/resources/jenkins/plugins/logstash/configuration/HostBasedLogstashIndexer/help-host.html: -------------------------------------------------------------------------------- 1 |
2 |

The host name or IP address of the indexer to send log data to.
3 |

4 | -------------------------------------------------------------------------------- /src/main/resources/jenkins/plugins/logstash/configuration/HostBasedLogstashIndexer/help-port.html: -------------------------------------------------------------------------------- 1 |
2 |

The port number the indexer listens on.

3 |
4 | -------------------------------------------------------------------------------- /src/main/resources/jenkins/plugins/logstash/configuration/Logstash/help.jelly: -------------------------------------------------------------------------------- 1 | 2 | 3 | Push to Logstash with TCP input. 4 | 5 | -------------------------------------------------------------------------------- /src/main/resources/jenkins/plugins/logstash/configuration/RabbitMq/configure-advanced.jelly: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/main/resources/jenkins/plugins/logstash/configuration/RabbitMq/help-charset.html: -------------------------------------------------------------------------------- 1 |
2 |

The charset to use when publishing to the RabbitMQ server.
3 | Leave empty to use the default charset. 4 |

5 |
6 | -------------------------------------------------------------------------------- /src/main/resources/jenkins/plugins/logstash/configuration/RabbitMq/help-password.html: -------------------------------------------------------------------------------- 1 |
2 |

The password to use when connecting to the remote indexing server.
3 | Leave this field blank if authentication is not enabled.

4 |
5 | -------------------------------------------------------------------------------- /src/main/resources/jenkins/plugins/logstash/configuration/RabbitMq/help-queue.html: -------------------------------------------------------------------------------- 1 |
2 | The name of a RabbitMq queue. 3 |
4 | -------------------------------------------------------------------------------- /src/main/resources/jenkins/plugins/logstash/configuration/RabbitMq/help-username.html: -------------------------------------------------------------------------------- 1 |
2 |

The username to use when connecting to the remote indexing server.
3 | Leave this field blank if authentication is not enabled or a username is not required.

4 |
5 | -------------------------------------------------------------------------------- /src/main/resources/jenkins/plugins/logstash/configuration/RabbitMq/help-virtualHost.html: -------------------------------------------------------------------------------- 1 |
2 |

The virtual Host of the RabbitMQ server. 3 |

4 |
5 | -------------------------------------------------------------------------------- /src/main/resources/jenkins/plugins/logstash/configuration/RabbitMq/help.jelly: -------------------------------------------------------------------------------- 1 | 2 | 3 | RabbitMQ {mechanism => PLAIN} 4 | 5 | -------------------------------------------------------------------------------- /src/main/resources/jenkins/plugins/logstash/configuration/Redis/configure-advanced.jelly: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/main/resources/jenkins/plugins/logstash/configuration/Redis/help-key.html: -------------------------------------------------------------------------------- 1 |
2 | The name of a Redis list or channel. 3 |
4 | -------------------------------------------------------------------------------- /src/main/resources/jenkins/plugins/logstash/configuration/Redis/help-password.html: -------------------------------------------------------------------------------- 1 |
2 |

The password to use when connecting to the remote indexing server.
3 | Leave this field blank if authentication is not enabled.

4 |
5 | -------------------------------------------------------------------------------- /src/main/resources/jenkins/plugins/logstash/configuration/Redis/help.jelly: -------------------------------------------------------------------------------- 1 | 2 | 3 | Redis {format => 'json_event'} 4 | 5 | -------------------------------------------------------------------------------- /src/main/resources/jenkins/plugins/logstash/configuration/Syslog/configure-advanced.jelly: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ${it.name()} 5 | 6 | 7 | ${it.name()} 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/main/resources/jenkins/plugins/logstash/configuration/Syslog/help-messageFormat.html: -------------------------------------------------------------------------------- 1 |
2 |

The Syslog format used to send the messages.
3 | RFC5424: The default format.
4 | RFC3164: The format supported by the Logstash Syslog input plugin.

5 |
6 | -------------------------------------------------------------------------------- /src/main/resources/jenkins/plugins/logstash/configuration/Syslog/help-syslogProtocol.html: -------------------------------------------------------------------------------- 1 |
2 |

The Syslog protocol used to send the messages.

3 |
4 | -------------------------------------------------------------------------------- /src/main/resources/jenkins/plugins/logstash/configuration/Syslog/help.jelly: -------------------------------------------------------------------------------- 1 | 2 | 3 | Syslog {format => cee/json (RFC-5424,RFC-3164), protocol => UDP} 4 | 5 | -------------------------------------------------------------------------------- /src/main/resources/jenkins/plugins/logstash/pipeline/LogstashSendStep/config.jelly: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/main/resources/jenkins/plugins/logstash/pipeline/LogstashSendStep/help.html: -------------------------------------------------------------------------------- 1 |
2 |

Send the tail of the log to Logstash.

3 | Hint: 4 | In order to get the result in the data sent to Logstash it must be set before the logstashSend step.
5 | Due to the way log output was collected in older version of the pipeline plugin, the logstashSend step might not 6 | transfer the lines logged directly before the step is called. Adding a sleep of 1 second might help here. 7 |

8 |
9 | -------------------------------------------------------------------------------- /src/main/resources/jenkins/plugins/logstash/pipeline/LogstashStep/config.jelly: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/main/resources/jenkins/plugins/logstash/pipeline/LogstashStep/help.html: -------------------------------------------------------------------------------- 1 |
Send individual log lines to Logstash.
2 | -------------------------------------------------------------------------------- /src/main/webapp/help/help-failBuild.html: -------------------------------------------------------------------------------- 1 |
2 |

If checked, mark build as failed if this step fails.

3 |
4 | -------------------------------------------------------------------------------- /src/main/webapp/help/help-maxLines.html: -------------------------------------------------------------------------------- 1 |
2 |

The maximum number of log lines to send to Logstash.
3 | If the log is bigger than this, only the most recent lines are sent.
4 | -1 indicates there is no maximum (for very large logs, consider using the Logstash Job Property instead or the logstash step).

5 |
6 | -------------------------------------------------------------------------------- /src/main/webapp/help/help.html: -------------------------------------------------------------------------------- 1 | Send individual log lines to Logstash. 2 | -------------------------------------------------------------------------------- /src/test/java/jenkins/plugins/logstash/ConfigAsCodeTest.java: -------------------------------------------------------------------------------- 1 | package jenkins.plugins.logstash; 2 | 3 | import static org.hamcrest.Matchers.instanceOf; 4 | import static org.hamcrest.Matchers.is; 5 | import static org.junit.Assert.assertThat; 6 | 7 | import org.junit.Rule; 8 | import org.junit.Test; 9 | import org.jvnet.hudson.test.JenkinsRule; 10 | 11 | import io.jenkins.plugins.casc.ConfigurationAsCode; 12 | import io.jenkins.plugins.casc.ConfiguratorException; 13 | import jenkins.plugins.logstash.configuration.ElasticSearch; 14 | import jenkins.plugins.logstash.configuration.Logstash; 15 | import jenkins.plugins.logstash.configuration.RabbitMq; 16 | import jenkins.plugins.logstash.configuration.Redis; 17 | 18 | public class ConfigAsCodeTest 19 | { 20 | 21 | @Rule public JenkinsRule r = new JenkinsRule(); 22 | 23 | @Test 24 | public void elasticSearch() throws ConfiguratorException 25 | { 26 | ConfigurationAsCode.get().configure(ConfigAsCodeTest.class.getResource("/jcasc/elasticSearch.yaml").toString()); 27 | LogstashConfiguration c = LogstashConfiguration.getInstance(); 28 | assertThat(c.isEnabled(), is(true)); 29 | assertThat(c.isEnableGlobally(), is(true)); 30 | assertThat(c.isMilliSecondTimestamps(), is(true)); 31 | assertThat(c.getLogstashIndexer(), is(instanceOf(ElasticSearch.class))); 32 | ElasticSearch es = (ElasticSearch) c.getLogstashIndexer(); 33 | assertThat(es.getUri().toString(), is("http://localhost:9200/jenkins/test")); 34 | assertThat(es.getMimeType(), is("application/json")); 35 | assertThat(es.getUsername(), is("es")); 36 | } 37 | 38 | @Test 39 | public void logstash() throws ConfiguratorException 40 | { 41 | ConfigurationAsCode.get().configure(ConfigAsCodeTest.class.getResource("/jcasc/logstash.yaml").toString()); 42 | LogstashConfiguration c = LogstashConfiguration.getInstance(); 43 | assertThat(c.isEnabled(), is(true)); 44 | assertThat(c.isEnableGlobally(), is(true)); 45 | assertThat(c.isMilliSecondTimestamps(), is(true)); 46 | assertThat(c.getLogstashIndexer(), is(instanceOf(Logstash.class))); 47 | Logstash logstash = (Logstash) c.getLogstashIndexer(); 48 | assertThat(logstash.getHost(), is("localhost")); 49 | assertThat(logstash.getPort(), is(9200)); 50 | } 51 | 52 | @Test 53 | public void rabbitMq() throws ConfiguratorException 54 | { 55 | ConfigurationAsCode.get().configure(ConfigAsCodeTest.class.getResource("/jcasc/rabbitmq.yaml").toString()); 56 | LogstashConfiguration c = LogstashConfiguration.getInstance(); 57 | assertThat(c.isEnabled(), is(true)); 58 | assertThat(c.isEnableGlobally(), is(true)); 59 | assertThat(c.isMilliSecondTimestamps(), is(true)); 60 | assertThat(c.getLogstashIndexer(), is(instanceOf(RabbitMq.class))); 61 | RabbitMq rabbitMq = (RabbitMq) c.getLogstashIndexer(); 62 | assertThat(rabbitMq.getHost(), is("localhost")); 63 | assertThat(rabbitMq.getPort(), is(9200)); 64 | assertThat(rabbitMq.getVirtualHost(), is("/vhost")); 65 | assertThat(rabbitMq.getUsername(), is("rabbit")); 66 | } 67 | 68 | @Test 69 | public void redis() throws ConfiguratorException 70 | { 71 | ConfigurationAsCode.get().configure(ConfigAsCodeTest.class.getResource("/jcasc/redis.yaml").toString()); 72 | LogstashConfiguration c = LogstashConfiguration.getInstance(); 73 | assertThat(c.isEnabled(), is(true)); 74 | assertThat(c.isEnableGlobally(), is(true)); 75 | assertThat(c.isMilliSecondTimestamps(), is(true)); 76 | assertThat(c.getLogstashIndexer(), is(instanceOf(Redis.class))); 77 | Redis redis = (Redis) c.getLogstashIndexer(); 78 | assertThat(redis.getHost(), is("localhost")); 79 | assertThat(redis.getPort(), is(9200)); 80 | assertThat(redis.getKey(), is("redis")); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/test/java/jenkins/plugins/logstash/LogstashBuildWrapperConversionTest.java: -------------------------------------------------------------------------------- 1 | package jenkins.plugins.logstash; 2 | 3 | import static org.hamcrest.Matchers.equalTo; 4 | import static org.hamcrest.Matchers.not; 5 | import static org.junit.Assert.assertEquals; 6 | import static org.junit.Assert.assertThat; 7 | 8 | import java.io.File; 9 | import java.io.IOException; 10 | import java.net.URL; 11 | import java.text.MessageFormat; 12 | 13 | import org.junit.Before; 14 | import org.junit.Rule; 15 | import org.junit.Test; 16 | import org.jvnet.hudson.test.JenkinsRule; 17 | 18 | import org.htmlunit.FailingHttpStatusCodeException; 19 | import org.htmlunit.HttpMethod; 20 | import org.htmlunit.WebRequest; 21 | 22 | import hudson.model.Project; 23 | 24 | public class LogstashBuildWrapperConversionTest 25 | { 26 | @Rule 27 | public JenkinsRule j; 28 | 29 | public LogstashBuildWrapperConversionTest() throws Exception 30 | { 31 | j = new JenkinsRule().withExistingHome(new File("src/test/resources/home")); 32 | } 33 | 34 | @Test 35 | public void existingJobIsConvertedAtStartup() 36 | { 37 | Project project = (Project)j.getInstance().getItem("test"); 38 | checkNoBuildWrapper(project); 39 | } 40 | 41 | @Before 42 | public void setup() 43 | { 44 | j.jenkins.setSecurityRealm(j.createDummySecurityRealm()); 45 | } 46 | 47 | @Test 48 | public void buildWrapperIsConvertedToJobPropertyWhenPostingXMLToNewJob() throws IOException 49 | { 50 | j.jenkins.setCrumbIssuer(null); 51 | String newJobName = "newJob"; 52 | URL apiURL = new URL(MessageFormat.format( 53 | "{0}createItem?name={1}", 54 | j.getURL().toString(), newJobName)); 55 | WebRequest request = new WebRequest(apiURL, HttpMethod.POST); 56 | request.setAdditionalHeader("Content-Type", "application/xml"); 57 | 58 | int result = -1; 59 | try 60 | { 61 | request.setRequestBody("\n\n" + 62 | "" + 63 | "" + 64 | "" + 65 | "" + 66 | ""); 67 | result = j.createWebClient().getPage(request).getWebResponse().getStatusCode(); 68 | } 69 | catch (FailingHttpStatusCodeException e) 70 | { 71 | result = e.getResponse().getStatusCode(); 72 | } 73 | 74 | assertEquals("Creating job should succeed.", 200, result); 75 | Project project = (Project)j.getInstance().getItem(newJobName); 76 | checkNoBuildWrapper(project); 77 | } 78 | 79 | @Test 80 | public void buildWrapperIsConvertedToJobPropertyWhenPostingXMLToExistingJob() throws IOException 81 | { 82 | j.jenkins.setCrumbIssuer(null); 83 | String newJobName = "newJob"; 84 | j.createFreeStyleProject(newJobName); 85 | URL apiURL = new URL(MessageFormat.format( 86 | "{0}job/{1}/config.xml", 87 | j.getURL().toString(), newJobName)); 88 | WebRequest request = new WebRequest(apiURL, HttpMethod.POST); 89 | request.setAdditionalHeader("Content-Type", "application/xml"); 90 | 91 | int result = -1; 92 | try 93 | { 94 | request.setRequestBody("\n\n" + 95 | "" + 96 | "" + 97 | "" + 98 | "" + 99 | ""); 100 | result = j.createWebClient().getPage(request).getWebResponse().getStatusCode(); 101 | } 102 | catch (FailingHttpStatusCodeException e) 103 | { 104 | result = e.getResponse().getStatusCode(); 105 | } 106 | 107 | assertEquals("Updating job should succeed.", 200, result); 108 | Project project = (Project)j.getInstance().getItem(newJobName); 109 | checkNoBuildWrapper(project); 110 | } 111 | 112 | private void checkNoBuildWrapper(Project project) 113 | { 114 | assertThat(project.getBuildWrappersList().get(LogstashBuildWrapper.class), equalTo(null)); 115 | assertThat(project.getProperty(LogstashJobProperty.class), not(equalTo(null))); 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/test/java/jenkins/plugins/logstash/LogstashConfigurationMigrationTest.java: -------------------------------------------------------------------------------- 1 | package jenkins.plugins.logstash; 2 | 3 | import static org.hamcrest.Matchers.equalTo; 4 | import static org.hamcrest.Matchers.is; 5 | import static org.junit.Assert.assertThat; 6 | import static org.mockito.Mockito.mockStatic; 7 | import static org.mockito.Mockito.when; 8 | 9 | import java.io.File; 10 | import java.net.MalformedURLException; 11 | import java.net.URI; 12 | import java.net.URISyntaxException; 13 | 14 | import org.hamcrest.core.IsInstanceOf; 15 | import org.junit.After; 16 | import org.junit.Before; 17 | import org.junit.Rule; 18 | import org.junit.Test; 19 | import org.junit.runner.RunWith; 20 | import org.jvnet.hudson.test.JenkinsRule; 21 | import org.mockito.Mock; 22 | import org.mockito.MockedStatic; 23 | import org.mockito.junit.MockitoJUnitRunner; 24 | 25 | import com.cloudbees.syslog.MessageFormat; 26 | 27 | import jenkins.plugins.logstash.LogstashInstallation.Descriptor; 28 | import jenkins.plugins.logstash.configuration.ElasticSearch; 29 | import jenkins.plugins.logstash.configuration.LogstashIndexer; 30 | import jenkins.plugins.logstash.configuration.RabbitMq; 31 | import jenkins.plugins.logstash.configuration.Redis; 32 | import jenkins.plugins.logstash.configuration.Syslog; 33 | import jenkins.plugins.logstash.persistence.LogstashIndexerDao.IndexerType; 34 | import jenkins.plugins.logstash.persistence.LogstashIndexerDao.SyslogFormat; 35 | 36 | @RunWith(MockitoJUnitRunner.class) 37 | public class LogstashConfigurationMigrationTest extends LogstashConfigurationTestBase 38 | { 39 | 40 | private MockedStatic mockedLogstashInstallation; 41 | 42 | @Rule 43 | public JenkinsRule j = new JenkinsRule(); 44 | 45 | @Mock 46 | Descriptor descriptor; 47 | 48 | LogstashConfiguration configuration; 49 | 50 | @Before 51 | public void setup() 52 | { 53 | configFile = new File("notExisting.xml"); 54 | mockedLogstashInstallation = mockStatic(LogstashInstallation.class); 55 | mockedLogstashInstallation.when(LogstashInstallation::getLogstashDescriptor).thenAnswer(invocationOnMock -> descriptor); 56 | when(descriptor.getHost()).thenReturn("localhost"); 57 | when(descriptor.getPort()).thenReturn(4567); 58 | when(descriptor.getKey()).thenReturn("logstash"); 59 | when(descriptor.getUsername()).thenReturn("user"); 60 | when(descriptor.getPassword()).thenReturn("pwd"); 61 | configuration = new LogstashConfigurationForTest(); 62 | } 63 | 64 | @After 65 | public void after() throws Exception { 66 | mockedLogstashInstallation.closeOnDemand(); 67 | } 68 | 69 | @Test 70 | public void NoConfigMigration() 71 | { 72 | when(descriptor.getType()).thenReturn(null); 73 | configuration.migrateData(); 74 | assertThat(configuration.isEnabled(),equalTo(false)); 75 | } 76 | 77 | @Test 78 | public void redisMigration() 79 | { 80 | when(descriptor.getType()).thenReturn(IndexerType.REDIS); 81 | configuration.migrateData(); 82 | LogstashIndexer indexer = configuration.getLogstashIndexer(); 83 | assertThat(indexer, IsInstanceOf.instanceOf(Redis.class)); 84 | assertThat(configuration.isMilliSecondTimestamps(),equalTo(false)); 85 | assertThat(configuration.isEnabled(),equalTo(true)); 86 | Redis redis = (Redis) indexer; 87 | assertThat(redis.getHost(),equalTo("localhost")); 88 | assertThat(redis.getPort(),is(4567)); 89 | assertThat(redis.getKey(), equalTo("logstash")); 90 | assertThat(redis.getPassword().getPlainText(), equalTo("pwd")); 91 | } 92 | 93 | @Test 94 | public void syslogMigrationRFC3164() 95 | { 96 | when(descriptor.getType()).thenReturn(IndexerType.SYSLOG); 97 | when(descriptor.getSyslogFormat()).thenReturn(SyslogFormat.RFC3164); 98 | configuration.migrateData(); 99 | LogstashIndexer indexer = configuration.getLogstashIndexer(); 100 | assertThat(indexer, IsInstanceOf.instanceOf(Syslog.class)); 101 | assertThat(configuration.isMilliSecondTimestamps(),equalTo(false)); 102 | assertThat(configuration.isEnabled(),equalTo(true)); 103 | Syslog syslog = (Syslog) indexer; 104 | assertThat(syslog.getHost(),equalTo("localhost")); 105 | assertThat(syslog.getPort(),is(4567)); 106 | assertThat(syslog.getMessageFormat(), equalTo(MessageFormat.RFC_3164)); 107 | } 108 | 109 | @Test 110 | public void syslogMigrationRFC5424() 111 | { 112 | when(descriptor.getType()).thenReturn(IndexerType.SYSLOG); 113 | when(descriptor.getSyslogFormat()).thenReturn(SyslogFormat.RFC5424); 114 | configuration.migrateData(); 115 | LogstashIndexer indexer = configuration.getLogstashIndexer(); 116 | assertThat(indexer, IsInstanceOf.instanceOf(Syslog.class)); 117 | assertThat(configuration.isMilliSecondTimestamps(),equalTo(false)); 118 | assertThat(configuration.isEnabled(),equalTo(true)); 119 | Syslog syslog = (Syslog) indexer; 120 | assertThat(syslog.getHost(),equalTo("localhost")); 121 | assertThat(syslog.getPort(),is(4567)); 122 | assertThat(syslog.getMessageFormat(), equalTo(MessageFormat.RFC_5424)); 123 | } 124 | 125 | @Test 126 | public void elasticSearchMigration() throws URISyntaxException, MalformedURLException 127 | { 128 | when(descriptor.getType()).thenReturn(IndexerType.ELASTICSEARCH); 129 | when(descriptor.getHost()).thenReturn("http://localhost"); 130 | configuration.migrateData(); 131 | LogstashIndexer indexer = configuration.getLogstashIndexer(); 132 | assertThat(indexer, IsInstanceOf.instanceOf(ElasticSearch.class)); 133 | assertThat(configuration.isMilliSecondTimestamps(),equalTo(false)); 134 | assertThat(configuration.isEnabled(),equalTo(true)); 135 | ElasticSearch es = (ElasticSearch) indexer; 136 | URI uri = new URI("http://localhost:4567/logstash"); 137 | assertThat(es.getUri(),equalTo(uri)); 138 | assertThat(es.getPassword().getPlainText(), equalTo("pwd")); 139 | assertThat(es.getUsername(), equalTo("user")); 140 | } 141 | 142 | @Test 143 | public void rabbitMqMigration() 144 | { 145 | when(descriptor.getType()).thenReturn(IndexerType.RABBIT_MQ); 146 | configuration.migrateData(); 147 | LogstashIndexer indexer = configuration.getLogstashIndexer(); 148 | assertThat(indexer, IsInstanceOf.instanceOf(RabbitMq.class)); 149 | assertThat(configuration.isMilliSecondTimestamps(),equalTo(false)); 150 | assertThat(configuration.isEnabled(),equalTo(true)); 151 | RabbitMq es = (RabbitMq) indexer; 152 | assertThat(es.getHost(),equalTo("localhost")); 153 | assertThat(es.getPort(),is(4567)); 154 | assertThat(es.getQueue(), equalTo("logstash")); 155 | assertThat(es.getPassword().getPlainText(), equalTo("pwd")); 156 | assertThat(es.getUsername(), equalTo("user")); 157 | } 158 | 159 | } 160 | -------------------------------------------------------------------------------- /src/test/java/jenkins/plugins/logstash/LogstashConfigurationTest.java: -------------------------------------------------------------------------------- 1 | package jenkins.plugins.logstash; 2 | 3 | import static org.hamcrest.Matchers.equalTo; 4 | import static org.hamcrest.text.MatchesPattern.matchesPattern; 5 | import static org.junit.Assert.assertThat; 6 | 7 | import java.io.File; 8 | import java.net.URL; 9 | import java.util.Date; 10 | 11 | import org.apache.commons.lang.time.FastDateFormat; 12 | import org.hamcrest.core.IsInstanceOf; 13 | import org.junit.Rule; 14 | import org.junit.Test; 15 | import org.jvnet.hudson.test.JenkinsRule; 16 | 17 | import org.htmlunit.html.HtmlForm; 18 | import org.htmlunit.html.HtmlPage; 19 | 20 | import jenkins.plugins.logstash.configuration.ElasticSearch; 21 | import jenkins.plugins.logstash.configuration.RabbitMq; 22 | import jenkins.plugins.logstash.persistence.ElasticSearchDao; 23 | import jenkins.plugins.logstash.persistence.RabbitMqDao; 24 | import jenkins.plugins.logstash.persistence.RedisDao; 25 | import jenkins.plugins.logstash.persistence.SyslogDao; 26 | 27 | public class LogstashConfigurationTest extends LogstashConfigurationTestBase 28 | { 29 | 30 | @Rule 31 | public JenkinsRule j = new JenkinsRule(); 32 | 33 | @Test 34 | public void unconfiguredWillReturnNull() 35 | { 36 | LogstashConfigurationTestBase.configFile = new File("src/test/resources/notExisting.xml"); 37 | LogstashConfiguration configuration = new LogstashConfigurationForTest(); 38 | assertThat(configuration.getIndexerInstance(), equalTo(null)); 39 | assertThat(configuration.isEnabled(), equalTo(false)); 40 | } 41 | 42 | @Test 43 | public void disabled() 44 | { 45 | LogstashConfigurationTestBase.configFile = new File("src/test/resources/disabled.xml"); 46 | LogstashConfiguration configuration = new LogstashConfigurationForTest(); 47 | assertThat(configuration.isEnabled(), equalTo(false)); 48 | } 49 | 50 | @Test 51 | public void elasticSearchIsProperlyConfigured() 52 | { 53 | LogstashConfigurationTestBase.configFile = new File("src/test/resources/elasticSearch.xml"); 54 | LogstashConfiguration configuration = new LogstashConfigurationForTest(); 55 | assertThat(configuration.getIndexerInstance(), IsInstanceOf.instanceOf(ElasticSearchDao.class)); 56 | assertThat(configuration.isEnabled(), equalTo(true)); 57 | } 58 | 59 | @Test 60 | public void rabbitMqIsProperlyConfigured() 61 | { 62 | LogstashConfigurationTestBase.configFile = new File("src/test/resources/rabbitmq.xml"); 63 | LogstashConfiguration configuration = new LogstashConfigurationForTest(); 64 | assertThat(configuration.getIndexerInstance(), IsInstanceOf.instanceOf(RabbitMqDao.class)); 65 | assertThat(configuration.isEnabled(), equalTo(true)); 66 | assertThat(configuration.getLogstashIndexer(),IsInstanceOf.instanceOf(RabbitMq.class)); 67 | } 68 | 69 | @Test 70 | public void redisIsProperlyConfigured() 71 | { 72 | LogstashConfigurationTestBase.configFile = new File("src/test/resources/redis.xml"); 73 | LogstashConfiguration configuration = new LogstashConfigurationForTest(); 74 | assertThat(configuration.getIndexerInstance(), IsInstanceOf.instanceOf(RedisDao.class)); 75 | assertThat(configuration.isEnabled(), equalTo(true)); 76 | } 77 | 78 | @Test 79 | public void syslogIsProperlyConfigured() 80 | { 81 | LogstashConfigurationTestBase.configFile = new File("src/test/resources/syslog.xml"); 82 | LogstashConfiguration configuration = new LogstashConfigurationForTest(); 83 | assertThat(configuration.getIndexerInstance(), IsInstanceOf.instanceOf(SyslogDao.class)); 84 | assertThat(configuration.isEnabled(), equalTo(true)); 85 | } 86 | 87 | @Test 88 | public void milliSecondsConfigured() 89 | { 90 | LogstashConfigurationTestBase.configFile = new File("src/test/resources/rabbitmq.xml"); 91 | LogstashConfiguration configuration = new LogstashConfigurationForTest(); 92 | assertThat(configuration.isMilliSecondTimestamps(),equalTo(true)); 93 | FastDateFormat formatter = configuration.getDateFormatter(); 94 | assertThat(formatter.format(new Date(118,2,10,22,22)), matchesPattern("2018-03-10T22:22:00.000[+-]\\d{4}")); 95 | } 96 | 97 | /** 98 | * Test whether we can open the Jenkins configuration page and save it without 99 | * changing anything i.e. plugin is disabled. (JENKINS-51793) 100 | */ 101 | @Test 102 | public void jenkinsInitialConfigurationCanBeSaved() throws Exception 103 | { 104 | HtmlPage p = j.createWebClient().goTo("configure"); 105 | HtmlForm f = p.getFormByName("config"); 106 | j.submit(f); 107 | } 108 | 109 | @Test 110 | public void programmaticConfigurationChangesActiveIndexer() throws Exception 111 | { 112 | LogstashConfigurationTestBase.configFile = new File("src/test/resources/rabbitmq.xml"); 113 | LogstashConfiguration configuration = new LogstashConfigurationForTest(); 114 | ElasticSearch es = new ElasticSearch(); 115 | es.setUri(new URL("http://localhost/key")); 116 | configuration.setLogstashIndexer(es); 117 | assertThat(configuration.getIndexerInstance(), IsInstanceOf.instanceOf(ElasticSearchDao.class)); 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/test/java/jenkins/plugins/logstash/LogstashConfigurationTestBase.java: -------------------------------------------------------------------------------- 1 | package jenkins.plugins.logstash; 2 | 3 | import java.io.File; 4 | 5 | import hudson.XmlFile; 6 | 7 | public class LogstashConfigurationTestBase 8 | { 9 | protected static File configFile; 10 | 11 | public static class LogstashConfigurationForTest extends LogstashConfiguration 12 | { 13 | 14 | @Override 15 | public synchronized void save() 16 | { 17 | } 18 | 19 | @Override 20 | protected XmlFile getConfigFile() 21 | { 22 | return new XmlFile(configFile); 23 | } 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/test/java/jenkins/plugins/logstash/LogstashConsoleLogFilterTest.java: -------------------------------------------------------------------------------- 1 | package jenkins.plugins.logstash; 2 | 3 | import static org.junit.Assert.*; 4 | import static org.mockito.Mockito.verify; 5 | import static org.mockito.Mockito.verifyNoMoreInteractions; 6 | import static org.mockito.Mockito.when; 7 | 8 | import hudson.model.AbstractBuild; 9 | import hudson.model.Project; 10 | import hudson.model.Run; 11 | 12 | import java.io.ByteArrayOutputStream; 13 | import java.io.IOException; 14 | import java.io.OutputStream; 15 | 16 | import jenkins.plugins.logstash.persistence.BuildData; 17 | 18 | import org.junit.After; 19 | import org.junit.Before; 20 | import org.junit.Test; 21 | import org.junit.runner.RunWith; 22 | import org.mockito.Mock; 23 | import org.mockito.MockedStatic; 24 | import org.mockito.Mockito; 25 | import org.mockito.junit.MockitoJUnitRunner; 26 | 27 | @RunWith(MockitoJUnitRunner.class) 28 | public class LogstashConsoleLogFilterTest { 29 | 30 | private MockedStatic mockedLogstashConfiguration; 31 | 32 | @Mock 33 | private LogstashConfiguration logstashConfiguration; 34 | 35 | // Extension of the unit under test that avoids making calls to statics or constructors 36 | static class MockLogstashConsoleLogFilter extends LogstashConsoleLogFilter { 37 | LogstashWriter writer; 38 | 39 | MockLogstashConsoleLogFilter(LogstashWriter writer) { 40 | super(); 41 | this.writer = writer; 42 | } 43 | 44 | @Override 45 | LogstashWriter getLogStashWriter(Run build, OutputStream errorStream) { 46 | // Simulate bad Writer 47 | if(writer.isConnectionBroken()) { 48 | try { 49 | errorStream.write("Mocked Constructor failure".getBytes()); 50 | } catch (IOException e) { 51 | } 52 | } 53 | return writer; 54 | } 55 | } 56 | 57 | ByteArrayOutputStream buffer; 58 | 59 | @Mock AbstractBuild mockBuild; 60 | @Mock Project mockProject; 61 | @Mock BuildData mockBuildData; 62 | @Mock LogstashWriter mockWriter; 63 | 64 | @Before 65 | public void before() throws Exception { 66 | mockedLogstashConfiguration = Mockito.mockStatic(LogstashConfiguration.class); 67 | mockedLogstashConfiguration.when(LogstashConfiguration::getInstance).thenAnswer(invocationOnMock -> logstashConfiguration); 68 | when(logstashConfiguration.isEnableGlobally()).thenReturn(false); 69 | when(logstashConfiguration.isEnabled()).thenReturn(true); 70 | 71 | when(mockWriter.isConnectionBroken()).thenReturn(false); 72 | when(mockBuild.getParent()).thenReturn(mockProject); 73 | when(mockProject.getProperty(LogstashJobProperty.class)).thenReturn(new LogstashJobProperty()); 74 | 75 | buffer = new ByteArrayOutputStream(); 76 | } 77 | 78 | @After 79 | public void after() throws Exception { 80 | verifyNoMoreInteractions(mockWriter); 81 | verifyNoMoreInteractions(mockBuildData); 82 | buffer.close(); 83 | mockedLogstashConfiguration.closeOnDemand(); 84 | } 85 | 86 | @Test 87 | public void decorateLoggerSuccess() throws Exception { 88 | MockLogstashConsoleLogFilter consoleLogFilter = new MockLogstashConsoleLogFilter(mockWriter); 89 | 90 | // Unit under test 91 | OutputStream result = consoleLogFilter.decorateLogger(mockBuild, buffer); 92 | 93 | // Verify results 94 | assertNotNull("Result was null", result); 95 | assertTrue("Result is not the right type", result instanceof LogstashOutputStream); 96 | assertSame("Result has wrong writer", mockWriter, ((LogstashOutputStream) result).getLogstashWriter()); 97 | assertEquals("Results don't match", "", buffer.toString()); 98 | verify(mockWriter).isConnectionBroken(); 99 | } 100 | 101 | @Test 102 | public void decorateLoggerSuccessLogstashNotEnabled() throws Exception { 103 | when(mockProject.getProperty(LogstashJobProperty.class)).thenReturn(null); 104 | 105 | MockLogstashConsoleLogFilter consoleLogFilter = new MockLogstashConsoleLogFilter(mockWriter); 106 | 107 | // Unit under test 108 | OutputStream result = consoleLogFilter.decorateLogger(mockBuild, buffer); 109 | 110 | // Verify results 111 | assertNotNull("Result was null", result); 112 | assertTrue("Result is not the right type", result == buffer); 113 | assertEquals("Results don't match", "", buffer.toString()); 114 | } 115 | 116 | @Test 117 | public void decorateLoggerSuccessBadWriter() throws Exception { 118 | when(mockWriter.isConnectionBroken()).thenReturn(true); 119 | 120 | MockLogstashConsoleLogFilter consoleLogFilter = new MockLogstashConsoleLogFilter(mockWriter); 121 | 122 | // Unit under test 123 | OutputStream result = consoleLogFilter.decorateLogger(mockBuild, buffer); 124 | 125 | // Verify results 126 | assertNotNull("Result was null", result); 127 | assertTrue("Result is not the right type", result instanceof LogstashOutputStream); 128 | assertSame("Result has wrong writer", mockWriter, ((LogstashOutputStream) result).getLogstashWriter()); 129 | assertEquals("Error was not written", "Mocked Constructor failure", buffer.toString()); 130 | verify(mockWriter).isConnectionBroken(); 131 | } 132 | 133 | @Test 134 | public void decorateLoggerSuccessEnabledGlobally() throws IOException, InterruptedException 135 | { 136 | when(logstashConfiguration.isEnableGlobally()).thenReturn(true); 137 | MockLogstashConsoleLogFilter buildWrapper = new MockLogstashConsoleLogFilter(mockWriter); 138 | 139 | // Unit under test 140 | OutputStream result = buildWrapper.decorateLogger(mockBuild, buffer); 141 | 142 | // Verify results 143 | assertNotNull("Result was null", result); 144 | assertTrue("Result is not the right type", result instanceof LogstashOutputStream); 145 | assertSame("Result has wrong writer", mockWriter, ((LogstashOutputStream) result).getLogstashWriter()); 146 | assertEquals("Results don't match", "", buffer.toString()); 147 | verify(mockWriter).isConnectionBroken(); 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /src/test/java/jenkins/plugins/logstash/LogstashOutputStreamTest.java: -------------------------------------------------------------------------------- 1 | package jenkins.plugins.logstash; 2 | 3 | import static org.hamcrest.core.StringContains.containsString; 4 | import static org.hamcrest.MatcherAssert.assertThat; 5 | import static org.junit.Assert.assertEquals; 6 | import static org.mockito.Mockito.*; 7 | 8 | import java.io.ByteArrayOutputStream; 9 | import java.io.OutputStream; 10 | import java.nio.charset.Charset; 11 | 12 | import org.junit.After; 13 | import org.junit.Before; 14 | import org.junit.Test; 15 | import org.junit.runner.RunWith; 16 | import org.mockito.Mock; 17 | import org.mockito.Mockito; 18 | import org.mockito.junit.MockitoJUnitRunner; 19 | 20 | @SuppressWarnings("resource") 21 | @RunWith(MockitoJUnitRunner.class) 22 | public class LogstashOutputStreamTest { 23 | // Extension of the unit under test that avoids making calls to getInstance() to get the DAO singleton 24 | static LogstashOutputStream createLogstashOutputStream(OutputStream delegate, LogstashWriter logstash) { 25 | return new LogstashOutputStream(delegate, logstash); 26 | } 27 | 28 | ByteArrayOutputStream buffer; 29 | @Mock LogstashWriter mockWriter; 30 | 31 | @Before 32 | public void before() throws Exception { 33 | buffer = new ByteArrayOutputStream(); 34 | Mockito.doNothing().when(mockWriter).write(anyString()); 35 | when(mockWriter.isConnectionBroken()).thenReturn(false); 36 | when(mockWriter.getCharset()).thenReturn(Charset.defaultCharset().toString()); 37 | } 38 | 39 | @After 40 | public void after() throws Exception { 41 | verifyNoMoreInteractions(mockWriter); 42 | buffer.close(); 43 | } 44 | 45 | @Test 46 | public void constructorSuccess() throws Exception { 47 | new LogstashOutputStream(buffer, mockWriter); 48 | 49 | // Verify results 50 | assertEquals("Results don't match", "", buffer.toString()); 51 | } 52 | 53 | @Test 54 | public void eolSuccess() throws Exception { 55 | LogstashOutputStream los = new LogstashOutputStream(buffer, mockWriter); 56 | String msg = "test"; 57 | buffer.reset(); 58 | 59 | // Unit under test 60 | los.eol(msg.getBytes(), msg.length()); 61 | 62 | // Verify results 63 | assertEquals("Results don't match", msg, buffer.toString()); 64 | verify(mockWriter).isConnectionBroken(); 65 | verify(mockWriter).write(msg); 66 | verify(mockWriter).getCharset(); 67 | } 68 | 69 | @Test 70 | public void eolSuccessConnectionBroken() throws Exception { 71 | LogstashOutputStream los = new LogstashOutputStream(buffer, mockWriter); 72 | 73 | String msg = "[logstash-plugin]: Failed to send log data to REDIS:localhost:8080.\n" + 74 | "[logstash-plugin]: No Further logs will be sent.\n" + 75 | "java.io.IOException: BOOM!"; 76 | 77 | buffer.reset(); 78 | 79 | // Unit under test 80 | los.eol(msg.getBytes(), msg.length()); 81 | 82 | // Verify results 83 | assertEquals("Results don't match", msg, buffer.toString()); 84 | 85 | // Break the dao connection after this write 86 | buffer.reset(); 87 | 88 | // Unit under test 89 | los.eol(msg.getBytes(), msg.length()); 90 | 91 | // Verify results 92 | assertEquals("Results don't match", msg, buffer.toString()); 93 | when(mockWriter.isConnectionBroken()).thenReturn(true); 94 | 95 | // Verify logs still write but on further calls are made to dao 96 | buffer.reset(); 97 | // Unit under test 98 | los.eol(msg.getBytes(), msg.length()); 99 | 100 | // Verify results 101 | for (String msgLine : msg.split("\n")) { 102 | assertThat("Results don't match", buffer.toString(), containsString(msgLine)); 103 | } 104 | 105 | //Verify calls were made to the dao logging twice, not three times. 106 | verify(mockWriter, times(2)).write(msg); 107 | verify(mockWriter, times(3)).isConnectionBroken(); 108 | verify(mockWriter, times(2)).getCharset(); 109 | } 110 | 111 | @Test 112 | public void eolSuccessNoDao() throws Exception { 113 | when(mockWriter.isConnectionBroken()).thenReturn(true); 114 | LogstashOutputStream los = new LogstashOutputStream(buffer, mockWriter); 115 | String msg = "test"; 116 | buffer.reset(); 117 | 118 | // Unit under test 119 | los.eol(msg.getBytes(), msg.length()); 120 | 121 | // Verify results 122 | assertEquals("Results don't match", msg, buffer.toString()); 123 | verify(mockWriter).isConnectionBroken(); 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /src/test/java/jenkins/plugins/logstash/PipelineTest.java: -------------------------------------------------------------------------------- 1 | package jenkins.plugins.logstash; 2 | 3 | import static org.hamcrest.Matchers.equalTo; 4 | import static org.hamcrest.Matchers.greaterThan; 5 | import static org.hamcrest.MatcherAssert.assertThat; 6 | 7 | import java.util.List; 8 | 9 | import org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition; 10 | import org.jenkinsci.plugins.workflow.job.WorkflowJob; 11 | 12 | import org.junit.Before; 13 | import org.junit.ClassRule; 14 | import org.junit.Rule; 15 | import org.junit.Test; 16 | import org.jvnet.hudson.test.BuildWatcher; 17 | import org.jvnet.hudson.test.JenkinsRule; 18 | 19 | import hudson.model.Slave; 20 | import jenkins.plugins.logstash.configuration.MemoryIndexer; 21 | import jenkins.plugins.logstash.persistence.MemoryDao; 22 | import net.sf.json.JSONObject; 23 | 24 | public class PipelineTest 25 | { 26 | 27 | @ClassRule public static BuildWatcher buildWatcher = new BuildWatcher(); 28 | 29 | @Rule public JenkinsRule j = new JenkinsRule(); 30 | 31 | private MemoryDao memoryDao; 32 | 33 | @Before 34 | public void setup() throws Exception 35 | { 36 | memoryDao = new MemoryDao(); 37 | LogstashConfiguration config = LogstashConfiguration.getInstance(); 38 | MemoryIndexer indexer = new MemoryIndexer(memoryDao); 39 | config.setLogstashIndexer(indexer); 40 | config.setEnabled(true); 41 | } 42 | 43 | @Test 44 | public void logstash() throws Exception 45 | { 46 | WorkflowJob p = j.jenkins.createProject(WorkflowJob.class, "p"); 47 | p.setDefinition(new CpsFlowDefinition("logstash {\n" + 48 | "currentBuild.result = 'SUCCESS'\n" + 49 | "echo 'Message'\n" + 50 | "}", true)); 51 | j.assertBuildStatusSuccess(p.scheduleBuild2(0).get()); 52 | List dataLines = memoryDao.getOutput(); 53 | assertThat(dataLines.size(), equalTo(1)); 54 | JSONObject firstLine = dataLines.get(0); 55 | JSONObject data = firstLine.getJSONObject("data"); 56 | assertThat(data.getString("result"),equalTo("SUCCESS")); 57 | } 58 | 59 | @Test 60 | public void logstashStageAndAgent() throws Exception 61 | { 62 | Slave slave = j.createOnlineSlave(); 63 | String agentName = slave.getNodeName(); 64 | WorkflowJob p = j.jenkins.createProject(WorkflowJob.class, "p"); 65 | p.setDefinition(new CpsFlowDefinition("stage('stage1') { " + 66 | "node('" + agentName + "') {\n" + 67 | "logstash {\n" + 68 | "currentBuild.result = 'SUCCESS'\n" + 69 | "echo 'Message'\n" + 70 | "}}}", true)); 71 | j.assertBuildStatusSuccess(p.scheduleBuild2(0).get()); 72 | List dataLines = memoryDao.getOutput(); 73 | assertThat(dataLines.size(), equalTo(1)); 74 | JSONObject firstLine = dataLines.get(0); 75 | JSONObject data = firstLine.getJSONObject("data"); 76 | assertThat(data.getString("result"),equalTo("SUCCESS")); 77 | assertThat(data.getString("stageName"),equalTo("stage1")); 78 | assertThat(data.getString("agentName"),equalTo(agentName)); 79 | } 80 | 81 | @Test 82 | public void globalLogstash() throws Exception 83 | { 84 | LogstashConfiguration config = LogstashConfiguration.getInstance(); 85 | config.setEnableGlobally(true); 86 | WorkflowJob p = j.jenkins.createProject(WorkflowJob.class, "p"); 87 | p.setDefinition(new CpsFlowDefinition( 88 | "currentBuild.result = 'SUCCESS'\n" + 89 | "echo 'Message'\n", true)); 90 | j.assertBuildStatusSuccess(p.scheduleBuild2(0).get()); 91 | List dataLines = memoryDao.getOutput(); 92 | assertThat(dataLines.size(), greaterThan(0)); 93 | JSONObject lastLine = dataLines.get(dataLines.size()-1); 94 | JSONObject data = lastLine.getJSONObject("data"); 95 | assertThat(data.getString("result"),equalTo("SUCCESS")); 96 | } 97 | 98 | @Test 99 | public void logstashSendNotifier() throws Exception 100 | { 101 | WorkflowJob p = j.jenkins.createProject(WorkflowJob.class, "p"); 102 | p.setDefinition(new CpsFlowDefinition("node {" + 103 | "echo 'Message'\n" + 104 | "currentBuild.result = 'SUCCESS'\n" + 105 | "step([$class: 'LogstashNotifier', failBuild: true, maxLines: 5])" + 106 | "}", true)); 107 | j.assertBuildStatusSuccess(p.scheduleBuild2(0).get()); 108 | List dataLines = memoryDao.getOutput(); 109 | assertThat(dataLines.size(), equalTo(1)); 110 | JSONObject firstLine = dataLines.get(0); 111 | JSONObject data = firstLine.getJSONObject("data"); 112 | assertThat(data.getString("result"),equalTo("SUCCESS")); 113 | } 114 | 115 | @Test 116 | public void logstashSend() throws Exception 117 | { 118 | WorkflowJob p = j.jenkins.createProject(WorkflowJob.class, "p"); 119 | p.setDefinition(new CpsFlowDefinition( 120 | "echo 'Message'\n" + 121 | "currentBuild.result = 'SUCCESS'\n" + 122 | "logstashSend failBuild: true, maxLines: 5" 123 | , true)); 124 | j.assertBuildStatusSuccess(p.scheduleBuild2(0).get()); 125 | List dataLines = memoryDao.getOutput(); 126 | assertThat(dataLines.size(), equalTo(1)); 127 | JSONObject firstLine = dataLines.get(0); 128 | JSONObject data = firstLine.getJSONObject("data"); 129 | assertThat(data.getString("result"),equalTo("SUCCESS")); 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /src/test/java/jenkins/plugins/logstash/configuration/ElasticSearchTest.java: -------------------------------------------------------------------------------- 1 | package jenkins.plugins.logstash.configuration; 2 | 3 | import static org.hamcrest.Matchers.is; 4 | import static org.junit.Assert.assertThat; 5 | 6 | import java.net.MalformedURLException; 7 | import java.net.URISyntaxException; 8 | import java.net.URL; 9 | 10 | import hudson.util.Secret; 11 | import org.junit.Before; 12 | import org.junit.Rule; 13 | import org.junit.Test; 14 | import org.jvnet.hudson.test.JenkinsRule; 15 | 16 | public class ElasticSearchTest 17 | { 18 | 19 | @Rule 20 | public JenkinsRule j = new JenkinsRule(); 21 | 22 | private ElasticSearch indexer; 23 | private ElasticSearch indexer2; 24 | 25 | @Before 26 | public void setup() throws MalformedURLException, URISyntaxException 27 | { 28 | URL url = new URL("http://localhost:4567/key"); 29 | indexer = new ElasticSearch(); 30 | indexer.setUri(url); 31 | indexer.setPassword(Secret.fromString("password")); 32 | indexer.setUsername("user"); 33 | indexer.setMimeType("application/json"); 34 | 35 | indexer2 = new ElasticSearch(); 36 | indexer2.setUri(url); 37 | indexer2.setPassword(Secret.fromString("password")); 38 | indexer2.setUsername("user"); 39 | indexer2.setMimeType("application/json"); 40 | } 41 | 42 | @Test 43 | public void sameSettingsAreEqual() 44 | { 45 | assertThat(indexer.equals(indexer2), is(true)); 46 | } 47 | 48 | @Test 49 | public void passwordChangeIsNotEqual() 50 | { 51 | indexer.setPassword(Secret.fromString("newPassword")); 52 | assertThat(indexer.equals(indexer2), is(false)); 53 | } 54 | 55 | @Test 56 | public void urlChangeIsNotEqual() throws MalformedURLException, URISyntaxException 57 | { 58 | indexer.setUri(new URL("https://localhost:4567/key")); 59 | assertThat(indexer.equals(indexer2), is(false)); 60 | } 61 | 62 | @Test 63 | public void usernameChangeIsNotEqual() 64 | { 65 | indexer.setUsername("newUser"); 66 | assertThat(indexer.equals(indexer2), is(false)); 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /src/test/java/jenkins/plugins/logstash/configuration/HostBasedLogstashIndexerTest.java: -------------------------------------------------------------------------------- 1 | package jenkins.plugins.logstash.configuration; 2 | 3 | import static org.hamcrest.Matchers.is; 4 | import static org.junit.Assert.assertThat; 5 | 6 | import org.junit.Before; 7 | import org.junit.Test; 8 | 9 | import jenkins.plugins.logstash.persistence.MemoryDao; 10 | 11 | public class HostBasedLogstashIndexerTest 12 | { 13 | 14 | private LogstashIndexerForTest indexer; 15 | private LogstashIndexerForTest indexer2; 16 | 17 | @Before 18 | public void setup() 19 | { 20 | indexer = new LogstashIndexerForTest("localhost", 4567); 21 | indexer2 = new LogstashIndexerForTest("localhost", 4567); 22 | } 23 | 24 | @Test 25 | public void sameSettingsAreEqual() 26 | { 27 | assertThat(indexer.equals(indexer2), is(true)); 28 | } 29 | 30 | @Test 31 | public void hostChangeIsNotEqual() 32 | { 33 | indexer.setHost("remoteHost"); 34 | assertThat(indexer.equals(indexer2), is(false)); 35 | } 36 | 37 | @Test 38 | public void portChangeIsNotEqual() 39 | { 40 | indexer.setPort(7654); 41 | assertThat(indexer.equals(indexer2), is(false)); 42 | } 43 | 44 | public static class LogstashIndexerForTest extends HostBasedLogstashIndexer 45 | { 46 | 47 | public LogstashIndexerForTest(String host, int port) 48 | { 49 | setHost(host); 50 | setPort(port); 51 | } 52 | 53 | @Override 54 | public MemoryDao createIndexerInstance() 55 | { 56 | return new MemoryDao(); 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/test/java/jenkins/plugins/logstash/configuration/MemoryIndexer.java: -------------------------------------------------------------------------------- 1 | package jenkins.plugins.logstash.configuration; 2 | 3 | import jenkins.plugins.logstash.persistence.MemoryDao; 4 | 5 | public class MemoryIndexer extends LogstashIndexer 6 | { 7 | final MemoryDao dao; 8 | 9 | public MemoryIndexer(MemoryDao dao) 10 | { 11 | this.dao = dao; 12 | } 13 | 14 | @Override 15 | protected MemoryDao createIndexerInstance() 16 | { 17 | return dao; 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/test/java/jenkins/plugins/logstash/configuration/RabbitMqTest.java: -------------------------------------------------------------------------------- 1 | package jenkins.plugins.logstash.configuration; 2 | 3 | import static org.hamcrest.Matchers.equalTo; 4 | import static org.hamcrest.Matchers.is; 5 | import static org.junit.Assert.assertThat; 6 | 7 | import java.io.File; 8 | import java.nio.charset.Charset; 9 | 10 | import hudson.util.Secret; 11 | import org.hamcrest.core.IsInstanceOf; 12 | import org.junit.Before; 13 | import org.junit.Rule; 14 | import org.junit.Test; 15 | import org.jvnet.hudson.test.JenkinsRule; 16 | 17 | import jenkins.plugins.logstash.LogstashConfiguration; 18 | import jenkins.plugins.logstash.LogstashConfigurationTestBase; 19 | import jenkins.plugins.logstash.LogstashConfigurationTestBase.LogstashConfigurationForTest; 20 | import jenkins.plugins.logstash.persistence.RabbitMqDao; 21 | 22 | public class RabbitMqTest extends LogstashConfigurationTestBase 23 | { 24 | 25 | @Rule 26 | public JenkinsRule j = new JenkinsRule(); 27 | 28 | private RabbitMq indexer; 29 | private RabbitMq indexer2; 30 | private RabbitMq indexer3; 31 | 32 | @Before 33 | public void setup() 34 | { 35 | indexer = new RabbitMq("UTF-8"); 36 | indexer.setHost("localhost"); 37 | indexer.setPort(4567); 38 | indexer.setPassword(Secret.fromString("password")); 39 | indexer.setUsername("user"); 40 | indexer.setQueue("queue"); 41 | indexer.setVirtualHost("vhost"); 42 | 43 | indexer2 = new RabbitMq("UTF-8"); 44 | indexer2.setHost("localhost"); 45 | indexer2.setPort(4567); 46 | indexer2.setPassword(Secret.fromString("password")); 47 | indexer2.setUsername("user"); 48 | indexer2.setQueue("queue"); 49 | indexer2.setVirtualHost("vhost"); 50 | 51 | indexer3 = new RabbitMq("UTF-16"); 52 | indexer3.setHost("localhost"); 53 | indexer3.setPort(4567); 54 | indexer3.setPassword(Secret.fromString("password")); 55 | indexer3.setUsername("user"); 56 | indexer3.setQueue("queue"); 57 | indexer3.setQueue("vhost"); 58 | } 59 | 60 | @Test 61 | public void sameSettingsAreEqual() 62 | { 63 | assertThat(indexer.equals(indexer2), is(true)); 64 | } 65 | 66 | @Test 67 | public void passwordChangeIsNotEqual() 68 | { 69 | indexer.setPassword(Secret.fromString("newPassword")); 70 | assertThat(indexer.equals(indexer2), is(false)); 71 | } 72 | 73 | @Test 74 | public void usernameChangeIsNotEqual() 75 | { 76 | indexer.setUsername("newUser"); 77 | assertThat(indexer.equals(indexer2), is(false)); 78 | } 79 | 80 | @Test 81 | public void queueChangeIsNotEqual() 82 | { 83 | indexer.setQueue("newQueue"); 84 | assertThat(indexer.equals(indexer2), is(false)); 85 | } 86 | 87 | @Test 88 | public void charsetChangeIsNotEqual() 89 | { 90 | assertThat(indexer.equals(indexer3), is(false)); 91 | } 92 | 93 | public void vhostChangeIsNotEqual() 94 | { 95 | indexer.setVirtualHost("newVhost"); 96 | assertThat(indexer.equals(indexer2), is(false)); 97 | } 98 | 99 | @Test 100 | public void rabbitMqBrokenCharset_returns_default_charset() 101 | { 102 | LogstashConfigurationTestBase.configFile = new File("src/test/resources/rabbitmq_brokenCharset.xml"); 103 | LogstashConfiguration configuration = new LogstashConfigurationForTest(); 104 | assertThat(configuration.getIndexerInstance(), IsInstanceOf.instanceOf(RabbitMqDao.class)); 105 | assertThat(configuration.isEnabled(), equalTo(true)); 106 | assertThat(configuration.getLogstashIndexer(),IsInstanceOf.instanceOf(RabbitMq.class)); 107 | assertThat(((RabbitMq)configuration.getLogstashIndexer()).getEffectiveCharset(),equalTo(Charset.defaultCharset())); 108 | } 109 | 110 | } 111 | -------------------------------------------------------------------------------- /src/test/java/jenkins/plugins/logstash/configuration/RedisTest.java: -------------------------------------------------------------------------------- 1 | package jenkins.plugins.logstash.configuration; 2 | 3 | import static org.hamcrest.Matchers.is; 4 | import static org.junit.Assert.assertThat; 5 | 6 | import hudson.util.Secret; 7 | import org.junit.Before; 8 | import org.junit.Rule; 9 | import org.junit.Test; 10 | import org.jvnet.hudson.test.JenkinsRule; 11 | 12 | public class RedisTest 13 | { 14 | 15 | @Rule 16 | public JenkinsRule j = new JenkinsRule(); 17 | 18 | private Redis indexer; 19 | private Redis indexer2; 20 | 21 | @Before 22 | public void setup() 23 | { 24 | indexer = new Redis(); 25 | indexer.setHost("localhost"); 26 | indexer.setPort(4567); 27 | indexer.setKey("key"); 28 | indexer.setPassword(Secret.fromString("password")); 29 | 30 | indexer2 = new Redis(); 31 | indexer2.setHost("localhost"); 32 | indexer2.setPort(4567); 33 | indexer2.setKey("key"); 34 | indexer2.setPassword(Secret.fromString("password")); 35 | } 36 | 37 | @Test 38 | public void sameSettingsAreEqual() 39 | { 40 | assertThat(indexer.equals(indexer2), is(true)); 41 | } 42 | 43 | @Test 44 | public void passwordChangeIsNotEqual() 45 | { 46 | indexer.setPassword(Secret.fromString("newPassword")); 47 | assertThat(indexer.equals(indexer2), is(false)); 48 | } 49 | 50 | @Test 51 | public void keyChangeIsNotEqual() 52 | { 53 | indexer.setKey("newKey"); 54 | assertThat(indexer.equals(indexer2), is(false)); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/test/java/jenkins/plugins/logstash/configuration/SyslogTest.java: -------------------------------------------------------------------------------- 1 | package jenkins.plugins.logstash.configuration; 2 | 3 | import static org.hamcrest.Matchers.is; 4 | import static org.junit.Assert.assertThat; 5 | 6 | import org.junit.Before; 7 | import org.junit.Test; 8 | 9 | import com.cloudbees.syslog.MessageFormat; 10 | 11 | public class SyslogTest 12 | { 13 | 14 | private Syslog indexer; 15 | private Syslog indexer2; 16 | 17 | @Before 18 | public void setup() 19 | { 20 | indexer = new Syslog(); 21 | indexer.setHost("localhost"); 22 | indexer.setPort(4567); 23 | indexer.setMessageFormat(MessageFormat.RFC_3164); 24 | 25 | indexer2 = new Syslog(); 26 | indexer2.setHost("localhost"); 27 | indexer2.setPort(4567); 28 | indexer2.setMessageFormat(MessageFormat.RFC_3164); 29 | } 30 | 31 | @Test 32 | public void sameSettingsAreEqual() 33 | { 34 | assertThat(indexer.equals(indexer2), is(true)); 35 | } 36 | 37 | @Test 38 | public void messageFormatChangeIsNotEqual() 39 | { 40 | indexer.setMessageFormat(MessageFormat.RFC_5424); 41 | assertThat(indexer.equals(indexer2), is(false)); 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/test/java/jenkins/plugins/logstash/persistence/AbstractLogstashIndexerDaoTest.java: -------------------------------------------------------------------------------- 1 | package jenkins.plugins.logstash.persistence; 2 | 3 | import static net.sf.json.test.JSONAssert.assertEquals; 4 | import static org.mockito.Mockito.when; 5 | 6 | import java.io.IOException; 7 | import java.util.ArrayList; 8 | import java.util.Arrays; 9 | 10 | import net.sf.json.JSONObject; 11 | 12 | import org.junit.After; 13 | import org.junit.Before; 14 | import org.junit.Test; 15 | import org.junit.runner.RunWith; 16 | import org.mockito.Mock; 17 | import org.mockito.MockedStatic; 18 | import org.mockito.Mockito; 19 | import org.mockito.junit.MockitoJUnitRunner; 20 | 21 | import jenkins.plugins.logstash.LogstashConfiguration; 22 | 23 | @RunWith(MockitoJUnitRunner.class) 24 | public class AbstractLogstashIndexerDaoTest { 25 | 26 | private MockedStatic mockedLogstashConfiguration; 27 | 28 | static final String EMPTY_STRING = "{\"@buildTimestamp\":\"2000-01-01\",\"data\":{},\"message\":[],\"source\":\"jenkins\",\"source_host\":\"http://localhost:8080/jenkins\",\"@version\":1}"; 29 | static final String ONE_LINE_STRING = "{\"@buildTimestamp\":\"2000-01-01\",\"data\":{},\"message\":[\"LINE 1\"],\"source\":\"jenkins\",\"source_host\":\"http://localhost:8080/jenkins\",\"@version\":1}"; 30 | static final String TWO_LINE_STRING = "{\"@buildTimestamp\":\"2000-01-01\",\"data\":{},\"message\":[\"LINE 1\", \"LINE 2\"],\"source\":\"jenkins\",\"source_host\":\"http://localhost:8080/jenkins\",\"@version\":1}"; 31 | 32 | @Mock BuildData mockBuildData; 33 | @Mock LogstashConfiguration logstashConfiguration; 34 | 35 | @Before 36 | public void before() throws Exception { 37 | mockedLogstashConfiguration = Mockito.mockStatic(LogstashConfiguration.class); 38 | mockedLogstashConfiguration.when(LogstashConfiguration::getInstance).thenAnswer(invocationOnMock -> logstashConfiguration); 39 | when(logstashConfiguration.getDateFormatter()).thenCallRealMethod(); 40 | 41 | when(mockBuildData.toJson()).thenReturn(JSONObject.fromObject("{}")); 42 | when(mockBuildData.getTimestamp()).thenReturn("2000-01-01"); 43 | } 44 | 45 | @After 46 | public void after() throws Exception { 47 | mockedLogstashConfiguration.closeOnDemand(); 48 | } 49 | 50 | @Test 51 | public void buildPayloadSuccessEmpty() throws Exception { 52 | AbstractLogstashIndexerDao dao = getInstance(); 53 | 54 | // Unit under test 55 | JSONObject result = dao.buildPayload(mockBuildData, "http://localhost:8080/jenkins", new ArrayList<>()); 56 | result.remove("@timestamp"); 57 | 58 | // Verify results 59 | assertEquals("Results don't match", JSONObject.fromObject(EMPTY_STRING), result); 60 | } 61 | 62 | @Test 63 | public void buildPayloadSuccessOneLine() throws Exception { 64 | AbstractLogstashIndexerDao dao = getInstance(); 65 | 66 | // Unit under test 67 | JSONObject result = dao.buildPayload(mockBuildData, "http://localhost:8080/jenkins", Arrays.asList("LINE 1")); 68 | result.remove("@timestamp"); 69 | 70 | // Verify results 71 | assertEquals("Results don't match", JSONObject.fromObject(ONE_LINE_STRING), result); 72 | } 73 | 74 | @Test 75 | public void buildPayloadSuccessTwoLines() throws Exception { 76 | AbstractLogstashIndexerDao dao = getInstance(); 77 | 78 | // Unit under test 79 | JSONObject result = dao.buildPayload(mockBuildData, "http://localhost:8080/jenkins", Arrays.asList("LINE 1", "LINE 2")); 80 | result.remove("@timestamp"); 81 | 82 | // Verify results 83 | assertEquals("Results don't match", JSONObject.fromObject(TWO_LINE_STRING), result); 84 | } 85 | 86 | private AbstractLogstashIndexerDao getInstance() { 87 | return new AbstractLogstashIndexerDao() { 88 | 89 | @Override 90 | public void push(String data) throws IOException {} 91 | 92 | @Override 93 | public String getDescription() 94 | { 95 | return "test"; 96 | } 97 | }; 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/test/java/jenkins/plugins/logstash/persistence/ElasticSearchSSLCertsTest.java: -------------------------------------------------------------------------------- 1 | package jenkins.plugins.logstash.persistence; 2 | 3 | import org.apache.http.HttpRequest; 4 | import org.apache.http.HttpResponse; 5 | import org.apache.http.HttpException; 6 | import org.apache.http.HttpStatus; 7 | import org.apache.http.protocol.HttpRequestHandler; 8 | import org.apache.http.protocol.HttpContext; 9 | import org.apache.http.impl.bootstrap.HttpServer; 10 | import org.apache.http.impl.bootstrap.ServerBootstrap; 11 | import org.apache.http.ssl.SSLContexts; 12 | import org.hamcrest.core.IsInstanceOf; 13 | import org.junit.Rule; 14 | import org.junit.Test; 15 | import org.junit.rules.ExpectedException; 16 | 17 | import java.io.IOException; 18 | import java.io.InputStream; 19 | import java.net.Inet4Address; 20 | import java.net.URL; 21 | import java.net.URI; 22 | import java.net.UnknownHostException; 23 | import java.security.KeyManagementException; 24 | import java.security.KeyStore; 25 | import java.security.KeyStoreException; 26 | import java.security.NoSuchAlgorithmException; 27 | import java.security.SecureRandom; 28 | import java.security.UnrecoverableKeyException; 29 | import java.security.cert.CertificateException; 30 | 31 | import javax.net.ssl.KeyManager; 32 | import javax.net.ssl.KeyManagerFactory; 33 | import javax.net.ssl.SSLContext; 34 | import javax.net.ssl.SSLException; 35 | import javax.net.ssl.SSLHandshakeException; 36 | import javax.net.ssl.TrustManager; 37 | import javax.net.ssl.TrustManagerFactory; 38 | 39 | public class ElasticSearchSSLCertsTest { 40 | @Rule 41 | public ExpectedException thrown = ExpectedException.none(); 42 | 43 | private static final SSLContext NO_SSL_CONTEXT = null; 44 | 45 | private static final char[] KEYPASS_AND_STOREPASS_VALUE = "aaaaaa".toCharArray(); 46 | private static final String JAVA_KEYSTORE = "jks"; 47 | private static final String CLIENT_KEYSTORE = "elasticsearch-sslcerts/cert.pkcs12"; 48 | 49 | @Test 50 | public void NoSSLPost_NoSSLServer_Returns200OK() throws Exception { 51 | final HttpServer server = createLocalTestServer(NO_SSL_CONTEXT); 52 | server.start(); 53 | 54 | String baseUrl = getBaseUrl(server); 55 | ElasticSearchDao dao = new ElasticSearchDao(null, new URI("http://" + baseUrl), "", ""); 56 | 57 | try { 58 | dao.push(""); 59 | } finally { 60 | server.stop(); 61 | } 62 | } 63 | 64 | @Test 65 | public void SSLPost_NoSSLServer_NoTrustStore_ThrowsSSLException() throws Exception { 66 | SSLContext sslContext = createServerSSLContext(CLIENT_KEYSTORE, KEYPASS_AND_STOREPASS_VALUE); 67 | final HttpServer server = createLocalTestServer(sslContext); 68 | server.start(); 69 | 70 | String baseUrl = getBaseUrl(server); 71 | ElasticSearchDao dao = new ElasticSearchDao(null, new URI("https://" + baseUrl), "", ""); 72 | 73 | /* 74 | The server's cert does not exist in the default trust store. When connecting to a server 75 | that presents a certificate for validation during the SSL handshake, ElasticSearchDao cannot 76 | validate it and throws an SSLHandshakeException 77 | */ 78 | try { 79 | thrown.expect(IsInstanceOf.instanceOf(SSLHandshakeException.class)); 80 | thrown.expectMessage("unable to find valid certification path to requested target"); 81 | 82 | dao.push(""); 83 | } finally { 84 | server.stop(); 85 | } 86 | } 87 | 88 | @Test 89 | public void SSLPost_SSLServer_UpdatedTrustStore_Returns200OK() throws Exception { 90 | SSLContext serverSSLContext = createServerSSLContext(CLIENT_KEYSTORE, KEYPASS_AND_STOREPASS_VALUE); 91 | final HttpServer server = createLocalTestServer(serverSSLContext); 92 | server.start(); 93 | 94 | String baseUrl = getBaseUrl(server); 95 | 96 | ElasticSearchDao dao = new ElasticSearchDao(new URI("https://" + baseUrl), "", ""); 97 | KeyStore keyStore = getStore(CLIENT_KEYSTORE, KEYPASS_AND_STOREPASS_VALUE); 98 | dao.setCustomKeyStore(keyStore, "aaaaaa"); 99 | 100 | try { 101 | dao.push(""); 102 | } finally { 103 | server.stop(); 104 | } 105 | } 106 | 107 | @Test 108 | public void SSLPost_NoSSLServer_ThrowsSSLException() throws Exception { 109 | final HttpServer server = createLocalTestServer(NO_SSL_CONTEXT); 110 | server.start(); 111 | 112 | String baseUrl = getBaseUrl(server); 113 | 114 | ElasticSearchDao dao = new ElasticSearchDao(new URI("https://" + baseUrl), "", ""); 115 | KeyStore keyStore = getStore(CLIENT_KEYSTORE, KEYPASS_AND_STOREPASS_VALUE); 116 | dao.setCustomKeyStore(keyStore, "aaaaaa"); 117 | 118 | try { 119 | thrown.expect(IsInstanceOf.instanceOf(SSLException.class)); 120 | 121 | dao.push(""); 122 | } finally { 123 | server.stop(); 124 | } 125 | } 126 | 127 | protected HttpServer createLocalTestServer(SSLContext sslContext) throws UnknownHostException { 128 | return ServerBootstrap.bootstrap() 129 | .setLocalAddress(Inet4Address.getByName("localhost")) 130 | .setSslContext(sslContext) 131 | .registerHandler("*", new HttpRequestHandler(){ 132 | @Override 133 | public void handle(final HttpRequest request, final HttpResponse response, final HttpContext context) 134 | throws HttpException, IOException { 135 | response.setStatusCode(HttpStatus.SC_OK); 136 | } 137 | }) 138 | .create(); 139 | } 140 | 141 | protected String getBaseUrl(HttpServer server) { 142 | return server.getInetAddress().getHostName() + ":" + server.getLocalPort(); 143 | } 144 | 145 | protected SSLContext createServerSSLContext(final String keyStoreFileName, 146 | final char[] password) throws CertificateException, 147 | NoSuchAlgorithmException, KeyStoreException, IOException, UnrecoverableKeyException, 148 | KeyManagementException { 149 | KeyStore serverKeyStore = getStore(keyStoreFileName, password); 150 | KeyManager[] serverKeyManagers = getKeyManagers(serverKeyStore, password); 151 | 152 | SSLContext sslContext = SSLContexts.custom().useProtocol("TLS").build(); 153 | // We don't install any trust managers in the server, hence null 154 | sslContext.init(serverKeyManagers, null, new SecureRandom()); 155 | 156 | return sslContext; 157 | } 158 | 159 | protected KeyStore getStore(final String storeFileName, final char[] password) throws 160 | KeyStoreException, IOException, CertificateException, NoSuchAlgorithmException { 161 | final KeyStore store = KeyStore.getInstance(JAVA_KEYSTORE); 162 | URL url = getClass().getClassLoader().getResource(storeFileName); 163 | try (InputStream inputStream = url.openStream()) { 164 | store.load(inputStream, password); 165 | } 166 | 167 | return store; 168 | } 169 | 170 | protected KeyManager[] getKeyManagers(KeyStore store, final char[] password) throws 171 | NoSuchAlgorithmException, UnrecoverableKeyException, KeyStoreException { 172 | KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance( 173 | KeyManagerFactory.getDefaultAlgorithm()); 174 | keyManagerFactory.init(store, password); 175 | 176 | return keyManagerFactory.getKeyManagers(); 177 | } 178 | 179 | protected TrustManager[] getTrustManagers(KeyStore store) throws NoSuchAlgorithmException, 180 | KeyStoreException { 181 | TrustManagerFactory trustManagerFactory = 182 | TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); 183 | trustManagerFactory.init(store); 184 | 185 | return trustManagerFactory.getTrustManagers(); 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /src/test/java/jenkins/plugins/logstash/persistence/LogstashDaoTest.java: -------------------------------------------------------------------------------- 1 | package jenkins.plugins.logstash.persistence; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | 5 | import org.junit.Rule; 6 | import org.junit.Test; 7 | import org.junit.rules.ExpectedException; 8 | import org.junit.runner.RunWith; 9 | import org.mockito.junit.MockitoJUnitRunner; 10 | 11 | @RunWith(MockitoJUnitRunner.class) 12 | public class LogstashDaoTest { 13 | 14 | @Rule 15 | public ExpectedException thrown = ExpectedException.none(); 16 | 17 | LogstashDao dao; 18 | 19 | LogstashDao createDao(String host, int port) { 20 | return new LogstashDao(host, port); 21 | } 22 | 23 | @Test 24 | public void constructorFailNullHost() throws Exception { 25 | thrown.expect(IllegalArgumentException.class); 26 | thrown.expectMessage("host name is required"); 27 | createDao(null, 9000); 28 | } 29 | 30 | @Test 31 | public void constructorFailEmptyHost() throws Exception { 32 | thrown.expect(IllegalArgumentException.class); 33 | thrown.expectMessage("host name is required"); 34 | createDao(" ", 9000); 35 | } 36 | 37 | @Test 38 | public void constructorSuccess() throws Exception { 39 | // Unit under test 40 | dao = createDao("localhost", 5672); 41 | 42 | // Verify results 43 | assertEquals("Wrong host name", "localhost", dao.getHost()); 44 | assertEquals("Wrong port", 5672, dao.getPort()); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/test/java/jenkins/plugins/logstash/persistence/MemoryDao.java: -------------------------------------------------------------------------------- 1 | package jenkins.plugins.logstash.persistence; 2 | 3 | import java.io.IOException; 4 | import java.util.ArrayList; 5 | import java.util.List; 6 | 7 | import jenkins.plugins.logstash.persistence.AbstractLogstashIndexerDao; 8 | import net.sf.json.JSONObject; 9 | 10 | public class MemoryDao extends AbstractLogstashIndexerDao 11 | { 12 | final List output = new ArrayList<>(); 13 | 14 | public MemoryDao() 15 | { 16 | super(); 17 | } 18 | 19 | @Override 20 | public void push(String data) throws IOException 21 | { 22 | JSONObject json = JSONObject.fromObject(data); 23 | output.add(json); 24 | } 25 | 26 | public List getOutput() 27 | { 28 | return output; 29 | } 30 | 31 | @Override 32 | public String getDescription() 33 | { 34 | return "test"; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/test/java/jenkins/plugins/logstash/persistence/RedisDaoTest.java: -------------------------------------------------------------------------------- 1 | package jenkins.plugins.logstash.persistence; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | import static org.mockito.Mockito.doThrow; 5 | import static org.mockito.Mockito.verify; 6 | import static org.mockito.Mockito.verifyNoMoreInteractions; 7 | import static org.mockito.Mockito.when; 8 | 9 | import org.apache.commons.lang.exception.ExceptionUtils; 10 | import org.junit.After; 11 | import org.junit.Before; 12 | import org.junit.Test; 13 | import org.junit.runner.RunWith; 14 | import org.mockito.Mock; 15 | import org.mockito.junit.MockitoJUnitRunner; 16 | 17 | import redis.clients.jedis.Jedis; 18 | import redis.clients.jedis.JedisPool; 19 | import redis.clients.jedis.exceptions.JedisConnectionException; 20 | 21 | import java.io.IOException; 22 | 23 | @RunWith(MockitoJUnitRunner.class) 24 | public class RedisDaoTest { 25 | RedisDao dao; 26 | @Mock JedisPool mockPool; 27 | @Mock Jedis mockJedis; 28 | 29 | RedisDao createDao(String host, int port, String key, String username, String password) { 30 | return new RedisDao(mockPool, host, port, key, password); 31 | } 32 | 33 | @Before 34 | public void before() throws Exception { 35 | int port = (int) (Math.random() * 1000); 36 | dao = createDao("localhost", port, "logstash", "username", "password"); 37 | 38 | when(mockPool.getResource()).thenReturn(mockJedis); 39 | } 40 | 41 | @After 42 | public void after() throws Exception { 43 | verifyNoMoreInteractions(mockPool); 44 | verifyNoMoreInteractions(mockJedis); 45 | } 46 | 47 | @Test(expected = IllegalArgumentException.class) 48 | public void constructorFailNullHost() throws Exception { 49 | try { 50 | createDao(null, 6379, "logstash", "username", "password"); 51 | } catch (IllegalArgumentException e) { 52 | assertEquals("Wrong error message was thrown", "host name is required", e.getMessage()); 53 | throw e; 54 | } 55 | } 56 | 57 | @Test(expected = IllegalArgumentException.class) 58 | public void constructorFailEmptyHost() throws Exception { 59 | try { 60 | createDao(" ", 6379, "logstash", "username", "password"); 61 | } catch (IllegalArgumentException e) { 62 | assertEquals("Wrong error message was thrown", "host name is required", e.getMessage()); 63 | throw e; 64 | } 65 | } 66 | 67 | @Test(expected = IllegalArgumentException.class) 68 | public void constructorFailNullKey() throws Exception { 69 | try { 70 | createDao("localhost", 6379, null, "username", "password"); 71 | } catch (IllegalArgumentException e) { 72 | assertEquals("Wrong error message was thrown", "redis key is required", e.getMessage()); 73 | throw e; 74 | } 75 | } 76 | 77 | @Test(expected = IllegalArgumentException.class) 78 | public void constructorFailEmptyKey() throws Exception { 79 | try { 80 | createDao("localhost", 6379, " ", "username", "password"); 81 | } catch (IllegalArgumentException e) { 82 | assertEquals("Wrong error message was thrown", "redis key is required", e.getMessage()); 83 | throw e; 84 | } 85 | } 86 | 87 | @Test 88 | public void constructorSuccess() throws Exception { 89 | // Unit under test 90 | dao = createDao("localhost", 6379, "logstash", "username", "password"); 91 | 92 | // Verify results 93 | assertEquals("Wrong host name", "localhost", dao.getHost()); 94 | assertEquals("Wrong port", 6379, dao.getPort()); 95 | assertEquals("Wrong key", "logstash", dao.getKey()); 96 | assertEquals("Wrong password", "password", dao.getPassword()); 97 | } 98 | 99 | @Test(expected = IOException.class) 100 | public void pushFailUnauthorized() throws Exception { 101 | // Initialize mocks 102 | when(mockJedis.auth("password")).thenThrow(new JedisConnectionException("Unauthorized")); 103 | 104 | // Unit under test 105 | try { 106 | dao.push(""); 107 | } catch (IOException e) { 108 | // Verify results 109 | verify(mockPool).getResource(); 110 | verify(mockPool).returnBrokenResource(mockJedis); 111 | verify(mockJedis).auth("password"); 112 | assertEquals("wrong error message", 113 | "IOException: redis.clients.jedis.exceptions.JedisConnectionException: Unauthorized", ExceptionUtils.getMessage(e)); 114 | throw e; 115 | } 116 | } 117 | 118 | @Test(expected = IOException.class) 119 | public void pushFailCantConnect() throws Exception { 120 | // Initialize mocks 121 | doThrow(new JedisConnectionException("Connection refused")).when(mockJedis).connect(); 122 | 123 | // Unit under test 124 | try { 125 | dao.push(""); 126 | } catch (IOException e) { 127 | // Verify results 128 | verify(mockPool).getResource(); 129 | verify(mockPool).returnBrokenResource(mockJedis); 130 | verify(mockJedis).auth("password"); 131 | verify(mockJedis).connect(); 132 | assertEquals("wrong error message", 133 | "IOException: redis.clients.jedis.exceptions.JedisConnectionException: Connection refused", ExceptionUtils.getMessage(e)); 134 | throw e; 135 | } 136 | } 137 | 138 | @Test(expected = IOException.class) 139 | public void pushFailCantWrite() throws Exception { 140 | String json = "{ 'foo': 'bar' }"; 141 | 142 | // Initialize mocks 143 | when(mockJedis.rpush("logstash", json)).thenThrow(new JedisConnectionException("Push failed")); 144 | 145 | try { 146 | // Unit under test 147 | dao.push(json); 148 | } catch (IOException e) { 149 | // Verify results 150 | verify(mockPool).getResource(); 151 | verify(mockPool).returnBrokenResource(mockJedis); 152 | verify(mockJedis).auth("password"); 153 | verify(mockJedis).connect(); 154 | verify(mockJedis).rpush("logstash", json); 155 | assertEquals("wrong error message", 156 | "IOException: redis.clients.jedis.exceptions.JedisConnectionException: Push failed", ExceptionUtils.getMessage(e)); 157 | throw e; 158 | } 159 | } 160 | 161 | @Test 162 | public void pushSuccess() throws Exception { 163 | String json = "{ 'foo': 'bar' }"; 164 | 165 | // Initialize mocks 166 | when(mockJedis.rpush("logstash", json)).thenReturn(1L); 167 | 168 | // Unit under test 169 | dao.push(json); 170 | 171 | // Verify results 172 | verify(mockPool).getResource(); 173 | verify(mockPool).returnResource(mockJedis); 174 | verify(mockJedis).auth("password"); 175 | verify(mockJedis).connect(); 176 | verify(mockJedis).rpush("logstash", json); 177 | verify(mockJedis).disconnect(); 178 | } 179 | 180 | @Test 181 | public void pushSuccessNoAuth() throws Exception { 182 | String json = "{ 'foo': 'bar' }"; 183 | 184 | // Initialize mocks 185 | dao = createDao("localhost", 6379, "logstash", null, null); 186 | when(mockJedis.rpush("logstash", json)).thenReturn(1L); 187 | 188 | // Unit under test 189 | dao.push(json); 190 | 191 | // Verify results 192 | verify(mockPool).getResource(); 193 | verify(mockPool).returnResource(mockJedis); 194 | verify(mockJedis).connect(); 195 | verify(mockJedis).rpush("logstash", json); 196 | verify(mockJedis).disconnect(); 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /src/test/java/jenkins/plugins/logstash/persistence/SyslogDaoTest.java: -------------------------------------------------------------------------------- 1 | package jenkins.plugins.logstash.persistence; 2 | 3 | import static org.mockito.Mockito.*; 4 | 5 | import org.junit.Before; 6 | import org.junit.Test; 7 | import org.junit.runner.RunWith; 8 | 9 | import org.mockito.Mock; 10 | import org.mockito.junit.MockitoJUnitRunner; 11 | 12 | import com.cloudbees.syslog.Facility; 13 | import com.cloudbees.syslog.MessageFormat; 14 | import com.cloudbees.syslog.Severity; 15 | import com.cloudbees.syslog.sender.UdpSyslogMessageSender; 16 | 17 | @RunWith(MockitoJUnitRunner.class) 18 | public class SyslogDaoTest { 19 | SyslogDao dao; 20 | String data = "{ 'junit': 'SyslogDaoTest' }"; 21 | String host = "localhost"; 22 | String appname = "jenkins:"; 23 | int port = 514; 24 | @Mock UdpSyslogMessageSender mockUdpSyslogMessageSender; 25 | 26 | @Before 27 | public void before() throws Exception { 28 | dao = createDao(host, port, null, null, null); 29 | dao.push(data); 30 | } 31 | 32 | // Test the Message content. 33 | @Test 34 | public void ceeMessageFormat() throws Exception { 35 | verify(mockUdpSyslogMessageSender, times(1)).sendMessage(" @cee: " + data); 36 | } 37 | 38 | // Test the MessageSender configuration. 39 | @Test 40 | public void syslogConfig() throws Exception { 41 | verify(mockUdpSyslogMessageSender, times(1)).setDefaultMessageHostname(host); 42 | verify(mockUdpSyslogMessageSender, times(1)).setDefaultAppName(appname); 43 | verify(mockUdpSyslogMessageSender, times(1)).setSyslogServerHostname(host); 44 | verify(mockUdpSyslogMessageSender, times(1)).setSyslogServerPort(port); 45 | verify(mockUdpSyslogMessageSender, times(1)).setDefaultFacility(Facility.USER); 46 | verify(mockUdpSyslogMessageSender, times(1)).setDefaultSeverity(Severity.INFORMATIONAL); 47 | verify(mockUdpSyslogMessageSender, times(1)).setMessageFormat(MessageFormat.RFC_3164); 48 | } 49 | 50 | SyslogDao createDao(String host, int port, String key, String username, String password) { 51 | return new SyslogDao(mockUdpSyslogMessageSender, host, port); 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /src/test/java/jenkins/plugins/logstash/persistence/SyslogDaoTestIT.java: -------------------------------------------------------------------------------- 1 | package jenkins.plugins.logstash.persistence; 2 | 3 | import com.cloudbees.syslog.MessageFormat; 4 | import com.cloudbees.syslog.sender.UdpSyslogMessageSender; 5 | 6 | import java.io.FileNotFoundException; 7 | import java.io.PrintWriter; 8 | import java.nio.file.Files; 9 | import java.nio.file.Paths; 10 | import java.util.concurrent.Callable; 11 | import java.util.concurrent.TimeUnit; 12 | import net.sf.json.JSONObject; 13 | import org.junit.Before; 14 | import org.junit.Test; 15 | 16 | import static org.awaitility.Awaitility.*; 17 | import static org.junit.Assert.assertEquals; 18 | import static org.junit.Assert.fail; 19 | 20 | public class SyslogDaoTestIT{ 21 | SyslogDao dao; 22 | String data = "{ 'junit': 'SyslogDaoTest' }"; 23 | String host = "localhost"; 24 | String appname = "jenkins:"; 25 | String logfile = "target/logs/syslog-test.log"; 26 | String logmessage = ""; 27 | String logcontent = ""; 28 | int port = 514; 29 | int logstashTimeout = 15; 30 | UdpSyslogMessageSender udpSyslogMessageSender = new UdpSyslogMessageSender(); 31 | 32 | @Before 33 | public void before() throws Exception { 34 | dao = createDao(host, port, null, null, null); 35 | } 36 | 37 | // Send a real Syslog message to Logstash. 38 | @Test 39 | public void syslogSendRFC3164UDP() throws Exception { 40 | // Clean up the the logstash log file 41 | try (PrintWriter writer = new PrintWriter(logfile)) { 42 | } catch (FileNotFoundException e) { 43 | fail("Unable to clean up the logstash log file: " + e.getMessage()); 44 | } 45 | 46 | // Send the syslog message 47 | dao.setMessageFormat(MessageFormat.RFC_3164); 48 | dao.push(data); 49 | 50 | // Await for logstash to process the message 51 | try { 52 | await().atMost(logstashTimeout, TimeUnit.SECONDS).until(logfileIsNotEmpty()); 53 | } 54 | catch (Exception e) { 55 | fail("Unable to find any logstash generated data within the logfile " + logfile + ": " + e.getMessage()); 56 | } 57 | 58 | // Parse the logstash generated logfile 59 | try { 60 | JSONObject logjson = JSONObject.fromObject(logcontent); 61 | logmessage = logjson.get("message").toString(); 62 | } 63 | catch (Exception e) { 64 | fail("Unable to parse the logstash generated logfile content: " + e.getMessage()); 65 | } 66 | 67 | assertEquals(" @cee: " + data, logmessage); 68 | 69 | } 70 | 71 | private Callable logfileIsNotEmpty() { 72 | return new Callable() { 73 | @Override 74 | public Boolean call() throws Exception { 75 | // Check the content of logfile generated by logstash 76 | logcontent = new String(Files.readAllBytes(Paths.get(logfile))); 77 | // The condition that must be fulfilled 78 | return logcontent.length() > 0; 79 | } 80 | }; 81 | } 82 | 83 | SyslogDao createDao(String host, int port, String key, String username, String password) { 84 | return new SyslogDao(udpSyslogMessageSender, host, port); 85 | } 86 | 87 | } 88 | -------------------------------------------------------------------------------- /src/test/resources/buildWrapperConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | false 7 | 8 | true 9 | false 10 | false 11 | false 12 | 13 | false 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/test/resources/disabled.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | localhost 4 | 5672 5 | logstash 6 | 7 | {AQAAABAAAAAQ5wlElDfWGri9VuaMh0MCBZWs1fjL31zvnxrkszfW5pA=} 8 | 9 | false 10 | true 11 | true 12 | 13 | -------------------------------------------------------------------------------- /src/test/resources/elasticSearch.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | http://localhost:9300/logstash 5 | 6 | {AQAAABAAAAAQdB13xsx5UlCScGiBcVUOL0GqWYwAU5syhW9iBb6tG+4=} 7 | 8 | true 9 | 10 | -------------------------------------------------------------------------------- /src/test/resources/elasticsearch-sslcerts/cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIFADCCAuigAwIBAgIJALQe5k3cp+24MA0GCSqGSIb3DQEBCwUAMBQxEjAQBgNV 3 | BAMMCWxvY2FsaG9zdDAgFw0xODA1MTQxODU5MjBaGA8zMDE3MDkxNDE4NTkyMFow 4 | FDESMBAGA1UEAwwJbG9jYWxob3N0MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC 5 | CgKCAgEAuT8eBg20u8myQZrcZ1UrZglQOcBty5vSvoBDPf4MyjM/Ix+KpXt7TjW3 6 | R/oyOnj3HWXFW1Vu+yCRfQNpIRpokZedAychn2pUqHKBf+8RHCo8gqGeai6Nkv3Y 7 | q0ftdsAioLsXLdKhNJ9sr5hBmshI0QgIkyp0Wl+0Dr4yvweWzOvoNuUgKvM7YYqB 8 | iDRngepIbM+6FwYStLivbHYnK1WgDnBpDHpsgmbuZOKCelXDkoY9GzFSMekua9oy 9 | ikEu/oyW+vzSt+ayTtd//jG4ACRXSgLabt4tCSdEfn7gpxdCOuHY208i5rc/CQgF 10 | 9Fi2JGvNcIVyjOfPX2vg2NC49Bq+iIumRLJb5JcEsYShiWGkDgbJTRgU1nbiCN96 11 | 8+ZhZMOVzpIV9bKyY5AwlwJE4HufjIfKodm4+VwXHG/gXxCfnn+8Ekv+cmJP6XPk 12 | bbZf4X3Na3IvVlky6n5+GqFCjBafuQALvQ2x4oIMbNFBtcoXrrNxJSBTlLHzNrkz 13 | vj42nkAg+a7VLB985Ow25BGkP2JsPqdJUuArlf2vJF2CHI+ukwD6OAxHRo3YFosB 14 | sG/P3/g0HTlBch6BnVuItTAHNrLCSO7W0BlUIdHriA2tYcwnhpFNxDhVLTsNM5+O 15 | BbXeyHusd7Il+mm1yFn3DhSUphuT2q1ScwoVWF6VscvKY8Fgx8kCAwEAAaNTMFEw 16 | HQYDVR0OBBYEFE5JEGuMlvzKoN6XoomOw4c2lE8NMB8GA1UdIwQYMBaAFE5JEGuM 17 | lvzKoN6XoomOw4c2lE8NMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQAD 18 | ggIBADb5kg0ptfx3SffLAuvYisZ6Ud8n1T8+aVPMpQ9cDsK1T3/pB5JRW202ry50 19 | P7HGyRyRD09NTKu2xp57LRN/gViSV4wyJdFjK8UkPfKE1gvZw8xBqyzvZ6+1dsqD 20 | tpMY3korLnwHRSd+sm/YTLsSZ8+KXSiD3G79oXjOciG/6cEVCzGmOrmSOkw1J1Sc 21 | JviszhZF01qD6hWewc1UIkt+OkCXPTBi7zkstic7FT1MiTMBRdQU9XG2sfPoJju1 22 | FMKevaIVDW3A36YxomY+yl72OLT47Eb24AxPKSH/pYhNUt3XgT0Rm26bFY58p609 23 | SuljR8urp6hRgNjKFoHhoe69oYuAHZfXquQe/7rMIYrXmMqZUH+m8UnoZRehNxhs 24 | xIpjje8foO74qARKaU3ZWTmahUoCRdbd7C0aTolZesCOlfWQtJe+P3W4Is6EfYm7 25 | W8ahkfthxVJ31nK+etXbxlEawT7UUadyRwedz6fn9qYFtbu5+oFZvqDpmgVPVVH5 26 | 4t0KRrcP3T8NkHBGOEUf8zdjlq6yZMwi+0WPBB2zq8IQLY/90EMt2hrlRPxWCR6m 27 | 55cdl/VdA9Li16h9RC9NJFIAsTiJjOkuHmvMgYRBuf+wBbYWwIw0j9W7EB+eQpJa 28 | amlYBRJJSfjFfOj8Na2CwTC6w3s2NU2XU0Tye7siIFwBVWde 29 | -----END CERTIFICATE----- 30 | -------------------------------------------------------------------------------- /src/test/resources/elasticsearch-sslcerts/cert.pkcs12: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/logstash-plugin/a10e30d246c9b51919c4e741d915f23fc7353367/src/test/resources/elasticsearch-sslcerts/cert.pkcs12 -------------------------------------------------------------------------------- /src/test/resources/elasticsearch-sslcerts/key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIJQgIBADANBgkqhkiG9w0BAQEFAASCCSwwggkoAgEAAoICAQC5Px4GDbS7ybJB 3 | mtxnVStmCVA5wG3Lm9K+gEM9/gzKMz8jH4qle3tONbdH+jI6ePcdZcVbVW77IJF9 4 | A2khGmiRl50DJyGfalSocoF/7xEcKjyCoZ5qLo2S/dirR+12wCKguxct0qE0n2yv 5 | mEGayEjRCAiTKnRaX7QOvjK/B5bM6+g25SAq8zthioGINGeB6khsz7oXBhK0uK9s 6 | dicrVaAOcGkMemyCZu5k4oJ6VcOShj0bMVIx6S5r2jKKQS7+jJb6/NK35rJO13/+ 7 | MbgAJFdKAtpu3i0JJ0R+fuCnF0I64djbTyLmtz8JCAX0WLYka81whXKM589fa+DY 8 | 0Lj0Gr6Ii6ZEslvklwSxhKGJYaQOBslNGBTWduII33rz5mFkw5XOkhX1srJjkDCX 9 | AkTge5+Mh8qh2bj5XBccb+BfEJ+ef7wSS/5yYk/pc+Rttl/hfc1rci9WWTLqfn4a 10 | oUKMFp+5AAu9DbHiggxs0UG1yheus3ElIFOUsfM2uTO+PjaeQCD5rtUsH3zk7Dbk 11 | EaQ/Ymw+p0lS4CuV/a8kXYIcj66TAPo4DEdGjdgWiwGwb8/f+DQdOUFyHoGdW4i1 12 | MAc2ssJI7tbQGVQh0euIDa1hzCeGkU3EOFUtOw0zn44Ftd7Ie6x3siX6abXIWfcO 13 | FJSmG5ParVJzChVYXpWxy8pjwWDHyQIDAQABAoICAGgvenhXHw36u1mwekNXoGfr 14 | 1wYUFuxLwDRKOQdVqeXS+rCLXdQCZfAvv9woeDVwsTMEeQIMQ7XTtF/GGkt26dbw 15 | mWsbiAp7qA9xDypfz5SyoIOr2EfJ1PAV+tUiSAjguNkDQF8SNn49J8h+bm9aM7H0 16 | vbXfS79EUiV0jV/pKcQo7dpp9TSSxhPu7TkomAp2NLOWBQhL7xtbP7ZVoWP67WER 17 | kdNlyz9wGqX8yvt43ty6yQVRMPRHPEeGWaRuUrrxYTzaEr1Bb4luJFtXpS/XQIys 18 | nCN8q4GZZylpvHndV+979BYsJOnzp+0xaP/rIM3NH6coNqvO+E+ngbcPdOY9YGi/ 19 | jSc85mbtms6HXaVP3vzJRuu5qclZt3WiHagqN0OOe8BVDKaZnNDsfJ7AFDvl5otg 20 | CghOIONm6U6oH5CpPCf2zNeGZFTW90HfxnKPXb55TnKjv4utgwdPo3WuFxL7MRI8 21 | AGV2XhYpwBtLfcneN3aaH7ErBbkB36kFUYbXRD2Vobn9MDc5m1tGI7lk7C1mVT8G 22 | lTvHm1HoVt0RfWUNssYbgTqzQMvc/C4cnmxHB+qyNd3LMubrYRTAdAz7SsV0EBTP 23 | W80jG3BJfhsDK+SrYUX3FSX9FWkZ51p+uAfYRhDIfn6Ezsofjr/JH9L3r9nL3IvO 24 | T5bwmMBVcIU+aMptFP9tAoIBAQDuBQGcULS7jrhwDhZsZ8AnycPyZqUGqb9DPbK8 25 | Zf8BJHUpFWNwfcqENyeFF6OCz+8ryM63+ROWXlvk7vYhAASWRf8mVGy20HWeMLGD 26 | fvW32qKxaRuOexw9y2UrYP0pndnDFEO1VYmjTEnd8Ya9S6urW7DF3DObfbrfuM3i 27 | cJZZirgpiOHAqYdyEeLKPULkhrwjf3uQkUIlUfXkWgCG19128brUQs64/sumgoQW 28 | O/imQwTnQ+XN7bH8HY4mCwLlZZaP5/IcAp9YGVaVoBkRoeneQwgHwF+UoIKbJWjP 29 | 1/B+tOjhuZVOfA9+LRGscdlbdQT0T02+5v1aTX9RKW/3nxmjAoIBAQDHPYxxYo3c 30 | 72JkXsxeiFEepAtMt4cSTHmwcP7n6FdHuPw5rS41X9tfhEIcIskYGpTOOmZDBEOl 31 | yCj06gdJCF0yGPEc9KRLJNLADRQJ0Lt+2Vf3LR9ijZmL54Fk36CCeoY5vyE7DejC 32 | QjkqIfBcY4UPHElntHpEfsjN98m+WxY63fZ2ZhNrdIwsqW3RId0TAm2MQMkEuVQB 33 | 3UBq7edFEDfhnny6ssofNK+r6LPBByZ5PedsnwN9GsxEf+b8fCxA81C3NGaiOcSB 34 | kcOq26kQ79NUTlrbAuZ6SJKcHPIocjK5yhDEZhiQ1uvtyYbk7MoEF7KCtePxeDvt 35 | h6qUQcfjIAejAoIBAAQ1lHS2PKwAdySMKztZjl1lxRBZXlvCzr4arjmEuEDF60iB 36 | t1N69gTmkM1awKqEkN8+WuGIBx+mpYtj6nhk7q/VpxB/d7i38QyOIeWIbkIFHNF0 37 | YWdgp/wzx6M/wNpmjz5S8muXiqqXo8rIBbD9UJjkMzkcjtEWJSLlusZhZVdKC724 38 | TP3CQcHzrQYUlUVDWLpr+7xvxTKxw155dP7tfF4pIju0vsEoyGM2da2K+/e8wa5x 39 | VfQRWw9xWl0z3qY96K681FxIc5b0Q8K7pRXZvjAPEWpJracMO6MQw0zBreAfxOqR 40 | BKHxsbhyhZh+HvDuHdtQN7jDjwF002aVDATeDG8CggEAPno/eyLDh5+NhBnyDkbC 41 | 4U/htznp4kQW+MxGMuS50eThOxjiX5xih05LCNWjp6pWNWE8xhElQWxF458rj6xO 42 | xTHrEgBpMKMob07qlLVexkVTf1AoAliS6Ls86SvzAld3lc4oloMjFR99G4gu/lm6 43 | 1OUy45FFsgrr3yKvocCgdO87pR8XQd/3QN4UcLskvxxIy+1Y2+FdxYPi+mUF27Nd 44 | QqPXSxhbp3N3DvpWu9YJK4XM91PZ3TY1Ddnpzg0GN4vVKM4GzSI+S78w03g0SPpb 45 | k5lKhlkfewMc7lVpXmOJIpeJsHyvgWPT8en7Ifha1PO1Z6WwB/2DFVYwmHMUQlW9 46 | 7wKCAQEAkOgzxfmmH1ZQ/L1/GQCphUet2vc37ThAzJ7JkjEcmefrQfWY3zOPd9Xl 47 | DD4ti5A9lUBxwdNsE9ksJEyWHnotj4G47bd5f0rBus0NlHaJ17GTphkHtWGqDehK 48 | cQ/FJOwiSlwuAj3SKAtEWFlEuFFFyV3xmMMRRPR/1CBMDjCxC5fBDzC5ppNNinbQ 49 | 6S59a1n2ckTKQoKuIKuXCBCjwrlBpK7CmS2O66obnT2zfGYxngrjTJht/xwhxjwY 50 | CzqzXxAgl1jXarsTOEcQnLu+CFpG4vOSfZIYvQoMXz60aTh3OC8cTVqxDJDD+Ugf 51 | /1qErn7pZCzi72c6tciVjrAywALkUQ== 52 | -----END PRIVATE KEY----- 53 | -------------------------------------------------------------------------------- /src/test/resources/elasticsearch-sslcerts/keystore.ks: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/logstash-plugin/a10e30d246c9b51919c4e741d915f23fc7353367/src/test/resources/elasticsearch-sslcerts/keystore.ks -------------------------------------------------------------------------------- /src/test/resources/elasticsearch-sslcerts/truststore.ks: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/logstash-plugin/a10e30d246c9b51919c4e741d915f23fc7353367/src/test/resources/elasticsearch-sslcerts/truststore.ks -------------------------------------------------------------------------------- /src/test/resources/home/jobs/test/config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | false 6 | 7 | true 8 | false 9 | false 10 | false 11 | 12 | false 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/test/resources/jcasc/elasticSearch.yaml: -------------------------------------------------------------------------------- 1 | jenkins: 2 | agentProtocols: 3 | - "JNLP4-connect" 4 | - "Ping" 5 | disableRememberMe: false 6 | mode: NORMAL 7 | numExecutors: 2 8 | primaryView: 9 | all: 10 | name: "all" 11 | quietPeriod: 5 12 | scmCheckoutRetryCount: 0 13 | slaveAgentPort: 0 14 | views: 15 | - all: 16 | name: "all" 17 | unclassified: 18 | logstashConfiguration: 19 | enableGlobally: true 20 | enabled: true 21 | logstashIndexer: 22 | elasticSearch: 23 | mimeType: "application/json" 24 | uri: "http://localhost:9200/jenkins/test" 25 | username: "es" 26 | milliSecondTimestamps: true 27 | -------------------------------------------------------------------------------- /src/test/resources/jcasc/logstash.yaml: -------------------------------------------------------------------------------- 1 | jenkins: 2 | agentProtocols: 3 | - "JNLP4-connect" 4 | - "Ping" 5 | disableRememberMe: false 6 | mode: NORMAL 7 | numExecutors: 2 8 | primaryView: 9 | all: 10 | name: "all" 11 | quietPeriod: 5 12 | scmCheckoutRetryCount: 0 13 | slaveAgentPort: 0 14 | views: 15 | - all: 16 | name: "all" 17 | unclassified: 18 | logstashConfiguration: 19 | enableGlobally: true 20 | enabled: true 21 | logstashIndexer: 22 | logstash: 23 | host: "localhost" 24 | port: 9200 25 | milliSecondTimestamps: true 26 | -------------------------------------------------------------------------------- /src/test/resources/jcasc/rabbitmq.yaml: -------------------------------------------------------------------------------- 1 | jenkins: 2 | agentProtocols: 3 | - "JNLP4-connect" 4 | - "Ping" 5 | disableRememberMe: false 6 | mode: NORMAL 7 | numExecutors: 2 8 | primaryView: 9 | all: 10 | name: "all" 11 | quietPeriod: 5 12 | scmCheckoutRetryCount: 0 13 | slaveAgentPort: 0 14 | views: 15 | - all: 16 | name: "all" 17 | unclassified: 18 | logstashConfiguration: 19 | enableGlobally: true 20 | enabled: true 21 | logstashIndexer: 22 | rabbitMq: 23 | host: "localhost" 24 | port: 9200 25 | virtualHost: "/vhost" 26 | username: "rabbit" 27 | milliSecondTimestamps: true 28 | -------------------------------------------------------------------------------- /src/test/resources/jcasc/redis.yaml: -------------------------------------------------------------------------------- 1 | jenkins: 2 | agentProtocols: 3 | - "JNLP4-connect" 4 | - "Ping" 5 | disableRememberMe: false 6 | mode: NORMAL 7 | numExecutors: 2 8 | primaryView: 9 | all: 10 | name: "all" 11 | quietPeriod: 5 12 | scmCheckoutRetryCount: 0 13 | slaveAgentPort: 0 14 | views: 15 | - all: 16 | name: "all" 17 | unclassified: 18 | logstashConfiguration: 19 | enableGlobally: true 20 | enabled: true 21 | logstashIndexer: 22 | redis: 23 | host: "localhost" 24 | port: 9200 25 | key: "redis" 26 | milliSecondTimestamps: true 27 | -------------------------------------------------------------------------------- /src/test/resources/logs/syslog-test.log.sample: -------------------------------------------------------------------------------- 1 | {"severity":6,"@timestamp":"2017-06-06T01:40:28.000Z","@version":"1","host":"172.17.0.1","program":"jenkins:","message":" @cee: { 'junit': 'SyslogDaoTest' }","priority":14,"logsource":"localhost","facility":1,"severity_label":"Informational","timestamp":"Jun 06 01:40:28","facility_label":"user-level"} 2 | -------------------------------------------------------------------------------- /src/test/resources/logstash/pipeline/syslog.yml: -------------------------------------------------------------------------------- 1 | input { 2 | syslog { 3 | port => 5555 4 | } 5 | } 6 | 7 | output { 8 | file { 9 | path => "/tmp/logs/syslog-test.log" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker: -------------------------------------------------------------------------------- 1 | mock-maker-inline 2 | -------------------------------------------------------------------------------- /src/test/resources/rabbitmq.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | localhost 4 | 5672 5 | logstash 6 | 7 | {AQAAABAAAAAQ5wlElDfWGri9VuaMh0MCBZWs1fjL31zvnxrkszfW5pA=} 8 | UTF-8 9 | 10 | true 11 | true 12 | 13 | -------------------------------------------------------------------------------- /src/test/resources/rabbitmq_brokenCharset.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | localhost 4 | 5672 5 | logstash 6 | 7 | {AQAAABAAAAAQ5wlElDfWGri9VuaMh0MCBZWs1fjL31zvnxrkszfW5pA=} 8 | SOME-INVALID-CHARSET 9 | 10 | true 11 | true 12 | 13 | -------------------------------------------------------------------------------- /src/test/resources/redis.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | localhost 5 | 6379 6 | logstash 7 | {AQAAABAAAAAQAwaAxyveddM0PF+kR0dYFAymdth9PpitQnvJW0SR6JU=} 8 | 9 | true 10 | 11 | -------------------------------------------------------------------------------- /src/test/resources/syslog.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | localhost 5 | 519 6 | RFC_3164 7 | UDP 8 | 9 | true 10 | 11 | --------------------------------------------------------------------------------