diff --git a/build.xml b/build.xml index 4ec5f3b..2933ef2 100644 --- a/build.xml +++ b/build.xml @@ -34,6 +34,7 @@ + diff --git a/log4j.properties b/log4j.properties index 040e4de..7677c37 100644 --- a/log4j.properties +++ b/log4j.properties @@ -1,16 +1,6 @@ # Root logger option log4j.rootLogger=INFO, stdout -log4j.logger.sumologic = TRACE, sumo - -# Direct log messages to sumo -log4j.appender.sumo=com.sumologic.log4j.BufferedSumoLogicAppender -log4j.appender.sumo.url=https://collectors.us2.sumologic.com/receiver/v1/http/ZaVnC4dhaV0GzIY4tZaKLL26afV52gXBvSFc3jG1eLc2lKINzS2doZdRjUMQMb2CXK8r6fdmHoUazJiHjJ-2OygApoWFaxCTkWFrzAiraCc5i411pkio-g== -log4j.appender.sumo.layout=org.apache.log4j.PatternLayout -log4j.appender.sumo.layout.ConversionPattern=%d{DATE} %5p %c{1}:%L - %m%n -log4j.additivity.sumo = false -log4j.appender.sumo.Threshold = TRACE log4j.appender.stdout=org.apache.log4j.ConsoleAppender log4j.appender.stdout.layout=org.apache.log4j.PatternLayout log4j.appender.stdout.layout.ConversionPattern=%d{DATE} %5p %c{1}:%L - %m%n -log4j.appender.stdout.Threshold = INFO diff --git a/src/main/java/com/sumologic/client/CloudWatchMessageModelSumologicTransformer.java b/src/main/java/com/sumologic/client/CloudWatchMessageModelSumologicTransformer.java index eb9072d..476176b 100644 --- a/src/main/java/com/sumologic/client/CloudWatchMessageModelSumologicTransformer.java +++ b/src/main/java/com/sumologic/client/CloudWatchMessageModelSumologicTransformer.java @@ -2,32 +2,24 @@ import java.io.IOException; -import com.amazonaws.services.kinesis.connectors.BasicJsonTransformer; import com.amazonaws.services.kinesis.model.Record; import com.amazonaws.util.json.JSONArray; import com.amazonaws.util.json.JSONException; import com.amazonaws.util.json.JSONObject; -import com.sumologic.client.SimpleKinesisMessageModel; import com.sumologic.client.implementations.SumologicTransformer; +import com.sumologic.client.model.CloudWatchLogsMessageModel; +import com.sumologic.client.model.LogEvent; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; +import org.apache.log4j.Logger; -import java.io.ByteArrayInputStream; -import java.io.BufferedReader; -import java.io.InputStreamReader; import java.nio.ByteBuffer; import java.nio.CharBuffer; import java.nio.charset.CharacterCodingException; import java.nio.charset.Charset; -import java.nio.charset.CharsetDecoder; import java.nio.charset.CharsetEncoder; import java.util.List; -import java.util.zip.GZIPInputStream; -import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.ObjectMapper; -import com.google.gson.Gson; /** * A custom transfomer for {@link CloudWatchLogsMessageModel} records in JSON format. The output is in a format @@ -35,9 +27,8 @@ */ public class CloudWatchMessageModelSumologicTransformer implements SumologicTransformer { - - private static final Log LOG = LogFactory.getLog(CloudWatchMessageModelSumologicTransformer.class); - + private static final Logger LOG = Logger.getLogger(CloudWatchMessageModelSumologicTransformer.class.getName()); + private static CharsetEncoder encoder = Charset.forName("UTF-8").newEncoder(); /** @@ -84,7 +75,7 @@ public String fromClass(CloudWatchLogsMessageModel message) { @Override public CloudWatchLogsMessageModel toClass(Record record) { byte[] decodedRecord = record.getData().array(); - String stringifiedRecord = decompressGzip(decodedRecord); + String stringifiedRecord = SumologicKinesisUtils.decompressGzip(decodedRecord); if (stringifiedRecord == null) { LOG.error("Unable to decompress the record: "+new String(record.getData().array()) @@ -109,48 +100,5 @@ public CloudWatchLogsMessageModel toClass(Record record) { +"\nerror: "+e.getMessage()); } return null; - } - - public static String decompressGzip(byte[] compressedData) { - try { - GZIPInputStream gis = new GZIPInputStream(new ByteArrayInputStream(compressedData)); - BufferedReader bf = new BufferedReader(new InputStreamReader(gis, "UTF-8")); - - String outStr = ""; - String line; - while ((line=bf.readLine())!=null) { - outStr += line; - } - return outStr; - } catch (IOException exc) { - LOG.warn("Exception during decompression of data: " + exc.getMessage()); - return null; - } - } - - public static String byteBufferToString(ByteBuffer buffer){ - String data = ""; - CharsetDecoder decoder = Charset.forName("UTF-8").newDecoder(); - try{ - int old_position = buffer.position(); - data = decoder.decode(buffer).toString(); - buffer.position(old_position); - }catch (Exception e){ - e.printStackTrace(); - return ""; - } - return data; - } - - private static final Gson gson = new Gson(); - public static boolean verifyJSON(String json) { - try { - gson.fromJson(json, Object.class); - return true; - } catch(com.google.gson.JsonSyntaxException ex) { - return false; - } - } - } diff --git a/src/main/java/com/sumologic/client/DefaultKinesisMessageModelSumologicTransformer.java b/src/main/java/com/sumologic/client/DefaultKinesisMessageModelSumologicTransformer.java index 5266d00..bfc61ba 100644 --- a/src/main/java/com/sumologic/client/DefaultKinesisMessageModelSumologicTransformer.java +++ b/src/main/java/com/sumologic/client/DefaultKinesisMessageModelSumologicTransformer.java @@ -2,24 +2,9 @@ import java.io.IOException; -import com.amazonaws.services.kinesis.connectors.BasicJsonTransformer; import com.amazonaws.services.kinesis.model.Record; -import com.sumologic.client.SimpleKinesisMessageModel; -import com.sumologic.client.implementations.SumologicEmitter; import com.sumologic.client.implementations.SumologicTransformer; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -import java.io.ByteArrayInputStream; -import java.io.BufferedReader; -import java.io.InputStreamReader; -import java.util.zip.GZIPInputStream; -import java.nio.charset.StandardCharsets; -import java.util.Arrays; - -import org.apache.commons.codec.binary.Base64; - +import com.sumologic.client.model.SimpleKinesisMessageModel; /** * A custom transfomer for {@link SimpleKinesisMessageModel} records in JSON format. The output is in a format @@ -27,9 +12,6 @@ */ public class DefaultKinesisMessageModelSumologicTransformer implements SumologicTransformer { - - private static final Log LOG = LogFactory.getLog(DefaultKinesisMessageModelSumologicTransformer.class); - /** * Creates a new KinesisMessageModelSumologicTransformer. */ @@ -46,7 +28,7 @@ public String fromClass(SimpleKinesisMessageModel message) { public SimpleKinesisMessageModel toClass(Record record) throws IOException { byte[] decodedRecord = record.getData().array(); String stringifiedRecord = new String(decodedRecord); - + return new SimpleKinesisMessageModel(stringifiedRecord); } } diff --git a/src/main/java/com/sumologic/client/SumologicExecutor.java b/src/main/java/com/sumologic/client/SumologicExecutor.java index f2e48c6..b3e3b38 100644 --- a/src/main/java/com/sumologic/client/SumologicExecutor.java +++ b/src/main/java/com/sumologic/client/SumologicExecutor.java @@ -1,13 +1,11 @@ package com.sumologic.client; -import com.amazonaws.services.kinesis.connectors.KinesisConnectorRecordProcessorFactory; - +import com.sumologic.kinesis.KinesisConnectorRecordProcessorFactory; import com.sumologic.kinesis.KinesisConnectorExecutor; -import com.sumologic.client.SimpleKinesisMessageModel; import com.sumologic.client.SumologicMessageModelPipeline; +import com.sumologic.client.model.SimpleKinesisMessageModel; public class SumologicExecutor extends KinesisConnectorExecutor { - private static String configFile = "SumologicConnector.properties"; /** diff --git a/src/main/java/com/sumologic/client/SumologicKinesisUtils.java b/src/main/java/com/sumologic/client/SumologicKinesisUtils.java new file mode 100644 index 0000000..5e1ee0f --- /dev/null +++ b/src/main/java/com/sumologic/client/SumologicKinesisUtils.java @@ -0,0 +1,86 @@ +package com.sumologic.client; + +import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.ByteBuffer; +import java.nio.charset.Charset; +import java.nio.charset.CharsetDecoder; +import java.util.zip.GZIPInputStream; +import java.util.zip.GZIPOutputStream; + +import org.apache.log4j.Logger; + +import com.google.gson.Gson; + +public class SumologicKinesisUtils { + private static final Logger LOG = Logger.getLogger(SumologicKinesisUtils.class.getName()); + + public static byte[] compressGzip(String data) { + if (data == null || data.length() == 0) { + return null; + } + + ByteArrayOutputStream outputStream=new ByteArrayOutputStream(); + GZIPOutputStream gzip; + try { + gzip = new GZIPOutputStream(outputStream); + } catch (IOException e) { + LOG.error("Cannot compress into GZIP "+e.getMessage()); + return null; + } + + // Put data into the GZIP buffer + try { + gzip.write(data.getBytes("UTF-8")); + gzip.close(); + } catch (IOException e) { + e.printStackTrace(); + } + + return outputStream.toByteArray(); + } + + public static String decompressGzip(byte[] compressedData) { + try { + GZIPInputStream gis = new GZIPInputStream(new ByteArrayInputStream(compressedData)); + BufferedReader bf = new BufferedReader(new InputStreamReader(gis, "UTF-8")); + + String outStr = ""; + String line; + while ((line=bf.readLine())!=null) { + outStr += line; + } + return outStr; + } catch (IOException exc) { + LOG.warn("Exception during decompression of data: " + exc.getMessage()); + return null; + } + } + + public static String byteBufferToString(ByteBuffer buffer){ + String data = ""; + CharsetDecoder decoder = Charset.forName("UTF-8").newDecoder(); + try{ + int old_position = buffer.position(); + data = decoder.decode(buffer).toString(); + buffer.position(old_position); + }catch (Exception e){ + e.printStackTrace(); + return ""; + } + return data; + } + + private static final Gson gson = new Gson(); + public static boolean verifyJSON(String json) { + try { + gson.fromJson(json, Object.class); + return true; + } catch(com.google.gson.JsonSyntaxException ex) { + return false; + } + } +} \ No newline at end of file diff --git a/src/main/java/com/sumologic/client/SumologicMessageModelPipeline.java b/src/main/java/com/sumologic/client/SumologicMessageModelPipeline.java index edbf76b..13c8a2e 100644 --- a/src/main/java/com/sumologic/client/SumologicMessageModelPipeline.java +++ b/src/main/java/com/sumologic/client/SumologicMessageModelPipeline.java @@ -1,11 +1,9 @@ package com.sumologic.client; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; +import org.apache.log4j.Logger; -import com.sumologic.client.SimpleKinesisMessageModel; -import com.sumologic.client.CloudWatchMessageModelSumologicTransformer; import com.sumologic.client.implementations.SumologicEmitter; +import com.sumologic.client.model.SimpleKinesisMessageModel; import com.amazonaws.services.kinesis.connectors.interfaces.IKinesisConnectorPipeline; import com.amazonaws.services.kinesis.connectors.KinesisConnectorConfiguration; import com.amazonaws.services.kinesis.connectors.impl.BasicMemoryBuffer; @@ -28,9 +26,8 @@ */ public class SumologicMessageModelPipeline implements IKinesisConnectorPipeline { + private static final Logger LOG = Logger.getLogger(SumologicMessageModelPipeline.class.getName()); - private static final Log LOG = LogFactory.getLog(SumologicMessageModelPipeline.class); - @Override public IEmitter getEmitter(KinesisConnectorConfiguration configuration) { return new SumologicEmitter(configuration); diff --git a/src/main/java/com/sumologic/client/SumologicSender.java b/src/main/java/com/sumologic/client/SumologicSender.java index c0ee0c8..10b569f 100644 --- a/src/main/java/com/sumologic/client/SumologicSender.java +++ b/src/main/java/com/sumologic/client/SumologicSender.java @@ -48,7 +48,7 @@ public boolean sendToSumologic(String data) throws IOException{ BoundRequestBuilder builder = null; builder = this.clientPreparePost(url); - byte[] compressedData = SumologicSender.compressGzip(data); + byte[] compressedData = SumologicKinesisUtils.compressGzip(data); builder.setHeader("Content-Encoding", "gzip"); builder.setBody(compressedData); @@ -81,29 +81,4 @@ public boolean sendToSumologic(String data) throws IOException{ return true; } } - - public static byte[] compressGzip(String data) { - if (data == null || data.length() == 0) { - return null; - } - - ByteArrayOutputStream outputStream=new ByteArrayOutputStream(); - GZIPOutputStream gzip; - try { - gzip = new GZIPOutputStream(outputStream); - } catch (IOException e) { - LOG.error("Cannot compress into GZIP "+e.getMessage()); - return null; - } - - // Put data into the GZIP buffer - try { - gzip.write(data.getBytes("UTF-8")); - gzip.close(); - } catch (IOException e) { - e.printStackTrace(); - } - - return outputStream.toByteArray(); - } } \ No newline at end of file diff --git a/src/main/java/com/sumologic/client/implementations/SumologicEmitter.java b/src/main/java/com/sumologic/client/implementations/SumologicEmitter.java index dc4c1da..d95ce1d 100644 --- a/src/main/java/com/sumologic/client/implementations/SumologicEmitter.java +++ b/src/main/java/com/sumologic/client/implementations/SumologicEmitter.java @@ -2,16 +2,12 @@ import java.io.IOException; import java.util.ArrayList; -import java.util.HashSet; import java.util.List; -import java.util.Set; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; +import org.apache.log4j.Logger; import com.sumologic.client.SumologicSender; import com.sumologic.client.KinesisConnectorForSumologicConfiguration; - import com.amazonaws.services.kinesis.connectors.KinesisConnectorConfiguration; import com.amazonaws.services.kinesis.connectors.UnmodifiableBuffer; import com.amazonaws.services.kinesis.connectors.interfaces.IEmitter; @@ -22,7 +18,7 @@ * Sumologic. */ public class SumologicEmitter implements IEmitter { - private static final Log LOG = LogFactory.getLog(SumologicEmitter.class); + private static final Logger LOG = Logger.getLogger(SumologicEmitter.class.getName()); private SumologicSender sender; private KinesisConnectorForSumologicConfiguration config; diff --git a/src/main/java/com/sumologic/client/CloudWatchLogsMessageModel.java b/src/main/java/com/sumologic/client/model/CloudWatchLogsMessageModel.java similarity index 97% rename from src/main/java/com/sumologic/client/CloudWatchLogsMessageModel.java rename to src/main/java/com/sumologic/client/model/CloudWatchLogsMessageModel.java index 75e7f23..3792755 100644 --- a/src/main/java/com/sumologic/client/CloudWatchLogsMessageModel.java +++ b/src/main/java/com/sumologic/client/model/CloudWatchLogsMessageModel.java @@ -1,4 +1,4 @@ -package com.sumologic.client; +package com.sumologic.client.model; import java.util.ArrayList; import java.util.HashMap; @@ -7,7 +7,6 @@ import org.apache.commons.lang.builder.ToStringBuilder; -import com.sumologic.client.LogEvent; import com.fasterxml.jackson.annotation.JsonAnyGetter; import com.fasterxml.jackson.annotation.JsonAnySetter; import com.fasterxml.jackson.annotation.JsonIgnore; diff --git a/src/main/java/com/sumologic/client/LogEvent.java b/src/main/java/com/sumologic/client/model/LogEvent.java similarity index 97% rename from src/main/java/com/sumologic/client/LogEvent.java rename to src/main/java/com/sumologic/client/model/LogEvent.java index f0eb6fd..9a2261e 100644 --- a/src/main/java/com/sumologic/client/LogEvent.java +++ b/src/main/java/com/sumologic/client/model/LogEvent.java @@ -1,4 +1,4 @@ -package com.sumologic.client; +package com.sumologic.client.model; import java.util.HashMap; import java.util.Map; diff --git a/src/main/java/com/sumologic/client/SimpleKinesisMessageModel.java b/src/main/java/com/sumologic/client/model/SimpleKinesisMessageModel.java similarity index 93% rename from src/main/java/com/sumologic/client/SimpleKinesisMessageModel.java rename to src/main/java/com/sumologic/client/model/SimpleKinesisMessageModel.java index da5e43c..9a6db32 100644 --- a/src/main/java/com/sumologic/client/SimpleKinesisMessageModel.java +++ b/src/main/java/com/sumologic/client/model/SimpleKinesisMessageModel.java @@ -1,4 +1,4 @@ -package com.sumologic.client; +package com.sumologic.client.model; import java.io.Serializable; diff --git a/src/main/java/com/sumologic/kinesis/BatchedStreamSource.java b/src/main/java/com/sumologic/kinesis/BatchedStreamSource.java index 192db34..f7e932a 100644 --- a/src/main/java/com/sumologic/kinesis/BatchedStreamSource.java +++ b/src/main/java/com/sumologic/kinesis/BatchedStreamSource.java @@ -11,11 +11,9 @@ import java.util.List; import java.util.UUID; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -import com.sumologic.client.SimpleKinesisMessageModel; +import org.apache.log4j.Logger; +import com.sumologic.client.model.SimpleKinesisMessageModel; import com.amazonaws.services.kinesis.connectors.KinesisConnectorConfiguration; import com.amazonaws.services.kinesis.model.PutRecordRequest; @@ -24,7 +22,7 @@ * input file specified in the constructor and batches up records before emitting them. */ public class BatchedStreamSource extends StreamSource { - private static Log LOG = LogFactory.getLog(BatchedStreamSource.class); + private static final Logger LOG = Logger.getLogger(BatchedStreamSource.class.getName()); private static int NUM_BYTES_PER_PUT_REQUEST = 50000; List buffer; diff --git a/src/main/java/com/sumologic/kinesis/KinesisConnectorExecutor.java b/src/main/java/com/sumologic/kinesis/KinesisConnectorExecutor.java index 77a6e56..745c134 100644 --- a/src/main/java/com/sumologic/kinesis/KinesisConnectorExecutor.java +++ b/src/main/java/com/sumologic/kinesis/KinesisConnectorExecutor.java @@ -4,8 +4,7 @@ import java.io.InputStream; import java.util.Properties; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; +import org.apache.log4j.Logger; import com.sumologic.client.KinesisConnectorForSumologicConfiguration; import com.sumologic.kinesis.KinesisConnectorExecutorBase; @@ -18,8 +17,7 @@ * */ public abstract class KinesisConnectorExecutor extends KinesisConnectorExecutorBase { - - private static final Log LOG = LogFactory.getLog(KinesisConnectorExecutor.class); + private static final Logger LOG = Logger.getLogger(KinesisConnectorExecutor.class.getName()); // Create Stream Source constants private static final String CREATE_STREAM_SOURCE = "createStreamSource"; diff --git a/src/main/java/com/sumologic/kinesis/KinesisConnectorExecutorBase.java b/src/main/java/com/sumologic/kinesis/KinesisConnectorExecutorBase.java index e7c8cae..ffcdfeb 100644 --- a/src/main/java/com/sumologic/kinesis/KinesisConnectorExecutorBase.java +++ b/src/main/java/com/sumologic/kinesis/KinesisConnectorExecutorBase.java @@ -1,18 +1,17 @@ package com.sumologic.kinesis; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; +import org.apache.log4j.Logger; +import com.sumologic.kinesis.KinesisConnectorRecordProcessorFactory; import com.amazonaws.services.kinesis.clientlibrary.lib.worker.KinesisClientLibConfiguration; import com.amazonaws.services.kinesis.clientlibrary.lib.worker.Worker; import com.amazonaws.services.kinesis.connectors.KinesisConnectorConfiguration; -import com.amazonaws.services.kinesis.connectors.KinesisConnectorRecordProcessorFactory; import com.amazonaws.services.kinesis.metrics.impl.NullMetricsFactory; import com.amazonaws.services.kinesis.metrics.interfaces.IMetricsFactory; public abstract class KinesisConnectorExecutorBase implements Runnable { - private static final Log LOG = LogFactory.getLog(KinesisConnectorExecutorBase.class); - + private static final Logger LOG = Logger.getLogger(KinesisConnectorExecutorBase.class.getName()); + // Amazon Kinesis Client Library worker to process records protected Worker worker; diff --git a/src/main/java/com/sumologic/kinesis/KinesisConnectorRecordProcessor.java b/src/main/java/com/sumologic/kinesis/KinesisConnectorRecordProcessor.java new file mode 100644 index 0000000..5c291ce --- /dev/null +++ b/src/main/java/com/sumologic/kinesis/KinesisConnectorRecordProcessor.java @@ -0,0 +1,213 @@ +/* + * Copyright 2013-2014 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Amazon Software License (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/asl/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ +package com.sumologic.kinesis; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import org.apache.log4j.Logger; + +import com.amazonaws.services.kinesis.clientlibrary.exceptions.InvalidStateException; +import com.amazonaws.services.kinesis.clientlibrary.exceptions.KinesisClientLibDependencyException; +import com.amazonaws.services.kinesis.clientlibrary.exceptions.ShutdownException; +import com.amazonaws.services.kinesis.clientlibrary.exceptions.ThrottlingException; +import com.amazonaws.services.kinesis.clientlibrary.interfaces.IRecordProcessor; +import com.amazonaws.services.kinesis.clientlibrary.interfaces.IRecordProcessorCheckpointer; +import com.amazonaws.services.kinesis.clientlibrary.types.ShutdownReason; +import com.amazonaws.services.kinesis.connectors.KinesisConnectorConfiguration; +import com.amazonaws.services.kinesis.connectors.UnmodifiableBuffer; +import com.amazonaws.services.kinesis.connectors.interfaces.IBuffer; +import com.amazonaws.services.kinesis.connectors.interfaces.ICollectionTransformer; +import com.amazonaws.services.kinesis.connectors.interfaces.IEmitter; +import com.amazonaws.services.kinesis.connectors.interfaces.IFilter; +import com.amazonaws.services.kinesis.connectors.interfaces.ITransformer; +import com.amazonaws.services.kinesis.connectors.interfaces.ITransformerBase; +import com.amazonaws.services.kinesis.model.Record; + +/** + * This is the base class for any KinesisConnector. It is configured by a constructor that takes in + * as parameters implementations of the IBuffer, ITransformer, and IEmitter dependencies defined in + * a IKinesisConnectorPipeline. It is typed to match the class that records are transformed into for + * filtering and manipulation. This class is produced by a KinesisConnectorRecordProcessorFactory. + *

