Skip to content

Commit fc64324

Browse files
vivetiwavivetiwa
andauthored
ISSUE-36 Use kafka connect standard dead letter and logger error reporter (#37)
* ISSUE-36 Use kafka connect standard dead letter and logger error reporter * ISSUE-36 Base image point to github repo docker repository Co-authored-by: vivetiwa <[email protected]>
1 parent ec2c9ee commit fc64324

File tree

16 files changed

+117
-410
lines changed

16 files changed

+117
-410
lines changed

DEVELOPER_GUIDE.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,6 @@ AEP Sink connector configurations can be supplied in the call register the conne
4444
| aep.connection.auth.client.code | IMS client code | | no | |
4545
| aep.connection.auth.client.secret | IME client secret | | no | |
4646
| aep.flush.bytes.kb | bytes threshold to determine the batch | 4 | no | |
47-
| aep.error.logger | put failed message to dead letter topic or log | none | no | kafka, log, both, none |
48-
| aep.error.topic | deadletter topic name | none | no | |
4947

5048
## Step-by-Step Workflow
5149

@@ -285,6 +283,11 @@ curl -s -X POST \
285283
}' http://localhost:8083/connectors
286284
```
287285

286+
#### Dead Letter Configuration
287+
To send error records to dead letter topic please use standard kafka connector error configuration.
288+
289+
Kafka connect dead letter configurations : `https://docs.confluent.io/platform/current/connect/concepts.html#dead-letter-queue`
290+
288291
#### Poxy host configuration
289292
There are 2 ways to route request to aep endpoint through proxy server :
290293
1. **Using Environment Variable** : Export poxyHost and proxyPort on each kafka node, then restart kafka connect node.

docker-compose.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ services:
7373
- kafka-rest-proxy
7474

7575
kafka-connect:
76-
image: streaming-connect
76+
image: ghcr.io/adobe/experience-platform-streaming-connect
7777
hostname: kafka-connect
7878
ports:
7979
- "8083:8083"

streaming-connect-common/src/main/java/com/adobe/platform/streaming/http/HttpException.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,4 +40,9 @@ public HttpException(String message, Throwable cause, int responseCode) {
4040
public int getResponseCode() {
4141
return responseCode;
4242
}
43+
44+
@Override
45+
public String getMessage() {
46+
return String.format("Message : %s, ResponseCode : %s", super.getMessage(), this.responseCode);
47+
}
4348
}

