diff --git a/README.md b/README.md
index 13ebbee0..6412a8ac 100644
--- a/README.md
+++ b/README.md
@@ -81,6 +81,31 @@ public class PushOneDocument {
```
+## Logging
+When pushing multiple documents into your source using a service (e.g. `PushService`, `StreamService`), make sure to configure a **logger** to be able to see what happens.
+to do so .. in your `resources` folder.
+
+### Log4j2 XML Configuration Example
+To log execution output into the console, use the below `log4j2.xml` configuration:
+```xml
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+See [Log4j2 configuration](https://logging.apache.org/log4j/2.x/manual/configuration.html) for more details.
+
## Local Setup to Contribute
### Formatting
diff --git a/pom.xml b/pom.xml
index 8e412905..98a4b73c 100644
--- a/pom.xml
+++ b/pom.xml
@@ -130,6 +130,11 @@
+
+ org.apache.logging.log4j
+ log4j-core
+ 2.20.0
+
com.google.code.gson
gson
diff --git a/src/main/java/com/coveo/pushapiclient/ApiCore.java b/src/main/java/com/coveo/pushapiclient/ApiCore.java
index 5e07feb3..50f575f6 100644
--- a/src/main/java/com/coveo/pushapiclient/ApiCore.java
+++ b/src/main/java/com/coveo/pushapiclient/ApiCore.java
@@ -6,17 +6,22 @@
import java.net.http.HttpRequest;
import java.net.http.HttpRequest.BodyPublisher;
import java.net.http.HttpResponse;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
// TODO: LENS-934 - Support throttling
class ApiCore {
private final HttpClient httpClient;
+ private final Logger logger;
public ApiCore() {
this.httpClient = HttpClient.newHttpClient();
+ this.logger = LogManager.getLogger(ApiCore.class);
}
- public ApiCore(HttpClient httpClient) {
+ public ApiCore(HttpClient httpClient, Logger logger) {
this.httpClient = httpClient;
+ this.logger = logger;
}
public HttpResponse post(URI uri, String[] headers)
@@ -26,26 +31,60 @@ public HttpResponse post(URI uri, String[] headers)
public HttpResponse post(URI uri, String[] headers, BodyPublisher body)
throws IOException, InterruptedException {
+ this.logger.debug("POST " + uri);
HttpRequest request = HttpRequest.newBuilder().headers(headers).uri(uri).POST(body).build();
- return this.httpClient.send(request, HttpResponse.BodyHandlers.ofString());
+ HttpResponse response =
+ this.httpClient.send(request, HttpResponse.BodyHandlers.ofString());
+ this.logResponse(response);
+ return response;
}
public HttpResponse put(URI uri, String[] headers, BodyPublisher body)
throws IOException, InterruptedException {
+ this.logger.debug("PUT " + uri);
HttpRequest request = HttpRequest.newBuilder().headers(headers).uri(uri).PUT(body).build();
- return this.httpClient.send(request, HttpResponse.BodyHandlers.ofString());
+ HttpResponse response =
+ this.httpClient.send(request, HttpResponse.BodyHandlers.ofString());
+ this.logResponse(response);
+ return response;
}
public HttpResponse delete(URI uri, String[] headers)
throws IOException, InterruptedException {
+ this.logger.debug("DELETE " + uri);
HttpRequest request = HttpRequest.newBuilder().headers(headers).uri(uri).DELETE().build();
- return this.httpClient.send(request, HttpResponse.BodyHandlers.ofString());
+ HttpResponse response =
+ this.httpClient.send(request, HttpResponse.BodyHandlers.ofString());
+ this.logResponse(response);
+ return response;
}
public HttpResponse delete(URI uri, String[] headers, BodyPublisher body)
throws IOException, InterruptedException {
+ this.logger.debug("DELETE " + uri);
HttpRequest request =
HttpRequest.newBuilder().headers(headers).uri(uri).method("DELETE", body).build();
- return this.httpClient.send(request, HttpResponse.BodyHandlers.ofString());
+ HttpResponse response =
+ this.httpClient.send(request, HttpResponse.BodyHandlers.ofString());
+ this.logResponse(response);
+ return response;
+ }
+
+ private void logResponse(HttpResponse response) {
+ if (response == null) {
+ return;
+ }
+ int status = response.statusCode();
+ String method = response.request().method();
+ String statusMessage = method + " status: " + status;
+ String responseMessage = method + " response: " + response.body();
+
+ if (status < 200 || status >= 300) {
+ this.logger.error(statusMessage);
+ this.logger.error(responseMessage);
+ } else {
+ this.logger.debug(statusMessage);
+ this.logger.debug(responseMessage);
+ }
}
}
diff --git a/src/main/java/com/coveo/pushapiclient/DocumentUploadQueue.java b/src/main/java/com/coveo/pushapiclient/DocumentUploadQueue.java
index bd83a2bc..5e81f55f 100644
--- a/src/main/java/com/coveo/pushapiclient/DocumentUploadQueue.java
+++ b/src/main/java/com/coveo/pushapiclient/DocumentUploadQueue.java
@@ -2,9 +2,12 @@
import java.io.IOException;
import java.util.ArrayList;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
/** Represents a queue for uploading documents using a specified upload strategy */
class DocumentUploadQueue {
+ private static final Logger logger = LogManager.getLogger(DocumentUploadQueue.class);
private final UploadStrategy uploader;
private final int maxQueueSize = 5 * 1024 * 1024;
private ArrayList documentToAddList;
@@ -30,11 +33,14 @@ public DocumentUploadQueue(UploadStrategy uploader) {
*/
public void flush() throws IOException, InterruptedException {
if (this.isEmpty()) {
+ logger.debug("Empty batch. Skipping upload");
return;
}
- BatchUpdate batch = this.getBatch();
// TODO: LENS-871: support concurrent requests
+ BatchUpdate batch = this.getBatch();
+ logger.info("Uploading document batch");
this.uploader.apply(batch);
+
this.size = 0;
this.documentToAddList.clear();
this.documentToDeleteList.clear();
@@ -58,6 +64,7 @@ public void add(DocumentBuilder document) throws IOException, InterruptedExcepti
this.flush();
}
documentToAddList.add(document);
+ logger.info("Adding document to batch: " + document.getDocument().uri);
this.size += sizeOfDoc;
}
@@ -79,6 +86,7 @@ public void add(DeleteDocument document) throws IOException, InterruptedExceptio
this.flush();
}
documentToDeleteList.add(document);
+ logger.info("Adding document to batch: " + document.documentId);
this.size += sizeOfDoc;
}
diff --git a/src/main/java/com/coveo/pushapiclient/PlatformClient.java b/src/main/java/com/coveo/pushapiclient/PlatformClient.java
index d4b45cd2..a70bbb06 100644
--- a/src/main/java/com/coveo/pushapiclient/PlatformClient.java
+++ b/src/main/java/com/coveo/pushapiclient/PlatformClient.java
@@ -10,6 +10,7 @@
import java.util.Arrays;
import java.util.HashMap;
import java.util.stream.Stream;
+import org.apache.logging.log4j.LogManager;
/** PlatformClient handles network requests to the Coveo platform */
public class PlatformClient {
@@ -58,7 +59,7 @@ public PlatformClient(String apiKey, String organizationId, PlatformUrl platform
public PlatformClient(String apiKey, String organizationId, HttpClient httpClient) {
this.apiKey = apiKey;
this.organizationId = organizationId;
- this.api = new ApiCore(httpClient);
+ this.api = new ApiCore(httpClient, LogManager.getLogger(ApiCore.class));
this.platformUrl = new PlatformUrlBuilder().build();
}
diff --git a/src/main/java/com/coveo/pushapiclient/StreamService.java b/src/main/java/com/coveo/pushapiclient/StreamService.java
index 0213d720..3ba94f5e 100644
--- a/src/main/java/com/coveo/pushapiclient/StreamService.java
+++ b/src/main/java/com/coveo/pushapiclient/StreamService.java
@@ -4,6 +4,8 @@
import com.google.gson.Gson;
import java.io.IOException;
import java.net.http.HttpResponse;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
public class StreamService {
private final StreamEnabledSource source;
@@ -27,11 +29,13 @@ public StreamService(StreamEnabledSource source) {
String organizationId = source.getOrganizationId();
PlatformUrl platformUrl = source.getPlatformUrl();
UploadStrategy uploader = this.getUploadStrategy();
+ Logger logger = LogManager.getLogger(StreamService.class);
this.source = source;
this.queue = new DocumentUploadQueue(uploader);
this.platformClient = new PlatformClient(apiKey, organizationId, platformUrl);
- this.service = new StreamServiceInternal(this.source, this.queue, this.platformClient);
+
+ this.service = new StreamServiceInternal(this.source, this.queue, this.platformClient, logger);
}
/**
diff --git a/src/main/java/com/coveo/pushapiclient/StreamServiceInternal.java b/src/main/java/com/coveo/pushapiclient/StreamServiceInternal.java
index e5579752..04b7bf1f 100644
--- a/src/main/java/com/coveo/pushapiclient/StreamServiceInternal.java
+++ b/src/main/java/com/coveo/pushapiclient/StreamServiceInternal.java
@@ -4,19 +4,25 @@
import com.google.gson.Gson;
import java.io.IOException;
import java.net.http.HttpResponse;
+import org.apache.logging.log4j.Logger;
/** For internal use only. Made to easily test the service without having to use PowerMock */
class StreamServiceInternal {
+ private Logger logger;
private final StreamEnabledSource source;
private final PlatformClient platformClient;
private String streamId;
private DocumentUploadQueue queue;
public StreamServiceInternal(
- StreamEnabledSource source, DocumentUploadQueue queue, PlatformClient platformClient) {
+ StreamEnabledSource source,
+ DocumentUploadQueue queue,
+ PlatformClient platformClient,
+ Logger logger) {
this.source = source;
this.queue = queue;
this.platformClient = platformClient;
+ this.logger = logger;
}
public String add(DocumentBuilder document) throws IOException, InterruptedException {
@@ -35,10 +41,12 @@ public HttpResponse close()
}
queue.flush();
String sourceId = this.getSourceId();
+ this.logger.info("Closing open stream " + this.streamId);
return this.platformClient.closeStream(sourceId, this.streamId);
}
private String getStreamId() throws IOException, InterruptedException {
+ this.logger.info("Opening new stream");
String sourceId = this.getSourceId();
HttpResponse response = this.platformClient.openStream(sourceId);
StreamResponse streamResponse = new Gson().fromJson(response.body(), StreamResponse.class);
diff --git a/src/test/java/com/coveo/pushapiclient/ApiCoreTest.java b/src/test/java/com/coveo/pushapiclient/ApiCoreTest.java
new file mode 100644
index 00000000..c876fbfe
--- /dev/null
+++ b/src/test/java/com/coveo/pushapiclient/ApiCoreTest.java
@@ -0,0 +1,82 @@
+package com.coveo.pushapiclient;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+import java.net.http.HttpResponse.BodyHandler;
+import org.apache.logging.log4j.Logger;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+public class ApiCoreTest {
+
+ @Mock private HttpClient httpClient;
+ @Mock private HttpRequest httpRequest;
+ @Mock private Logger logger;
+ @Mock private HttpResponse httpResponse;
+
+ @InjectMocks private ApiCore api;
+
+ private AutoCloseable closeable;
+ private static final String[] headers = {
+ "Content-Type", "application/json", "Accept", "application/json"
+ };
+
+ private void mockSuccessResponse() {
+ when(httpResponse.statusCode()).thenReturn(200);
+ when(httpResponse.body()).thenReturn("All good!");
+ when(httpRequest.method()).thenReturn("POST");
+ }
+
+ private void mockErrorResponse() {
+ when(httpResponse.statusCode()).thenReturn(412);
+ when(httpResponse.body()).thenReturn("BAD_REQUEST");
+ when(httpRequest.method()).thenReturn("DELETE");
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ closeable = MockitoAnnotations.openMocks(this);
+
+ when(httpClient.send(any(HttpRequest.class), any(BodyHandler.class))).thenReturn(httpResponse);
+ when(httpResponse.request()).thenReturn(httpRequest);
+ }
+
+ @After
+ public void closeService() throws Exception {
+ closeable.close();
+ }
+
+ @Test
+ public void testShouldLogRequestAndResonse()
+ throws IOException, InterruptedException, URISyntaxException {
+ this.mockSuccessResponse();
+ this.api.post(new URI("https://perdu.com/"), headers);
+
+ verify(logger, times(1)).debug("POST https://perdu.com/");
+ verify(logger, times(1)).debug("POST status: 200");
+ verify(logger, times(1)).debug("POST response: All good!");
+ }
+
+ @Test
+ public void testShouldLogResponse() throws IOException, InterruptedException, URISyntaxException {
+ this.mockErrorResponse();
+ this.api.delete(new URI("https://perdu.com/"), headers);
+
+ verify(logger, times(1)).debug("DELETE https://perdu.com/");
+ verify(logger, times(1)).error("DELETE status: 412");
+ verify(logger, times(1)).error("DELETE response: BAD_REQUEST");
+ }
+}
diff --git a/src/test/java/com/coveo/pushapiclient/StreamServiceInternalTest.java b/src/test/java/com/coveo/pushapiclient/StreamServiceInternalTest.java
index d850c5e3..4acd9cec 100644
--- a/src/test/java/com/coveo/pushapiclient/StreamServiceInternalTest.java
+++ b/src/test/java/com/coveo/pushapiclient/StreamServiceInternalTest.java
@@ -1,5 +1,6 @@
package com.coveo.pushapiclient;
+import static org.mockito.ArgumentMatchers.contains;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -7,6 +8,7 @@
import com.coveo.pushapiclient.exceptions.NoOpenStreamException;
import java.io.IOException;
import java.net.http.HttpResponse;
+import org.apache.logging.log4j.core.Logger;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@@ -21,6 +23,8 @@ public class StreamServiceInternalTest {
@Mock private PlatformClient platformClient;
+ @Mock private Logger logger;
+
@InjectMocks private StreamServiceInternal service;
@Mock private HttpResponse httpResponse;
@@ -86,4 +90,14 @@ public void givenNoOpenStream_whenClose_thenShouldThrow()
throws IOException, InterruptedException, NoOpenStreamException {
service.close();
}
+
+ @Test
+ public void testShouldLogInfo() throws IOException, InterruptedException, NoOpenStreamException {
+ service.add(documentA);
+ service.add(documentB);
+ verify(logger, times(1)).info("Opening new stream");
+
+ service.close();
+ verify(logger, times(1)).info(contains("Closing open stream"));
+ }
}