Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Logz.io support #74

Open
wants to merge 57 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 42 commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
d986d80
first
May 31, 2018
8a00ac8
queue in workspace
Jun 3, 2018
de18b60
deleting comments
Jun 3, 2018
4ef9a2f
PR fixes
Jun 4, 2018
8c185d6
change dir path
Jun 4, 2018
6c7d6a6
using tmp dir
Jun 4, 2018
9b7621d
change @timestamp field name
Jun 4, 2018
5075433
using stream
Jun 5, 2018
fa317c1
Merge pull request #1 from idohalevi/first
idohalevi Jun 5, 2018
bf87eec
Update Logzio.java
idohalevi Jun 6, 2018
6d53f29
add debug info
Jun 17, 2018
4d9134d
change queue location
Jun 18, 2018
54cd262
change const
Jun 18, 2018
7a9164d
add debug
Jun 19, 2018
959f715
debug prints
Jun 20, 2018
03cb931
debug
Jun 24, 2018
035fb68
gzip logs
Jul 3, 2018
53ffac5
Merge branch 'master' of https://github.com/jenkinsci/logstash-plugin
Jul 3, 2018
84aed45
delete debug print
Jul 3, 2018
193f057
fixing for travis tests (#2)
idohalevi Jul 3, 2018
4dbe73e
Fix travis (#3)
idohalevi Jul 4, 2018
89a41e9
Use a container for more repeatable builds (#4)
tomwillfixit Jul 9, 2018
25c63b4
sync with upstream
Aug 22, 2018
bdab1a3
stream is back
Jul 4, 2018
488037f
logzio in memory https client
Aug 22, 2018
144e922
adding in memory http sender, local logger and updating README
Aug 23, 2018
30ccd6b
remove docker from readme install
Aug 23, 2018
bf200ca
delete comma
Aug 23, 2018
f2dc75e
Merge pull request #5 from idohalevi/PR
idohalevi Aug 23, 2018
ad75e1e
Ranges -> Range
jakub-bochenski Aug 23, 2018
6c27e9b
deleting mail
Aug 23, 2018
84d2051
adding static class/fields - travis findbugs
Aug 23, 2018
bd0ba73
adding charset - travis findbugs
Aug 23, 2018
22d88bc
change reporter class to static class
Aug 23, 2018
63647d6
change LOGGER to static member
Aug 23, 2018
6a70412
delete unused imports
Sep 5, 2018
f57f163
delete unused imports - test
Sep 5, 2018
bd062d0
add flatten data explanation
Sep 6, 2018
594c37d
send logs before size limit reached
Sep 6, 2018
07fcdb4
adding warning about thread safety
Sep 6, 2018
e2fb882
move LogzioHttpsClient to a top-level class
Sep 6, 2018
74fa7f6
fix spelling
Sep 6, 2018
93732e3
change key to token
Sep 12, 2018
203eca1
change messages list to Collections.synchronizedList
Sep 12, 2018
d31850b
style + typo
Sep 12, 2018
b3ffe89
rename help file
Sep 12, 2018
77577d9
change to capital
Sep 12, 2018
e7b056e
delete run-fast script
Sep 13, 2018
1508b3f
Merge branch 'master' of https://github.com/jenkinsci/logstash-plugin…
HananEgbaria Jan 30, 2019
3e37735
Merge pull request #6 from idohalevi/dev
HananEgbaria Feb 5, 2019
605a76c
add close method
HananEgbaria Feb 12, 2019
f5e9581
add "synchronized" to push method to resolve multi-threading (multi-j…
HananEgbaria Mar 5, 2019
1aec70e
update maven dependencies versions
HananEgbaria Mar 11, 2019
84c3602
remove unimportant comments
HananEgbaria Mar 11, 2019
93a5cd2
Merge pull request #7 from idohalevi/fix_comments
idohalevi Mar 18, 2019
b1dc099
remove unused methods
HananEgbaria Mar 19, 2019
261f22c
Merge pull request #8 from idohalevi/fix_comments
idohalevi Mar 19, 2019
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added .DS_Store
Binary file not shown.
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Jenkins Logstash Plugin
Travis: [![Build Status](https://travis-ci.org/jenkinsci/logstash-plugin.svg?branch=master)](https://travis-ci.org/jenkinsci/logstash-plugin)
Jenkins: [![Build Status](https://ci.jenkins.io/job/Plugins/job/logstash-plugin/job/master/badge/icon)](https://ci.jenkins.io/job/Plugins/job/logstash-plugin/job/master/)

This plugin adds support for sending a job's console log to Logstash indexers such as [Elastic Search](https://www.elastic.co/products/elasticsearch), [Logstash](https://www.elastic.co/de/products/logstash), [RabbitMQ](https://www.rabbitmq.com), [Redis](https://redis.io/) or to Syslog.
This plugin adds support for sending a job's console log to Logstash indexers such as [Logzio](https://logz.io/), [Elastic Search](https://www.elastic.co/products/elasticsearch), [Logstash](https://www.elastic.co/de/products/logstash), [RabbitMQ](https://www.rabbitmq.com), [Redis](https://redis.io/) or to Syslog.

* see [Jenkins wiki](https://wiki.jenkins-ci.org/display/JENKINS/Logstash+Plugin) for detailed feature descriptions
* use [JIRA](https://issues.jenkins-ci.org) to report issues / feature requests
Expand All @@ -15,6 +15,7 @@ Install
* Generate the `hpi` file with the command: `mvn package`

* Put the `hpi` file in the directory `$JENKINS_HOME/plugins`

* Restart jenkins

Configure
Expand All @@ -27,6 +28,7 @@ Currently supported methods of input/output:
* Redis {format => 'json_event'}
* RabbitMQ {mechanism => PLAIN}
* Syslog {format => cee/json ([RFC-5424](https://tools.ietf.org/html/rfc5424),[RFC-3164](https://tools.ietf.org/html/rfc3164)), protocol => UDP}
* Logz.io

Pipeline
========
Expand Down
12 changes: 12 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,18 @@
<scope>compile</scope>
</dependency>

<dependency>
<groupId>io.logz.sender</groupId>
<artifactId>logzio-sender</artifactId>
<version>1.0.14</version>
</dependency>

<dependency>
<groupId>com.github.wnameless</groupId>
<artifactId>json-flattener</artifactId>
<version>0.5.0</version>
</dependency>

<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
Expand Down
110 changes: 110 additions & 0 deletions src/main/java/jenkins/plugins/logstash/configuration/Logzio.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package jenkins.plugins.logstash.configuration;

import hudson.Extension;
import hudson.util.FormValidation;
import hudson.util.Secret;

import jenkins.plugins.logstash.persistence.LogzioDao;
import jenkins.plugins.logstash.Messages;

import org.apache.commons.lang.StringUtils;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.DataBoundSetter;
import org.kohsuke.stapler.QueryParameter;

import javax.annotation.Nonnull;

public class Logzio extends LogstashIndexer<LogzioDao>
{
private Secret key;
idohalevi marked this conversation as resolved.
Show resolved Hide resolved
private String host;

@DataBoundConstructor
public Logzio(){}

public String getHost(){ return this.host; }

@DataBoundSetter
public void setHost(String host){ this.host = host; }

public String getKey()
{
return Secret.toString(key);
}

@DataBoundSetter
public void setKey(String key)
{
this.key = Secret.fromString(key);
}


@Override
public boolean equals(Object obj)
{
if (obj == null)
return false;
if (this == obj)
return true;
if (getClass() != obj.getClass())
return false;
Logzio other = (Logzio) obj;
if (!Secret.toString(key).equals(other.getKey()))
{
return false;
}
if (host == null)
{
return other.host == null;
}
else return host.equals(other.host);
idohalevi marked this conversation as resolved.
Show resolved Hide resolved
}

@Override
public int hashCode()
{
final int prime = 31;
int result = super.hashCode();
result = prime * result + ((host == null) ? 0 : host.hashCode());
result = prime * result + Secret.toString(key).hashCode();
return result;
}

@Override
public LogzioDao createIndexerInstance() { return new LogzioDao(host, Secret.toString(key)); }

@Extension
public static class LogzioDescriptor extends LogstashIndexerDescriptor
{
@Nonnull
@Override
public String getDisplayName()
{
return "Logz.io";
}

@Override
public int getDefaultPort()
{
return 0;
}

public FormValidation doCheckKey(@QueryParameter("value") String value)
{
if (StringUtils.isBlank(value))
{
return FormValidation.error(Messages.ValueIsRequired());
}
return FormValidation.ok();
}

public FormValidation doCheckHost(@QueryParameter("value") String value)
idohalevi marked this conversation as resolved.
Show resolved Hide resolved
{
if (StringUtils.isBlank(value))
{
return FormValidation.error(Messages.ValueIsRequired());
}
return FormValidation.ok();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@

package jenkins.plugins.logstash.persistence;

import static com.google.common.collect.Ranges.closedOpen;
import static com.google.common.collect.Range.closedOpen;

import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang.StringUtils;
Expand Down
109 changes: 109 additions & 0 deletions src/main/java/jenkins/plugins/logstash/persistence/LogzioDao.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
package jenkins.plugins.logstash.persistence;

import com.github.wnameless.json.flattener.JsonFlattener;
import com.google.gson.JsonObject;
import io.logz.sender.FormattedLogMessage;
import io.logz.sender.exceptions.LogzioParameterErrorException;
import io.logz.sender.exceptions.LogzioServerErrorException;
import net.sf.json.JSONArray;
import net.sf.json.JSONObject;

import java.io.IOException;
import java.nio.charset.Charset;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.Map;

/**
* Logz.io Data Access Object.
*
* @author Ido Halevi
*
*/

public class LogzioDao extends AbstractLogstashIndexerDao {
private final String TYPE = "jenkins_logstash_plugin";
private String key;
private String host;
private LogzioHttpsClient httpsClient;

//primary constructor used by indexer factory
public LogzioDao(String host, String key){
this(null, host, key);
}

// Factored for unit testing
LogzioDao(LogzioHttpsClient factory, String host, String key) {
this.host = host;
this.key = key;
try{
this.httpsClient = factory == null ? new LogzioHttpsClient(key, host, TYPE) : factory;
jakub-bochenski marked this conversation as resolved.
Show resolved Hide resolved
}catch (LogzioParameterErrorException e) {
throw new IllegalArgumentException(e);
}
}

@Override
public void push(String data) throws IOException {
JSONObject jsonData = JSONObject.fromObject(data);
JSONArray logMessages = jsonData.getJSONArray("message");
if (!logMessages.isEmpty()) {
try{
for (Object logMsg : logMessages) {
JsonObject logLine = createLogLine(jsonData, logMsg.toString());
httpsClient.send(new FormattedLogMessage((logLine + "\n").getBytes(Charset.forName("UTF-8"))));
}
httpsClient.flush();
jakub-bochenski marked this conversation as resolved.
Show resolved Hide resolved
}catch (LogzioServerErrorException e){
throw new IOException(e);
}
}
}


private JsonObject createLogLine(JSONObject jsonData, String logMsg) {
JsonObject logLine = new JsonObject();

logLine.addProperty("message", logMsg);
logLine.addProperty("@timestamp", ZonedDateTime.now(ZoneOffset.UTC).format(DateTimeFormatter.ISO_OFFSET_DATE_TIME));

for (Object key : jsonData.keySet()) {
if (!key.equals("message")){
logLine.addProperty(key.toString(), jsonData.getString(key.toString()));
}
}

return logLine;
}

@Override
public JSONObject buildPayload(BuildData buildData, String jenkinsUrl, List<String> logLines) {
JSONObject payload = new JSONObject();
payload.put("message", logLines);
payload.put("source", "jenkins");
payload.put("source_host", jenkinsUrl);
payload.put("@buildTimestamp", buildData.getTimestamp());
payload.put("@version", 1);
// Flatten build data - so the user will be able to use fields for visualization in Kibana.
// In addition, it makes the query much easier.
Map<String, Object> flattenJson = JsonFlattener.flattenAsMap(buildData.toString());
idohalevi marked this conversation as resolved.
Show resolved Hide resolved
for (Map.Entry<String, Object> entry : flattenJson.entrySet()) {
String key = entry.getKey().replace('.','_');
Object value = entry.getValue();
payload.put(key, value);
}

return payload;
}

@Override
public String getDescription(){ return host; }

public String getHost(){ return host; }

public String getKey(){ return key; }

public String getType(){ return TYPE; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package jenkins.plugins.logstash.persistence;

import io.logz.sender.FormattedLogMessage;
import io.logz.sender.HttpsRequestConfiguration;
import io.logz.sender.HttpsSyncSender;
import io.logz.sender.SenderStatusReporter;
import io.logz.sender.exceptions.LogzioParameterErrorException;
import io.logz.sender.exceptions.LogzioServerErrorException;

import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

public class LogzioHttpsClient{
private static final int MAX_SIZE_IN_BYTES = 8 * 1024 * 1024; // 8 MB
private static final int CONNECT_TIMEOUT = 10 * 1000;
private static final int SOCKET_TIMEOUT = 10 * 1000;
private final HttpsSyncSender logzioClient;
private List<FormattedLogMessage> messages; //Not thread safe
private int size;

LogzioHttpsClient(String token, String listener, String type) throws LogzioParameterErrorException {
HttpsRequestConfiguration gzipHttpsRequestConfiguration = HttpsRequestConfiguration
.builder()
.setLogzioToken(token)
.setLogzioType(type)
.setLogzioListenerUrl(listener)
.setSocketTimeout(SOCKET_TIMEOUT)
.setConnectTimeout(CONNECT_TIMEOUT)
.setCompressRequests(true)
.build();

logzioClient = new HttpsSyncSender(gzipHttpsRequestConfiguration, new Reporter());
messages = new ArrayList<>();

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If not synchronized this will become a problem as soon as you have multiple parallel builds. Use
Collections.synchronizedList(new ArrayList<>())
Makes it thread safe probably.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good catch, I forgot it has to be synchronized if we reuse it on the -Dao level

size = 0;
}

public void send(FormattedLogMessage log) throws LogzioServerErrorException {
if (size + log.getSize() > MAX_SIZE_IN_BYTES) {
sendAndReset();
}
messages.add(log);
size += log.getSize();
}

private void reset(){
size = 0;
messages.clear();
}

void flush() throws LogzioServerErrorException {
if(messages.size() > 0) {
sendAndReset();
}
}

private void sendAndReset() throws LogzioServerErrorException {
logzioClient.sendToLogzio(messages);
reset();
}

private static class Reporter implements SenderStatusReporter{
private static final Logger LOGGER = Logger.getLogger(LogzioDao.class.getName());

private void pringLogMessage(Level level, String msg) {
LOGGER.log(level, msg);
}

@Override
public void error(String msg) {
pringLogMessage(Level.SEVERE, "[LogzioSender]ERROR: " + msg);
}

@Override
public void error(String msg, Throwable e) {
pringLogMessage(Level.SEVERE, "[LogzioSender]ERROR: " + msg + "\n" +e);
}

@Override
public void warning(String msg) {
pringLogMessage(Level.WARNING, "[LogzioSender]WARNING: " + msg);
}

@Override
public void warning(String msg, Throwable e) {
pringLogMessage(Level.WARNING, "[LogzioSender]WARNING: " + msg + "\n" + e);
}

@Override
public void info(String msg) {
pringLogMessage(Level.INFO, "[LogzioSender]INFO: " + msg);
}

@Override
public void info(String msg, Throwable e) {
pringLogMessage(Level.INFO, "[LogzioSender]INFO: " + msg + "\n" + e);
}
}
}
2 changes: 1 addition & 1 deletion src/main/resources/index.jelly
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
<div>
Adds the possibility to push builds logs and build data to a Logstash indexer such as Redis, RabbitMQ, Elastic Search or to Syslog.
Adds the possibility to push builds logs and build data to a Logstash indexer such as Redis, RabbitMQ, Elastic Search, Logz.io or to Syslog.
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?jelly escape-by-default='true'?>
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form">
<f:entry title="${%Logz.io Host}" field="host">
<f:textbox default="https://listener.logz.io:8071"/>
</f:entry>
<f:entry title="${%Logz.io key}" field="key">
idohalevi marked this conversation as resolved.
Show resolved Hide resolved
<f:password/>
</f:entry>
</j:jelly>
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<div>
<p>Logz.io listener URL.<br/>
If you are in the EU region insert https://listener-eu.logz.io:8071. Otherwise, use https://listener.logz.io:8071<br/>
You can tell which region you are in by checking your login URL</p>
</div>
Loading