Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions java/src/org/openqa/selenium/docker/Container.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@
package org.openqa.selenium.docker;

import java.time.Duration;
import java.util.ArrayList;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.openqa.selenium.internal.Require;
import org.openqa.selenium.remote.http.Contents;

public class Container {

Expand Down Expand Up @@ -72,6 +72,10 @@ public ContainerLogs getLogs() {
LOG.info("Getting logs " + getId());
return protocol.getContainerLogs(getId());
}
return new ContainerLogs(getId(), new ArrayList<>());
return new ContainerLogs(getId(), Contents.empty());
}

public boolean isRunning() {
return running;
}
}
32 changes: 27 additions & 5 deletions java/src/org/openqa/selenium/docker/ContainerLogs.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,21 +17,39 @@

package org.openqa.selenium.docker;

import static java.nio.charset.StandardCharsets.UTF_8;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UncheckedIOException;
import java.util.List;
import java.util.stream.Collectors;
import org.openqa.selenium.internal.Require;
import org.openqa.selenium.remote.http.Contents;

public class ContainerLogs {

private final List<String> logLines;
private final Contents.Supplier contents;
private final ContainerId id;

public ContainerLogs(ContainerId id, List<String> logLines) {
this.logLines = Require.nonNull("Container logs", logLines);
public ContainerLogs(ContainerId id, Contents.Supplier contents) {
this.contents = Require.nonNull("Container logs", contents);
this.id = Require.nonNull("Container id", id);
}

/**
* @deprecated List of container logs might be very long. If you need to write down the logs, use
* {@link #getLogs()} to avoid reading the whole content to memory.
*/
@Deprecated
public List<String> getLogLines() {
return logLines;
try (BufferedReader in = new BufferedReader(new InputStreamReader(contents.get(), UTF_8))) {
return in.lines().collect(Collectors.toList());
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}

public ContainerId getId() {
Expand All @@ -40,6 +58,10 @@ public ContainerId getId() {

@Override
public String toString() {
return String.format("ContainerInfo{containerLogs=%s, id=%s}", logLines, id);
return String.format("ContainerInfo{id=%s,size=%s}", id, contents.length());
}

public InputStream getLogs() {
return contents.get();
}
}
11 changes: 4 additions & 7 deletions java/src/org/openqa/selenium/docker/client/GetContainerLogs.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,10 @@
import static java.net.HttpURLConnection.HTTP_OK;
import static org.openqa.selenium.remote.http.HttpMethod.GET;

import java.util.List;
import java.util.logging.Logger;
import org.openqa.selenium.docker.ContainerId;
import org.openqa.selenium.docker.ContainerLogs;
import org.openqa.selenium.internal.Require;
import org.openqa.selenium.remote.http.Contents;
import org.openqa.selenium.remote.http.HttpHandler;
import org.openqa.selenium.remote.http.HttpRequest;
import org.openqa.selenium.remote.http.HttpResponse;
Expand All @@ -47,12 +45,11 @@ public ContainerLogs apply(ContainerId id) {
String requestUrl =
String.format("/v%s/containers/%s/logs?stdout=true&stderr=true", apiVersion, id);

HttpResponse res =
client.execute(new HttpRequest(GET, requestUrl).addHeader("Content-Type", "text/plain"));
HttpResponse res = client.execute(new HttpRequest(GET, requestUrl));
if (res.getStatus() != HTTP_OK) {
LOG.warning("Unable to inspect container " + id);
LOG.warning(() -> "Unable to inspect container " + id);
}
List<String> logLines = List.of(Contents.string(res).split("\n"));
return new ContainerLogs(id, logLines);

return new ContainerLogs(id, res.getContent());
}
}
53 changes: 45 additions & 8 deletions java/src/org/openqa/selenium/grid/node/docker/DockerSession.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,26 @@

package org.openqa.selenium.grid.node.docker;

import static java.util.logging.Level.FINE;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.DataInputStream;
import java.io.EOFException;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.time.Duration;
import java.time.Instant;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.jspecify.annotations.Nullable;
import org.openqa.selenium.Capabilities;
import org.openqa.selenium.docker.Container;
import org.openqa.selenium.docker.ContainerLogs;
import org.openqa.selenium.grid.node.DefaultActiveSession;
import org.openqa.selenium.internal.Require;
import org.openqa.selenium.remote.Dialect;
Expand Down Expand Up @@ -72,13 +81,41 @@ public void stop() {
}

private void saveLogs() {
if (!container.isRunning()) {
LOG.log(
FINE, () -> "Skip saving logs because container is not running: " + container.getId());
return;
}

String sessionAssetsPath = assetsPath.getContainerPath(getId());
String seleniumServerLog = String.format("%s/selenium-server.log", sessionAssetsPath);
try {
List<String> logs = container.getLogs().getLogLines();
Files.write(Paths.get(seleniumServerLog), logs);
} catch (Exception e) {
File seleniumServerLog = new File(sessionAssetsPath, "selenium-server.log");
ContainerLogs containerLogs = container.getLogs();

try (OutputStream out = new BufferedOutputStream(new FileOutputStream(seleniumServerLog))) {
parseMultiplexedStream(containerLogs.getLogs(), out);
LOG.log(
FINE,
() ->
String.format(
"Saved container %s logs to file %s", container.getId(), seleniumServerLog));
} catch (IOException e) {
LOG.log(Level.WARNING, "Error saving logs", e);
}
}

@SuppressWarnings("InfiniteLoopStatement")
private void parseMultiplexedStream(InputStream stream, OutputStream out) throws IOException {
try (DataInputStream in = new DataInputStream(new BufferedInputStream(stream))) {
while (true) {
in.skipBytes(1); // Skip "stream type" byte (1 = stdout, 2 = stderr)
in.skipBytes(3); // Skip the 3 empty padding bytes
int payloadSize = in.readInt(); // Read the 4-byte payload size
byte[] payload = new byte[payloadSize];
in.readFully(payload);
out.write(payload);
}
} catch (EOFException done) {
LOG.log(FINE, () -> "Finished reading multiplexed stream: " + done);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -466,7 +466,7 @@ private TimeZone getTimeZone(Capabilities sessionRequestCapabilities) {
}
}
String envTz = System.getenv("TZ");
if (List.of(TimeZone.getAvailableIDs()).contains(envTz)) {
if (envTz != null && List.of(TimeZone.getAvailableIDs()).contains(envTz)) {
return TimeZone.getTimeZone(envTz);
}
return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ private Contents.Supplier extractContent(java.net.http.HttpResponse<InputStream>
response
.headers()
.firstValue("Content-Type")
.map(contentType -> contentType.equalsIgnoreCase(MediaType.OCTET_STREAM.toString()))
.map(contentType -> isBinaryStream(contentType))
.orElse(false);

if (isBinaryStream) {
Expand All @@ -193,6 +193,12 @@ private Contents.Supplier extractContent(java.net.http.HttpResponse<InputStream>
}
}

private static boolean isBinaryStream(String contentType) {
return MediaType.OCTET_STREAM.toString().equalsIgnoreCase(contentType)
|| "application/vnd.docker.multiplexed-stream".equalsIgnoreCase(contentType)
|| "application/vnd.docker.raw-stream".equalsIgnoreCase(contentType);
}

private byte[] readResponseBody(java.net.http.HttpResponse<InputStream> response) {
try (InputStream in = response.body()) {
return Read.toByteArray(in);
Expand Down
Loading