-
Notifications
You must be signed in to change notification settings - Fork 202
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
GitHub-Issue#2778: Added CouldWatchLogsService, Tests and RetransmissionException #3023
Changes from 44 commits
f4dc9da
4387b29
f1c25bb
9d640e5
b100ee3
35859b0
7040186
6b13e21
3bb125a
e5ee1e5
e90b05a
43a0a40
40cb280
1aac686
903ea26
ffe5cbe
b56a845
b06ed0b
7f5a432
4576899
a539833
c89ea17
063e1d3
1aad0b5
cf1f8e1
77f6d0f
75d90fe
5f2f511
fdc5b00
0399694
3c02e1d
c2a02ec
abec5e3
9bbfedd
6e28adc
90418aa
5971190
53086c4
26f18a1
a5b8be7
070beef
6130b08
69320ec
993cbd0
2b18115
eee6197
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,163 @@ | ||
/* | ||
* Copyright OpenSearch Contributors | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
package org.opensearch.dataprepper.plugins.sink.client; | ||
|
||
import lombok.Builder; | ||
import org.opensearch.dataprepper.model.event.EventHandle; | ||
import org.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
import software.amazon.awssdk.core.exception.SdkClientException; | ||
import software.amazon.awssdk.services.cloudwatchlogs.CloudWatchLogsClient; | ||
import software.amazon.awssdk.services.cloudwatchlogs.model.CloudWatchLogsException; | ||
import software.amazon.awssdk.services.cloudwatchlogs.model.InputLogEvent; | ||
import software.amazon.awssdk.services.cloudwatchlogs.model.PutLogEventsRequest; | ||
|
||
import java.nio.charset.StandardCharsets; | ||
import java.util.ArrayList; | ||
import java.util.Collection; | ||
import java.util.List; | ||
import java.util.concurrent.Executor; | ||
|
||
@Builder | ||
public class CloudWatchLogsDispatcher { | ||
private static final long UPPER_RETRY_TIME_BOUND_MILLISECONDS = 2000; | ||
private static final float EXP_TIME_SCALE = 1.25F; | ||
private static final Logger LOG = LoggerFactory.getLogger(CloudWatchLogsDispatcher.class); | ||
private CloudWatchLogsClient cloudWatchLogsClient; | ||
private CloudWatchLogsMetrics cloudWatchLogsMetrics; | ||
private Executor executor; | ||
private String logGroup; | ||
private String logStream; | ||
private int retryCount; | ||
private long backOffTimeBase; | ||
public CloudWatchLogsDispatcher(final CloudWatchLogsClient cloudWatchLogsClient, | ||
final CloudWatchLogsMetrics cloudWatchLogsMetrics, | ||
final Executor executor, | ||
final String logGroup, final String logStream, | ||
final int retryCount, final long backOffTimeBase) { | ||
this.cloudWatchLogsClient = cloudWatchLogsClient; | ||
this.cloudWatchLogsMetrics = cloudWatchLogsMetrics; | ||
this.logGroup = logGroup; | ||
this.logStream = logStream; | ||
this.retryCount = retryCount; | ||
this.backOffTimeBase = backOffTimeBase; | ||
|
||
this.executor = executor; | ||
} | ||
|
||
/** | ||
* Will read in a collection of log messages in byte form and transform them into a collection of InputLogEvents. | ||
* @param eventMessageBytes Collection of byte arrays holding event messages. | ||
* @return List of InputLogEvents holding the wrapped event messages. | ||
*/ | ||
public List<InputLogEvent> prepareInputLogEvents(final Collection<byte[]> eventMessageBytes) { | ||
List<InputLogEvent> logEventList = new ArrayList<>(); | ||
|
||
/** | ||
* Current implementation, timestamp is generated by system time during transmission. | ||
MaGonzalMayedo marked this conversation as resolved.
Show resolved
Hide resolved
|
||
* To properly extract timestamp we need to order the InputLogEvents. Can be done by | ||
* refactoring buffer class with timestamp param, or adding a sorting algorithm in between | ||
* making the PLE object (in prepareInputLogEvents). | ||
*/ | ||
|
||
for (byte[] data : eventMessageBytes) { | ||
InputLogEvent tempLogEvent = InputLogEvent.builder() | ||
.message(new String(data, StandardCharsets.UTF_8)) | ||
.timestamp(System.currentTimeMillis()) | ||
.build(); | ||
logEventList.add(tempLogEvent); | ||
} | ||
|
||
return logEventList; | ||
} | ||
|
||
public void dispatchLogs(List<InputLogEvent> inputLogEvents, Collection<EventHandle> eventHandles) { | ||
PutLogEventsRequest putLogEventsRequest = PutLogEventsRequest.builder() | ||
.logEvents(inputLogEvents) | ||
.logGroupName(logGroup) | ||
.logStreamName(logStream) | ||
.build(); | ||
|
||
executor.execute(Uploader.builder() | ||
.cloudWatchLogsClient(cloudWatchLogsClient) | ||
.cloudWatchLogsMetrics(cloudWatchLogsMetrics) | ||
.putLogEventsRequest(putLogEventsRequest) | ||
.eventHandles(eventHandles) | ||
.backOffTimeBase(backOffTimeBase) | ||
.retryCount(retryCount) | ||
.build()); | ||
} | ||
|
||
@Builder | ||
protected static class Uploader implements Runnable { | ||
private final CloudWatchLogsClient cloudWatchLogsClient; | ||
private final CloudWatchLogsMetrics cloudWatchLogsMetrics; | ||
private final PutLogEventsRequest putLogEventsRequest; | ||
private final Collection<EventHandle> eventHandles; | ||
private final int retryCount; | ||
private final long backOffTimeBase; | ||
|
||
@Override | ||
public void run() { | ||
upload(); | ||
} | ||
|
||
public void upload() { | ||
boolean failedToTransmit = true; | ||
int failCount = 0; | ||
|
||
try { | ||
while (failedToTransmit && (failCount < retryCount)) { | ||
try { | ||
cloudWatchLogsClient.putLogEvents(putLogEventsRequest); | ||
|
||
cloudWatchLogsMetrics.increaseRequestSuccessCounter(1); | ||
failedToTransmit = false; | ||
|
||
} catch (CloudWatchLogsException | SdkClientException e) { | ||
LOG.error("Failed to push logs with error: {}", e.getMessage()); | ||
cloudWatchLogsMetrics.increaseRequestFailCounter(1); | ||
Thread.sleep(calculateBackOffTime(backOffTimeBase, failCount)); | ||
failCount++; | ||
} | ||
} | ||
} catch (InterruptedException e) { | ||
LOG.warn("Got interrupted while waiting!"); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Better log needed here. This doesn't tell the customer anything. Interrupted by what? stacktrace? while waiting for what? in what class? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thank you, I have added a more appropriate message alongside the actual thrown error message. |
||
//TODO: Push to DLQ. | ||
Thread.currentThread().interrupt(); | ||
} | ||
|
||
|
||
if (failedToTransmit) { | ||
cloudWatchLogsMetrics.increaseLogEventFailCounter(eventHandles.size()); | ||
releaseEventHandles(false, eventHandles); | ||
} else { | ||
cloudWatchLogsMetrics.increaseLogEventSuccessCounter(eventHandles.size()); | ||
releaseEventHandles(true, eventHandles); | ||
} | ||
} | ||
|
||
private long calculateBackOffTime(final long backOffTimeBase, final int failCounter) { | ||
long scale = (long)Math.pow(EXP_TIME_SCALE, failCounter); | ||
|
||
if (scale >= UPPER_RETRY_TIME_BOUND_MILLISECONDS) { | ||
return UPPER_RETRY_TIME_BOUND_MILLISECONDS; | ||
} | ||
|
||
return scale * backOffTimeBase; | ||
} | ||
|
||
private void releaseEventHandles(final boolean result, final Collection<EventHandle> eventHandles) { | ||
if (eventHandles.isEmpty()) { | ||
return; | ||
} | ||
|
||
for (EventHandle eventHandle : eventHandles) { | ||
eventHandle.release(result); | ||
} | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
/* | ||
* Copyright OpenSearch Contributors | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
package org.opensearch.dataprepper.plugins.sink.client; | ||
|
||
import io.micrometer.core.instrument.Counter; | ||
import org.opensearch.dataprepper.metrics.PluginMetrics; | ||
|
||
/** | ||
* Class is meant to abstract the metric book-keeping of | ||
* CloudWatchLogs metrics so that multiple instances | ||
* may refer to it. | ||
*/ | ||
public class CloudWatchLogsMetrics { | ||
protected static final String CLOUDWATCH_LOGS_REQUESTS_SUCCEEDED = "cloudWatchLogsRequestsSucceeded"; | ||
protected static final String CLOUDWATCH_LOGS_EVENTS_SUCCEEDED = "cloudWatchLogsEventsSucceeded"; | ||
protected static final String CLOUDWATCH_LOGS_EVENTS_FAILED = "cloudWatchLogsEventsFailed"; | ||
protected static final String CLOUDWATCH_LOGS_REQUESTS_FAILED = "cloudWatchLogsRequestsFailed"; | ||
private final Counter logEventSuccessCounter; | ||
private final Counter logEventFailCounter; | ||
private final Counter requestSuccessCount; | ||
private final Counter requestFailCount; | ||
|
||
public CloudWatchLogsMetrics(final PluginMetrics pluginMetrics) { | ||
this.logEventSuccessCounter = pluginMetrics.counter(CloudWatchLogsMetrics.CLOUDWATCH_LOGS_EVENTS_SUCCEEDED); | ||
this.requestFailCount = pluginMetrics.counter(CloudWatchLogsMetrics.CLOUDWATCH_LOGS_REQUESTS_FAILED); | ||
this.logEventFailCounter = pluginMetrics.counter(CloudWatchLogsMetrics.CLOUDWATCH_LOGS_EVENTS_FAILED); | ||
this.requestSuccessCount = pluginMetrics.counter(CloudWatchLogsMetrics.CLOUDWATCH_LOGS_REQUESTS_SUCCEEDED); | ||
} | ||
|
||
public void increaseLogEventSuccessCounter(int value) { | ||
logEventSuccessCounter.increment(value); | ||
} | ||
|
||
public void increaseRequestSuccessCounter(int value) { | ||
requestSuccessCount.increment(value); | ||
} | ||
|
||
public void increaseLogEventFailCounter(int value) { | ||
logEventFailCounter.increment(value); | ||
} | ||
|
||
public void increaseRequestFailCounter(int value) { | ||
requestFailCount.increment(value); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the
createOverrideConfiguration()
method you use nested builder. It is better practice to use lambdas instead (easier to read).There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you! I have added the lambda to increase code readability.