diff --git a/environment/LogSlurp/ClientLogger/Program.cs b/environment/LogSlurp/ClientLogger/Program.cs index 24aa03df..ccb97fe5 100644 --- a/environment/LogSlurp/ClientLogger/Program.cs +++ b/environment/LogSlurp/ClientLogger/Program.cs @@ -9,6 +9,8 @@ using var httpClient = new HttpClient(); var log_id = Guid.NewGuid().ToString(); var response = await httpClient.PostAsync($"http://localhost:{Port}/startNewLog", JsonContent.Create(new { log_id })); +Console.WriteLine(); +Console.WriteLine("Session ID: " + log_id); var ws = new ClientWebSocket(); ws.Options.SetRequestHeader("CBL-Log-ID", log_id); @@ -31,4 +33,4 @@ var logString = await httpClient.GetStringAsync($"http://localhost:{Port}/retrieveLog"); Console.WriteLine(); Console.WriteLine("==== Retrieved ===="); -Console.WriteLine(logString); \ No newline at end of file +Console.WriteLine(logString); diff --git a/environment/LogSlurp/README.md b/environment/LogSlurp/README.md index ca290913..9d5e2dad 100644 --- a/environment/LogSlurp/README.md +++ b/environment/LogSlurp/README.md @@ -106,3 +106,13 @@ quit

