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
14 changes: 12 additions & 2 deletions .github/workflows/build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,9 @@ jobs:
java-version: ${{ env.JAVA_VERSION }}
cache: 'maven'
- name: Build and test with Maven
# verify also runs the Spotless check, the JaCoCo coverage check and SpotBugs
run: ./mvnw -B verify -P default,test
# verify also runs the Spotless check, the JaCoCo coverage check and SpotBugs. The
# ci-logging profile writes the FINE logs of the core tests to a file (issue #1601).
run: ./mvnw -B verify -P default,test,ci-logging
- name: Check Javadoc
run: ./mvnw -B -q compile javadoc:javadoc
- name: Upload coverage and static analysis reports
Expand All @@ -40,3 +41,12 @@ jobs:
**/target/site/jacoco/
**/target/spotbugsXml.xml
if-no-files-found: ignore
- name: Upload test logs
if: failure()
uses: actions/upload-artifact@v7
with:
name: test-logs
path: |
**/target/restlet-fine.log
**/target/surefire-reports/
if-no-files-found: ignore
3 changes: 3 additions & 0 deletions changes.md
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,9 @@ Changes log
- Added Netty 4.2.19 (imported with the `netty-bom`). CI builds with Java 25.
- The `dangling-doc-comments` lint, new in Java 23, is disabled because license headers
use Javadoc-style comments.
- The HTTP client logs the steps of its exchanges and connections at the FINE level. CI
writes the FINE logs of the core tests to `target/restlet-fine.log` ("ci-logging"
profile) and uploads them when the build fails. Issue #1601.

- 3.0 Milestone 1 (10-11-2026)
- Breaking changes
Expand Down
21 changes: 21 additions & 0 deletions org.restlet/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,27 @@
</plugins>
</build>
</profile>
<!-- CI logging: the FINE level of the Restlet loggers goes to target/restlet-fine.log. -->
<profile>
<id>ci-logging</id>
<build>
<plugins>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<executions>
<execution>
<id>default-test</id>
<configuration>
<systemPropertyVariables combine.children="append">
<java.util.logging.config.file>${project.basedir}/src/test/ci-logging.properties</java.util.logging.config.file>
</systemPropertyVariables>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
<!-- Native QUIC library of the build platform, used by the experimental HTTP/3 tests. -->
<profile>
<id>quic-linux-x86_64</id>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -784,6 +784,7 @@ private void writeRequest(Representation entity) throws IOException {
lastContent = withTrailers;
}
check(ch.writeAndFlush(lastContent));
helper.getLogger().fine(() -> "Sent the request " + method + " " + target + " on " + ch);