streaming-connect-sink/src/main/java/com/adobe/platform/streaming/sink/AbstractAEPPublisher.java

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,6 @@ public abstract class AbstractAEPPublisher implements DataPublisher {
6464

6565
private static final String AEP_CONNECTION_AUTH_ENABLED_VALUE = "true";
6666
private static final String AEP_CONNECTION_AUTH_DISABLED_VALUE = "false";
67-
public static final String AEP_ERROR_LOGGER = "aep.error.logger";
68-
public static final String AEP_ERROR_TOPIC = "aep.error.topic";
6967

7068
protected HttpProducer getHttpProducer(Map<String, String> props) throws AEPStreamingException {
7169
return HttpProducer.newBuilder(getAepEndpoint(props.get(AEP_ENDPOINT)))

streaming-connect-sink/src/main/java/com/adobe/platform/streaming/sink/AbstractSinkTask.java

Lines changed: 9 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -19,22 +19,20 @@
1919
import com.google.gson.GsonBuilder;
2020

2121
import org.apache.commons.collections4.CollectionUtils;
22+
import org.apache.commons.lang3.tuple.Pair;
2223
import org.apache.kafka.common.utils.AppInfoParser;
23-
import org.apache.kafka.common.utils.Utils;
2424
import org.apache.kafka.connect.errors.ConnectException;
25-
import org.apache.kafka.connect.runtime.WorkerConfig;
25+
import org.apache.kafka.connect.sink.ErrantRecordReporter;
2626
import org.apache.kafka.connect.sink.SinkRecord;
2727
import org.apache.kafka.connect.sink.SinkTask;
2828
import org.apache.kafka.connect.sink.SinkTaskContext;
29-
import org.codehaus.plexus.util.ReflectionUtils;
3029
import org.slf4j.Logger;
3130
import org.slf4j.LoggerFactory;
3231

3332
import java.util.ArrayList;
3433
import java.util.Collection;
3534
import java.util.List;
3635
import java.util.Map;
37-
import java.util.Objects;
3836

3937
/**
4038
* @author Adobe Inc.
@@ -55,7 +53,7 @@ public abstract class AbstractSinkTask<T> extends SinkTask {
5553
private int flushIntervalMillis;
5654
private int flushBytesCount;
5755
private long lastFlushMilliSec = System.currentTimeMillis();
58-
private String bootstrapServers;
56+
private ErrantRecordReporter errantRecordReporter;
5957

6058
@Override
6159
public String version() {
@@ -65,21 +63,18 @@ public String version() {
6563
@Override
6664
public void initialize(SinkTaskContext context) {
6765
super.initialize(context);
68-
bootstrapServers = getBootstrapServers(context);
66+
errantRecordReporter = context.errantRecordReporter();
6967
}
7068

7169
@Override
7270
public void start(Map<String, String> props) {
7371
LOG.info("Started Sink Task with props: {}", props);
74-
if (Objects.nonNull(bootstrapServers)) {
75-
props.put(WorkerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers);
76-
}
7772

7873
try {
7974
flushIntervalMillis = SinkUtils.getProperty(props, FLUSH_INTERVAL_SECS, DEFAULT_FLUSH_INTERVAL, MILLIS_IN_A_SEC);
8075
flushBytesCount = SinkUtils.getProperty(props, FLUSH_BYTES_KB, DEFAULT_FLUSH_BYTES_KB, BYTES_IN_A_KB);
8176

82-
init(props);
77+
init(props, errantRecordReporter);
8378
LOG.info("Connection created with flush interval {} secs and flush bytes {} KB",
8479
flushIntervalMillis / MILLIS_IN_A_SEC, flushBytesCount / BYTES_IN_A_KB);
8580
} catch (AEPStreamingException aepStreamingException) {
@@ -104,7 +99,7 @@ public void put(Collection<SinkRecord> records) {
10499

105100
List<T> eventsToPublish = new ArrayList<>();
106101
for (SinkRecord record : records) {
107-
T dataToPublish = getDataToPublish(SinkUtils.getStringPayload(GSON, record));
102+
T dataToPublish = getDataToPublish(Pair.of(SinkUtils.getStringPayload(GSON, record), record));
108103
eventsToPublish.add(dataToPublish);
109104
bytesRead += getPayloadLength(dataToPublish);
110105

@@ -127,9 +122,10 @@ public void put(Collection<SinkRecord> records) {
127122
}
128123
}
129124

130-
public abstract void init(Map<String, String> properties) throws AEPStreamingException;
125+
public abstract void init(Map<String, String> properties,
126+
ErrantRecordReporter errantRecordReporter) throws AEPStreamingException;
131127

132-
public abstract T getDataToPublish(String sinkRecord);
128+
public abstract T getDataToPublish(Pair<String, SinkRecord> sinkRecord);
133129

134130
public abstract int getPayloadLength(T dataToPublish);
135131

@@ -150,19 +146,6 @@ private void publishAndLogIfRequired(List<T> eventsToPublish) {
150146
}
151147
}
152148

153-
private String getBootstrapServers(SinkTaskContext context) {
154-
try {
155-
Object workerSinkTask = ReflectionUtils.getValueIncludingSuperclasses("sinkTask", context);
156-
WorkerConfig workerConfig = (WorkerConfig) ReflectionUtils
157-
.getValueIncludingSuperclasses("workerConfig", workerSinkTask);
158-
159-
return Utils.join(workerConfig.getList(WorkerConfig.BOOTSTRAP_SERVERS_CONFIG), ",");
160-
} catch (IllegalAccessException exception) {
161-
LOG.error("Failed to get bootstrap server.", exception);
162-
}
163-
return null;
164-
}
165-
166149
private void reset(List<T> eventsToPublish, long tempCurrentTime) {
167150
lastFlushMilliSec = tempCurrentTime;
168151
bytesRead = 0;

streaming-connect-sink/src/main/java/com/adobe/platform/streaming/sink/DataPublisher.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
package com.adobe.platform.streaming.sink;
1414

1515
import com.adobe.platform.streaming.AEPStreamingException;
16+
import org.apache.commons.lang3.tuple.Pair;
17+
import org.apache.kafka.connect.sink.SinkRecord;
1618

1719
import java.util.List;
1820

@@ -23,7 +25,7 @@ public interface DataPublisher {
2325

2426
void start();
2527

26-
void publishData(List<String> messages) throws AEPStreamingException;
28+
void publishData(List<Pair<String, SinkRecord>> messages) throws AEPStreamingException;
2729

2830
void stop();
2931
}

streaming-connect-sink/src/main/java/com/adobe/platform/streaming/sink/impl/AEPPublisher.java

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -18,19 +18,20 @@
1818
import com.adobe.platform.streaming.http.HttpProducer;
1919
import com.adobe.platform.streaming.http.HttpUtil;
2020
import com.adobe.platform.streaming.sink.AbstractAEPPublisher;
21-
import com.adobe.platform.streaming.sink.impl.reporter.CompositeErrorReporter;
22-
import com.adobe.platform.streaming.sink.impl.reporter.ErrorReporter;
2321
import org.apache.commons.collections4.CollectionUtils;
2422
import org.apache.commons.lang3.StringUtils;
23+
import org.apache.commons.lang3.tuple.Pair;
2524
import org.apache.http.entity.ContentType;
25+
import org.apache.kafka.connect.sink.ErrantRecordReporter;
26+
import org.apache.kafka.connect.sink.SinkRecord;
2627
import org.json.JSONArray;
2728
import org.json.JSONObject;
2829
import org.slf4j.Logger;
2930
import org.slf4j.LoggerFactory;
3031

3132
import java.util.List;
3233
import java.util.Map;
33-
import java.util.concurrent.Future;
34+
import java.util.Objects;
3435

3536
/**
3637
* @author Adobe Inc.
@@ -42,24 +43,28 @@ public class AEPPublisher extends AbstractAEPPublisher {
4243

4344
private int count;
4445
private final HttpProducer producer;
45-
private final ErrorReporter<List<Future<?>>> errorReporter;
46+
private final ErrantRecordReporter errorReporter;
4647

47-
AEPPublisher(Map<String, String> props) throws AEPStreamingException {
48+
AEPPublisher(Map<String, String> props, ErrantRecordReporter errantRecordReporter) throws AEPStreamingException {
4849
count = 0;
4950
producer = getHttpProducer(props);
50-
errorReporter = new CompositeErrorReporter(props);
51+
errorReporter = errantRecordReporter;
5152
}
5253

5354
@Override
54-
public void publishData(List<String> messages) throws AEPStreamingException {
55+
public void publishData(List<Pair<String, SinkRecord>> messages) throws AEPStreamingException {
5556
if (CollectionUtils.isEmpty(messages)) {
5657
LOG.debug("No messages to publish");
5758
return;
5859
}
5960

6061
try {
61-
JSONArray jsonMessages = new JSONArray();
62-
messages.stream().map(JSONObject::new).forEach(jsonMessages::put);
62+
final JSONArray jsonMessages = new JSONArray();
63+
messages.stream()
64+
.map(Pair::getKey)
65+
.map(JSONObject::new)
66+
.forEach(jsonMessages::put);
67+
6368
JSONObject payload = new JSONObject();
6469
payload.put(MESSAGES_KEY, jsonMessages);
6570

@@ -74,7 +79,9 @@ public void publishData(List<String> messages) throws AEPStreamingException {
7479
LOG.debug("Successfully published data to Adobe Experience Platform: {}", response);
7580
} catch (HttpException httpException) {
7681
LOG.error("Failed to publish data to Adobe Experience Platform", httpException);
77-
errorReporter.report(messages, httpException);
82+
if (Objects.nonNull(errorReporter)) {
83+
messages.forEach(message -> errorReporter.report(message.getValue(), httpException));
84+
}
7885
if (HttpUtil.is500(httpException.getResponseCode()) || HttpUtil.isUnauthorized(httpException.getResponseCode())) {
7986
throw new AEPStreamingException("Failed to publish", httpException);
8087
}

streaming-connect-sink/src/main/java/com/adobe/platform/streaming/sink/impl/AEPSinkTask.java

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,35 +15,38 @@
1515
import com.adobe.platform.streaming.AEPStreamingException;
1616
import com.adobe.platform.streaming.sink.AbstractSinkTask;
1717
import com.adobe.platform.streaming.sink.DataPublisher;
18+
import org.apache.commons.lang3.tuple.Pair;
19+
import org.apache.kafka.connect.sink.ErrantRecordReporter;
20+
import org.apache.kafka.connect.sink.SinkRecord;
1821

1922
import java.util.List;
2023
import java.util.Map;
2124

2225
/**
2326
* @author Adobe Inc.
2427
*/
25-
public class AEPSinkTask extends AbstractSinkTask<String> {
28+
public class AEPSinkTask extends AbstractSinkTask<Pair<String, SinkRecord>> {
2629

2730
private DataPublisher publisher;
2831

2932
@Override
30-
public void init(Map<String, String> props) throws AEPStreamingException {
31-
publisher = new AEPPublisher(props);
33+
public void init(Map<String, String> props, ErrantRecordReporter errantRecordReporter) throws AEPStreamingException {
34+
publisher = new AEPPublisher(props, errantRecordReporter);
3235
publisher.start();
3336
}
3437

3538
@Override
36-
public String getDataToPublish(String sinkRecord) {
39+
public Pair<String, SinkRecord> getDataToPublish(Pair<String, SinkRecord> sinkRecord) {
3740
return sinkRecord;
3841
}
3942

4043
@Override
41-
public int getPayloadLength(String dataToPublish) {
42-
return dataToPublish.length();
44+
public int getPayloadLength(Pair<String, SinkRecord> dataToPublish) {
45+
return dataToPublish.getKey().length();
4346
}
4447

4548
@Override
46-
public void publishData(List<String> eventDataList) throws AEPStreamingException {
49+
public void publishData(List<Pair<String, SinkRecord>> eventDataList) throws AEPStreamingException {
4750
publisher.publishData(eventDataList);
4851
}
4952

streaming-connect-sink/src/main/java/com/adobe/platform/streaming/sink/impl/reporter/CompositeErrorReporter.java

Lines changed: 0 additions & 81 deletions
This file was deleted.

0 commit comments

Comments
 (0)