+ * When a Worker calls processRecords() on this class, the pipeline is used in the following way: + *

    + *
  1. Records are transformed into the corresponding data model (parameter type T) via the ITransformer.
  2. + *
  3. Transformed records are passed to the IBuffer.consumeRecord() method, which may optionally filter based on the + * IFilter in the pipeline.
  4. + *
  5. When the buffer is full (IBuffer.shouldFlush() returns true), records are transformed with the ITransformer to + * the output type (parameter type U) and a call is made to IEmitter.emit(). IEmitter.emit() returning an empty list is + * considered a success, so the record processor will checkpoint and emit will not be retried. Non-empty return values + * will result in additional calls to emit with failed records as the unprocessed list until the retry limit is reached. + * Upon exceeding the retry limit or an exception being thrown, the IEmitter.fail() method will be called with the + * unprocessed records.
  6. + *
  7. When the shutdown() method of this class is invoked, a call is made to the IEmitter.shutdown() method which + * should close any existing client connections.
  8. + *
+ * + */ +public class KinesisConnectorRecordProcessor implements IRecordProcessor { + + private final IEmitter emitter; + private final ITransformerBase transformer; + private final IFilter filter; + private final IBuffer buffer; + private final int retryLimit; + private final long backoffInterval; + private boolean isShutdown = false; + + private static final Logger LOG = Logger.getLogger(KinesisConnectorRecordProcessor.class.getName()); + + private String shardId; + + public KinesisConnectorRecordProcessor(IBuffer buffer, + IFilter filter, + IEmitter emitter, + ITransformerBase transformer, + KinesisConnectorConfiguration configuration) { + if (buffer == null || filter == null || emitter == null || transformer == null) { + throw new IllegalArgumentException("buffer, filter, emitter, and transformer must not be null"); + } + this.buffer = buffer; + this.filter = filter; + this.emitter = emitter; + this.transformer = transformer; + // Limit must be greater than zero + if (configuration.RETRY_LIMIT <= 0) { + retryLimit = 1; + } else { + retryLimit = configuration.RETRY_LIMIT; + } + this.backoffInterval = configuration.BACKOFF_INTERVAL; + } + + @Override + public void initialize(String shardId) { + this.shardId = shardId; + } + + @Override + public void processRecords(List records, IRecordProcessorCheckpointer checkpointer) { + // Note: This method will be called even for empty record lists. This is needed for checking the buffer time + // threshold. + if (isShutdown) { + LOG.warn("processRecords called on shutdown record processor for shardId: " + shardId); + return; + } + if (shardId == null) { + throw new IllegalStateException("Record processor not initialized"); + } + + // Transform each Amazon Kinesis Record and add the result to the buffer + for (Record record : records) { + try { + if (transformer instanceof ITransformer) { + ITransformer singleTransformer = (ITransformer) transformer; + filterAndBufferRecord(singleTransformer.toClass(record), record); + } else if (transformer instanceof ICollectionTransformer) { + ICollectionTransformer listTransformer = (ICollectionTransformer) transformer; + Collection transformedRecords = listTransformer.toClass(record); + for (T transformedRecord : transformedRecords) { + filterAndBufferRecord(transformedRecord, record); + } + } else { + throw new RuntimeException("Transformer must implement ITransformer or ICollectionTransformer"); + } + } catch (IOException e) { + LOG.error(e); + } + } + + if (buffer.shouldFlush()) { + List emitItems = transformToOutput(buffer.getRecords()); + emit(checkpointer, emitItems); + } + } + + private void filterAndBufferRecord(T transformedRecord, Record record) { + if (filter.keepRecord(transformedRecord)) { + buffer.consumeRecord(transformedRecord, record.getData().array().length, record.getSequenceNumber()); + } + } + + private List transformToOutput(List items) { + List emitItems = new ArrayList(); + for (T item : items) { + try { + emitItems.add(transformer.fromClass(item)); + } catch (IOException e) { + LOG.error("Failed to transform record " + item + " to output type", e); + } + } + return emitItems; + } + + private void emit(IRecordProcessorCheckpointer checkpointer, List emitItems) { + List unprocessed = new ArrayList(emitItems); + try { + for (int numTries = 0; numTries < retryLimit; numTries++) { + unprocessed = emitter.emit(new UnmodifiableBuffer(buffer, unprocessed)); + if (unprocessed.isEmpty()) { + break; + } + try { + Thread.sleep(backoffInterval); + } catch (InterruptedException e) { + } + } + if (!unprocessed.isEmpty()) { + emitter.fail(unprocessed); + } + final String lastSequenceNumberProcessed = buffer.getLastSequenceNumber(); + buffer.clear(); + // checkpoint once all the records have been consumed + if (lastSequenceNumberProcessed != null && unprocessed.isEmpty()) { + checkpointer.checkpoint(lastSequenceNumberProcessed); + } + } catch (IOException | KinesisClientLibDependencyException | InvalidStateException | ThrottlingException + | ShutdownException e) { + LOG.error(e); + emitter.fail(unprocessed); + } + } + + @Override + public void shutdown(IRecordProcessorCheckpointer checkpointer, ShutdownReason reason) { + LOG.info("Shutting down record processor with shardId: " + shardId + " with reason " + reason); + if (isShutdown) { + LOG.warn("Record processor for shardId: " + shardId + " has been shutdown multiple times."); + return; + } + switch (reason) { + case TERMINATE: + emit(checkpointer, transformToOutput(buffer.getRecords())); + try { + checkpointer.checkpoint(); + } catch (KinesisClientLibDependencyException | InvalidStateException | ThrottlingException | ShutdownException e) { + LOG.error(e); + } + break; + case ZOMBIE: + break; + default: + throw new IllegalStateException("invalid shutdown reason"); + } + emitter.shutdown(); + isShutdown = true; + } + +} \ No newline at end of file diff --git a/src/main/java/com/sumologic/kinesis/KinesisConnectorRecordProcessorFactory.java b/src/main/java/com/sumologic/kinesis/KinesisConnectorRecordProcessorFactory.java new file mode 100644 index 0000000..2144e62 --- /dev/null +++ b/src/main/java/com/sumologic/kinesis/KinesisConnectorRecordProcessorFactory.java @@ -0,0 +1,43 @@ +package com.sumologic.kinesis; + +import com.amazonaws.services.kinesis.clientlibrary.interfaces.IRecordProcessor; +import com.amazonaws.services.kinesis.clientlibrary.interfaces.IRecordProcessorFactory; +import com.amazonaws.services.kinesis.connectors.KinesisConnectorConfiguration; +import com.amazonaws.services.kinesis.connectors.interfaces.IBuffer; +import com.amazonaws.services.kinesis.connectors.interfaces.IEmitter; +import com.amazonaws.services.kinesis.connectors.interfaces.IFilter; +import com.amazonaws.services.kinesis.connectors.interfaces.IKinesisConnectorPipeline; +import com.amazonaws.services.kinesis.connectors.interfaces.ITransformerBase; + +/** + * This class is used to generate KinesisConnectorRecordProcessors that operate using the user's + * implemented classes. The createProcessor() method sets the dependencies of the + * KinesisConnectorRecordProcessor that are specified in the KinesisConnectorPipeline argument, + * which accesses instances of the users implementations. + */ +public class KinesisConnectorRecordProcessorFactory implements IRecordProcessorFactory { + + private IKinesisConnectorPipeline pipeline; + private KinesisConnectorConfiguration configuration; + + public KinesisConnectorRecordProcessorFactory(IKinesisConnectorPipeline pipeline, + KinesisConnectorConfiguration configuration) { + this.configuration = configuration; + this.pipeline = pipeline; + } + + @Override + public IRecordProcessor createProcessor() { + try { + IBuffer buffer = pipeline.getBuffer(configuration); + IEmitter emitter = pipeline.getEmitter(configuration); + ITransformerBase transformer = pipeline.getTransformer(configuration); + IFilter filter = pipeline.getFilter(configuration); + KinesisConnectorRecordProcessor processor = + new KinesisConnectorRecordProcessor(buffer, filter, emitter, transformer, configuration); + return processor; + } catch (Throwable t) { + throw new RuntimeException(t); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/sumologic/kinesis/StreamSource.java b/src/main/java/com/sumologic/kinesis/StreamSource.java index 0290b5d..ab74b37 100644 --- a/src/main/java/com/sumologic/kinesis/StreamSource.java +++ b/src/main/java/com/sumologic/kinesis/StreamSource.java @@ -6,12 +6,10 @@ import java.io.InputStreamReader; import java.nio.ByteBuffer; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; +import org.apache.log4j.Logger; -import com.sumologic.client.SimpleKinesisMessageModel; +import com.sumologic.client.model.SimpleKinesisMessageModel; import com.sumologic.kinesis.utils.KinesisUtils; - import com.amazonaws.auth.AWSCredentialsProvider; import com.amazonaws.regions.RegionUtils; import com.amazonaws.services.kinesis.AmazonKinesisClient; @@ -25,7 +23,7 @@ * stream defined in the KinesisConnectorConfiguration. */ public class StreamSource implements Runnable { - private static Log LOG = LogFactory.getLog(StreamSource.class); + private static final Logger LOG = Logger.getLogger(StreamSource.class.getName()); protected AmazonKinesisClient kinesisClient; protected KinesisConnectorConfiguration config; protected final String inputFile; @@ -106,7 +104,8 @@ protected void processInputStream(InputStream inputStream, int iteration) throws String line; int lines = 0; while ((line = br.readLine()) != null) { - SimpleKinesisMessageModel kinesisMessageModel = objectMapper.readValue(line, SimpleKinesisMessageModel.class); + SimpleKinesisMessageModel kinesisMessageModel = new SimpleKinesisMessageModel(line); + //SimpleKinesisMessageModel kinesisMessageModel = objectMapper.readValue(line, SimpleKinesisMessageModel.class); PutRecordRequest putRecordRequest = new PutRecordRequest(); putRecordRequest.setStreamName(config.KINESIS_INPUT_STREAM); diff --git a/src/test/java/com/sumologic/client/CloudWatchMessageModelSumologicTransformerTest.java b/src/test/java/com/sumologic/client/CloudWatchMessageModelSumologicTransformerTest.java index 1588a77..dab6ad0 100644 --- a/src/test/java/com/sumologic/client/CloudWatchMessageModelSumologicTransformerTest.java +++ b/src/test/java/com/sumologic/client/CloudWatchMessageModelSumologicTransformerTest.java @@ -7,8 +7,8 @@ import org.junit.Ignore; import com.amazonaws.services.kinesis.model.Record; -import com.sumologic.client.CloudWatchMessageModelSumologicTransformer; -import com.sumologic.client.SimpleKinesisMessageModel; +import com.sumologic.client.model.CloudWatchLogsMessageModel; +import com.sumologic.client.model.SimpleKinesisMessageModel; import java.io.IOException; import java.nio.charset.Charset; @@ -61,7 +61,7 @@ public void theTransformerShouldSucceedWhenTransformingAProperJSON() { + "}" +""; - byte[] compressData = SumologicSender.compressGzip(jsonData); + byte[] compressData = SumologicKinesisUtils.compressGzip(jsonData); ByteBuffer bufferedData = null; try { @@ -97,7 +97,7 @@ public void theTransformerShouldFailWhenTransformingAJSONWithTrailingCommas() { + "}" +""; - byte[] compressData = SumologicSender.compressGzip(jsonData); + byte[] compressData = SumologicKinesisUtils.compressGzip(jsonData); ByteBuffer bufferedData = null; try { @@ -139,7 +139,7 @@ public void theTransfomerShouldSeparateBatchesOfLogs() { + "}" +""; - byte[] compressData = SumologicSender.compressGzip(jsonData); + byte[] compressData = SumologicKinesisUtils.compressGzip(jsonData); ByteBuffer bufferedData = null; try { diff --git a/src/test/java/com/sumologic/client/SumologicKinesisUtilsTest.java b/src/test/java/com/sumologic/client/SumologicKinesisUtilsTest.java new file mode 100644 index 0000000..5982273 --- /dev/null +++ b/src/test/java/com/sumologic/client/SumologicKinesisUtilsTest.java @@ -0,0 +1,56 @@ +package com.sumologic.client; + +import org.junit.Assert; +import org.junit.Test; + +public class SumologicKinesisUtilsTest { + @Test + public void compressDecompressGzipTest() { + String data = "a string of characters"; + + byte[] compressData = SumologicKinesisUtils.compressGzip(data); + String result = SumologicKinesisUtils.decompressGzip(compressData); + + Assert.assertTrue(data.equals(result)); + } + + @Test + public void properJSONVerificationShouldReturnTrue() { + String jsonData = "" + +"{" + + "\"logEvents\": [{" + + "\"id\": \"3889492387492837492374982374897239847289374892\"," + + "\"message\": \"1 23423532532 eni-ac9342k3492 10.1.1.75 66.175.209.17 123 123 17 1 76 1437755534 1437755549 ACCEPT OK\"," + + "\"timestamp\": \"2342342342300\"" + + "}]," + + "\"logGroup\": \"MyFirstVPC\"," + + "\"logStream\": \"eni-ac6a7de4-all\"," + + "\"messageType\": \"DATA_MESSAGE\"," + + "\"owner\": \"2342352352\"," + + "\"subscriptionFilters\": [\"MyFirstVPC\"]" + + "}" + +""; + + Assert.assertTrue(SumologicKinesisUtils.verifyJSON(jsonData)); + } + + @Test + public void malformedJSONVerificationShouldReturnTrue() { + String jsonData = "" + +"{" + + "\"logEvents\": [{" + + "\"id\": \"3889492387492837492374982374897239847289374892\"," + + "\"message\": \"1 23423532532 eni-ac9342k3492 10.1.1.75 66.175.209.17 123 123 17 1 76 1437755534 1437755549 ACCEPT OK\"," + + "\"timestamp\": \"2342342342300\"" + + "}]," + + "\"logGroup\": \"MyFirstVPC\"," + + "\"logStream\": \"eni-ac6a7de4-all\"," + + "\"messageType\": \"DATA_MESSAGE\"," + + "\"owner\": \"2342352352\"," + + "\"subscriptionFilters\": [\"MyFirstVPC\"]," + + "}" + +""; + + Assert.assertFalse(SumologicKinesisUtils.verifyJSON(jsonData)); + } +} \ No newline at end of file diff --git a/src/test/java/com/sumologic/client/SumologicSenderTest.java b/src/test/java/com/sumologic/client/SumologicSenderTest.java index 1d9c54d..215ac3f 100644 --- a/src/test/java/com/sumologic/client/SumologicSenderTest.java +++ b/src/test/java/com/sumologic/client/SumologicSenderTest.java @@ -16,7 +16,6 @@ import com.github.tomakehurst.wiremock.client.WireMock; import com.github.tomakehurst.wiremock.junit.WireMockRule; -import com.sumologic.client.CloudWatchMessageModelSumologicTransformer; import com.sumologic.client.SumologicSender; import com.sumologic.client.implementations.SumologicEmitter; @@ -68,18 +67,7 @@ public void theSenderShouldReturnTrueOnSuccess () { } } - @Test - public void decompressGzipTest() { - String url = MOCKED_HOST + MOCKED_COLLECTION; - - String data = "a string of characters"; - - byte[] compressData = SumologicSender.compressGzip(data); - String result = CloudWatchMessageModelSumologicTransformer.decompressGzip(compressData); - - Assert.assertTrue(data.equals(result)); - } - + private void mockEmitMessages () { WireMock.stubFor(WireMock.post(WireMock.urlMatching(MOCKED_COLLECTION)) .willReturn(WireMock.aResponse()