lock.lock();
try {
Expand Down Expand Up @@ -907,6 +908,8 @@ private void awaitHead() throws IOException {
* @param response The response head.
*/
void onResponseHead(HttpResponse response) {
helper.getLogger()
.fine(() -> "Received the response head " + response.status() + " on " + channel);
for (Map.Entry<String, String> entry : response.headers()) {
String name =
kind.isStream() ? entry.getKey().toLowerCase(Locale.ROOT) : entry.getKey();
Expand Down Expand Up @@ -961,6 +964,7 @@ boolean onResponseContent(ByteBuf content) {

/** Called on the event loop when the response is fully received. */
void onResponseComplete(io.netty.handler.codec.http.HttpHeaders trailers) {
helper.getLogger().fine(() -> "Received the end of the response on " + channel);
org.restlet.Response currentResponse = response;
if (currentResponse != null) {
for (Map.Entry<String, String> trailer : trailers) {
Expand Down Expand Up @@ -1019,6 +1023,7 @@ void onFailure(Throwable cause) {
} finally {
lock.unlock();
}
helper.logFailure("The exchange failed on " + channel, cause);
ChannelInputStream stream = this.body;
org.restlet.Request request = currentRequest;
if (request != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -500,9 +500,13 @@ && getProxyHost() == null) {
Channel channel;
while ((channel = pool.idle.pollFirst()) != null) {
if (channel.isActive()) {
Channel reused = channel;
getLogger().fine(() -> "Reusing the idle connection " + reused);
bindHttp1(channel, call);
return channel;
}
Channel inactive = channel;
getLogger().fine(() -> "Discarding the inactive idle connection " + inactive);
}

if (getTransportMode() == TransportMode.HTTP1_1) {
Expand Down Expand Up @@ -609,8 +613,10 @@ void release(Channel channel, boolean reusable) {

OriginPool pool = channel.attr(POOL).get();
if (reusable && channel.isActive() && pool != null && group != null) {
getLogger().fine(() -> "Returning the connection " + channel + " to the pool");
pool.idle.offerFirst(channel);
} else {
getLogger().fine(() -> "Closing the connection " + channel + ", reusable=" + reusable);
channel.close();
}
}
Expand Down Expand Up @@ -714,7 +720,12 @@ protected void initChannel(SocketChannel ch) {
timeout,
"Unable to connect to " + pool.host + ":" + pool.port);
connections.add(channel);
channel.closeFuture().addListener(f -> releaseConnection(pool, true));
channel.closeFuture()
.addListener(
f -> {
releaseConnection(pool, true);
getLogger().fine(() -> "Closed the connection " + channel);
});
acquired = false;

Promise<String> ready = channel.attr(READY).get();
Expand All @@ -724,6 +735,7 @@ protected void initChannel(SocketChannel ch) {
channel.close();
throw e;
}
getLogger().fine(() -> "Opened the connection " + channel);
return channel;
} finally {
if (acquired) {
Expand Down
16 changes: 16 additions & 0 deletions org.restlet/src/test/ci-logging.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Logging of the tests in CI (profile "ci-logging"): the console keeps the INFO level, while the
# FINE level of the Restlet loggers, including the steps of the Netty client connector, goes to
# target/restlet-fine.log, uploaded when the build fails. Diagnoses intermittent failures such as
# the connector errors of SecurityTestCase (issue #1601).
handlers=java.util.logging.ConsoleHandler,java.util.logging.FileHandler
.level=INFO
org.restlet.level=FINE

java.util.logging.ConsoleHandler.level=INFO

java.util.logging.FileHandler.level=FINE
java.util.logging.FileHandler.pattern=target/restlet-fine.log
java.util.logging.FileHandler.limit=0
java.util.logging.FileHandler.append=true
java.util.logging.FileHandler.formatter=java.util.logging.SimpleFormatter
java.util.logging.SimpleFormatter.format=%1$tT.%1$tL [%3$s] %4$s %5$s%6$s%n
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
import org.restlet.data.MediaType;
import org.restlet.data.Protocol;
import org.restlet.data.Reference;
import org.restlet.engine.connector.HttpClientHelper;
import org.restlet.engine.connector.HttpServerHelper;

class Http1LookaheadTestCase {
Expand Down Expand Up @@ -288,10 +289,12 @@ public boolean awaitTermination(long timeout, TimeUnit unit) {

@Test
void publishesClientCompletionBeforeReleasingAnEmptyBodyConsumer() throws Exception {
var helper = new HttpClientHelper(null);
var trailers = new io.netty.handler.codec.http.DefaultHttpHeaders();
new NettyClientCall(null, "GET", new Reference("http://localhost/"), "/", false)
new NettyClientCall(helper, "GET", new Reference("http://localhost/"), "/", false)
.onResponseComplete(trailers);
var call = new NettyClientCall(null, "GET", new Reference("http://localhost/"), "/", false);
var call =
new NettyClientCall(helper, "GET", new Reference("http://localhost/"), "/", false);
var body = new ChannelInputStream(null, 0);
var bodyField = NettyClientCall.class.getDeclaredField("body");
bodyField.setAccessible(true);
Expand Down
Loading