==== Retrieved ====
ClientLogger: 2024-08-12 23:33:34,147 hello + +The client logger can be used to test the implementation of a logger. Do this: +- Start the log slurper +- Start the client logger. It will print the id of the session it started with the slurper +- When the client logger loops asking for log messages let it sit +- Run your logger implementation. Use the session id from the client logger and an arbitrary "tag" +- Log a few things from your implementation. Maybe type a couple of log messages at the client logger as well +- When you have enough logs to validate your implementation, type "quit" at the client logger +- The client logger will display the contents of the session, including the log messages you typed at it, along with the ones sent from your logger implementation, interleaved approprately. + diff --git a/jenkins/pipelines/android/Jenkinsfile b/jenkins/pipelines/android/Jenkinsfile index f2fe08b5..d14c7216 100644 --- a/jenkins/pipelines/android/Jenkinsfile +++ b/jenkins/pipelines/android/Jenkinsfile @@ -6,7 +6,7 @@ pipeline { string(name: 'CBL_BUILD', defaultValue: '', description: 'Couchbase Lite Build Number') string(name: 'SGW_URL', defaultValue: '', description: "The url of Sync Gateway to download") } - options { timeout(time: 30, unit: 'MINUTES') } + options { timeout(time: 60, unit: 'MINUTES') } stages { stage('Init') { steps { diff --git a/jenkins/pipelines/java/Jenkinsfile b/jenkins/pipelines/java/Jenkinsfile index 7e983860..e99c499a 100644 --- a/jenkins/pipelines/java/Jenkinsfile +++ b/jenkins/pipelines/java/Jenkinsfile @@ -6,7 +6,7 @@ pipeline { string(name: 'CBL_BUILD', defaultValue: '', description: 'Couchbase Lite Build Number') string(name: 'SGW_URL', defaultValue: '', description: "The url of Sync Gateway to download") } - options { timeout(time: 60, unit: 'MINUTES') } + options { timeout(time: 120, unit: 'MINUTES') } stages { stage('Init') { steps { diff --git a/jenkins/pipelines/java/desktop/win_tests.ps1 b/jenkins/pipelines/java/desktop/win_tests.ps1 index c1240d29..1839253a 100644 --- a/jenkins/pipelines/java/desktop/win_tests.ps1 +++ b/jenkins/pipelines/java/desktop/win_tests.ps1 @@ -3,7 +3,7 @@ param ( [string]$version, [Parameter(Mandatory=$true)] - [string]$buildNumber + [string]$buildNumber, [Parameter(Mandatory=$false)] [string]$sgUrl, diff --git a/servers/jak/shared/common/main/java/com/couchbase/lite/mobiletest/services/RemoteLogger.java b/servers/jak/shared/common/main/java/com/couchbase/lite/mobiletest/services/RemoteLogger.java index 265faade..648a937d 100644 --- a/servers/jak/shared/common/main/java/com/couchbase/lite/mobiletest/services/RemoteLogger.java +++ b/servers/jak/shared/common/main/java/com/couchbase/lite/mobiletest/services/RemoteLogger.java @@ -15,13 +15,15 @@ // package com.couchbase.lite.mobiletest.services; -import androidx.annotation.GuardedBy; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import java.io.PrintWriter; import java.io.StringWriter; +import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; import okhttp3.OkHttpClient; import okhttp3.Request; @@ -37,7 +39,46 @@ @SuppressWarnings({"PMD.UnusedPrivateField", "PMD.SingularField"}) public class RemoteLogger extends Log.TestLogger { private static final String TAG = "REMLOG"; + private static final long TIMEOUT_SECS = 30; + @NonNull + private final WebSocketListener listener = new WebSocketListener() { + @Override + public void onOpen(@NonNull WebSocket socket, @NonNull Response resp) { + if (!(resp.isSuccessful() || (resp.code() == 101))) { fail("Failed starting new log: " + resp.code()); } + final WebSocket oSocket = webSocket.getAndSet(socket); + startLatch.countDown(); + if (oSocket != null) { fail("Unexpected WebSocket open"); } + } + + @Override + public void onMessage(@NonNull WebSocket webSocket, @NonNull String text) { + Log.p(TAG, "Unexpected message from LogSlurper: " + text); + } + + @Override + public void onFailure(@NonNull WebSocket webSocket, @NonNull Throwable t, @Nullable Response resp) { + stopLatch.countDown(); + startLatch.countDown(); + fail("WebSocket error", t); + } + + @Override + public void onClosed(@NonNull WebSocket webSocket, int code, @NonNull String reason) { + stopLatch.countDown(); + startLatch.countDown(); + close(1000, "Closed"); + } + }; + + @NonNull + private final CountDownLatch startLatch = new CountDownLatch(1); + @NonNull + private final CountDownLatch stopLatch = new CountDownLatch(1); + @NonNull + private final AtomicReference webSocket = new AtomicReference<>(); + @NonNull + private final AtomicBoolean connected = new AtomicBoolean(); @NonNull private final String url; @@ -46,9 +87,6 @@ public class RemoteLogger extends Log.TestLogger { @NonNull private final String tag; - @Nullable - @GuardedBy("url") - private WebSocket webSocket; public RemoteLogger(@NonNull String url, @NonNull String sessionId, @NonNull String tag) { this.url = url; @@ -56,40 +94,27 @@ public RemoteLogger(@NonNull String url, @NonNull String sessionId, @NonNull Str this.tag = tag; } + // Synchronously open a connection to the remote log server. public void connect() { + if (connected.getAndSet(true)) { throw new ServerError("Attempt to reused a RemoteLogger"); } + new OkHttpClient.Builder() .readTimeout(0, TimeUnit.MILLISECONDS) .build() .newWebSocket( new Request.Builder() - .url("\"ws://" + url + "/openLogStream") + .url("http://" + url + "/openLogStream") .header("CBL-Log-ID", sessionId) .header("CBL-Log-Tag", tag) .get() .build(), - new WebSocketListener() { - @Override - public void onOpen(@NonNull WebSocket socket, @NonNull Response resp) { - if (!resp.isSuccessful()) { - fail("Failed starting new log response: " + resp.code()); - } - synchronized (url) { webSocket = socket; } - } - - @Override - public void onMessage(@NonNull WebSocket webSocket, @NonNull String text) { - fail("Unexpected message from LogSlurper: " + text); - } - - @Override - public void onFailure(@NonNull WebSocket webSocket, @NonNull Throwable t, @Nullable Response resp) { - fail("WebSocket error: " + t.getMessage(), t); - close(); - } - - @Override - public void onClosed(@NonNull WebSocket webSocket, int code, @NonNull String reason) { close(); } - }); + listener); + + try { + if (startLatch.await(10, TimeUnit.SECONDS)) { return; } + } + catch (InterruptedException ignore) { } + fail("Failed opening LogSlurper websocket"); } @Override @@ -99,37 +124,45 @@ public void log(@NonNull LogLevel level, @NonNull LogDomain domain, @NonNull Str @Override public void log(LogLevel level, String tag, String msg, Exception err) { - final WebSocket socket; - synchronized (url) { socket = webSocket; } - + final WebSocket socket = webSocket.get(); if (socket == null) { Log.p(TAG, "RemoteLogger is not connected"); return; } - final StringBuilder logMsg = new StringBuilder(); - logMsg.append(tag).append('/').append(level.toString()).append(' ').append(msg); + sendLogMessage(socket, new StringBuilder(tag).append('/').append(level).append(' ').append(msg).toString()); if (err != null) { final StringWriter sw = new StringWriter(); err.printStackTrace(new PrintWriter(sw)); - logMsg.append(System.lineSeparator()).append(sw); + sendLogMessage(socket, sw.toString()); } - - socket.send(logMsg.toString()); } @Override - public void close() { - final WebSocket socket; - synchronized (url) { socket = webSocket; } - if (socket != null) { socket.close(1000, null); } + public void close() { close(1001, "Closed by client"); } + + private void sendLogMessage(@NonNull WebSocket socket, @NonNull String message) { + if (!socket.send(message)) { Log.p(TAG, "Failed to send log message"); } } private void fail(String message) { fail(message, null); } private void fail(String message, Throwable e) { - close(); + close(1011, message); throw new ServerError(message, e); } + + // Synchronously close the connection to the remote log server. + private void close(int code, @NonNull String reason) { + final WebSocket socket = webSocket.getAndSet(null); + if (socket == null) { return; } + + socket.close(code, reason); + try { + if (stopLatch.await(TIMEOUT_SECS, TimeUnit.SECONDS)) { return; } + } + catch (InterruptedException ignore) { } + Log.p(TAG, "Failed closing LogSlurper websocket"); + } }