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")); + } }