From fcbd32eb20a73a18f778dd1b1012a4bf6abfc863 Mon Sep 17 00:00:00 2001 From: Eunbin Son Date: Sun, 18 Oct 2026 22:07:26 +0900 Subject: [PATCH] Restore per-attempt retry logging in OkHttpGrpcSender --- .../okhttp/internal/OkHttpGrpcSender.java | 29 ++++++++++-- .../okhttp/internal/OkHttpGrpcSenderTest.java | 47 +++++++++++++++++++ 2 files changed, 73 insertions(+), 3 deletions(-) diff --git a/exporters/sender/okhttp/src/main/java/io/opentelemetry/exporter/sender/okhttp/internal/OkHttpGrpcSender.java b/exporters/sender/okhttp/src/main/java/io/opentelemetry/exporter/sender/okhttp/internal/OkHttpGrpcSender.java index 41a06a1b45e..bafb8c46f34 100644 --- a/exporters/sender/okhttp/src/main/java/io/opentelemetry/exporter/sender/okhttp/internal/OkHttpGrpcSender.java +++ b/exporters/sender/okhttp/src/main/java/io/opentelemetry/exporter/sender/okhttp/internal/OkHttpGrpcSender.java @@ -219,10 +219,21 @@ private void sendAttempt( new Callback() { @Override public void onFailure(Call call, IOException e) { + boolean retryable = retryState != null && retryState.shouldRetryOnException(e); + if (retryState != null && logger.isLoggable(Level.FINER)) { + logger.log( + Level.FINER, + "Attempt " + + attempt + + " failed with " + + (retryable ? "retryable" : "non-retryable") + + " exception", + e); + } if (!call.isCanceled() && retryState != null && retryState.canRetry(attempt) - && retryState.shouldRetryOnException(e) + && retryable && retryState.backoff(OptionalLong.empty())) { sendAttempt( requestBuilder, @@ -241,10 +252,22 @@ public void onResponse(Call call, Response response) { handleResponse( response, (resolvedResponse, canRetry) -> { + boolean retryable = canRetry && isRetryable(resolvedResponse); + if (retryState != null && logger.isLoggable(Level.FINER)) { + logger.log( + Level.FINER, + "Attempt " + + attempt + + " returned " + + (retryable ? "retryable" : "non-retryable") + + " response: grpc-status=" + + resolvedResponse.getStatusCode() + + ", grpc-message=" + + resolvedResponse.getStatusDescription()); + } if (retryState != null && retryState.canRetry(attempt) - && canRetry - && isRetryable(resolvedResponse) + && retryable && retryState.backoff(OptionalLong.empty())) { sendAttempt( requestBuilder, diff --git a/exporters/sender/okhttp/src/test/java/io/opentelemetry/exporter/sender/okhttp/internal/OkHttpGrpcSenderTest.java b/exporters/sender/okhttp/src/test/java/io/opentelemetry/exporter/sender/okhttp/internal/OkHttpGrpcSenderTest.java index af5c1fb7c0f..93af06b3dcb 100644 --- a/exporters/sender/okhttp/src/test/java/io/opentelemetry/exporter/sender/okhttp/internal/OkHttpGrpcSenderTest.java +++ b/exporters/sender/okhttp/src/test/java/io/opentelemetry/exporter/sender/okhttp/internal/OkHttpGrpcSenderTest.java @@ -11,11 +11,13 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; +import io.github.netmikey.logunit.api.LogCapturer; import io.opentelemetry.exporter.internal.RetryUtil; import io.opentelemetry.sdk.common.CompletableResultCode; import io.opentelemetry.sdk.common.export.GrpcResponse; import io.opentelemetry.sdk.common.export.GrpcStatusCode; import io.opentelemetry.sdk.common.export.MessageWriter; +import io.opentelemetry.sdk.common.export.RetryPolicy; import java.io.IOException; import java.io.OutputStream; import java.net.ServerSocket; @@ -39,11 +41,17 @@ import okhttp3.Response; import okhttp3.ResponseBody; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; +import org.slf4j.event.Level; +import org.slf4j.event.LoggingEvent; class OkHttpGrpcSenderTest { + @RegisterExtension + LogCapturer logs = LogCapturer.create().captureForType(OkHttpGrpcSender.class, Level.TRACE); + private static final MediaType GRPC_MEDIA_TYPE = MediaType.get("application/grpc"); static Set provideRetryableGrpcStatusCodes() { @@ -401,6 +409,45 @@ void constructor_wrapsDefaultTrustManagerFailure() throws Exception { } } + @Test + void send_retryEnabled_logsEachFailedAttempt() throws Exception { + // Allocate an ephemeral port and immediately close it to get a port with nothing listening + int port; + try (ServerSocket socket = new ServerSocket(0)) { + port = socket.getLocalPort(); + } + + OkHttpGrpcSender sender = + new OkHttpGrpcSender( + "http://localhost:" + port, + null, + Duration.ofSeconds(10), + Duration.ofSeconds(10), + Collections::emptyMap, + RetryPolicy.builder().setMaxAttempts(2).setInitialBackoff(Duration.ofMillis(1)).build(), + null, + null, + null, + Long.MAX_VALUE, + null); + + CompletableResultCode sendResult = new CompletableResultCode(); + try { + sender.send( + new TestMessageWriter(), response -> sendResult.succeed(), error -> sendResult.fail()); + sendResult.join(10, TimeUnit.SECONDS); + } finally { + sender.shutdown().join(10, TimeUnit.SECONDS); + } + + assertThat(sendResult.isSuccess()).isFalse(); + assertThat(logs.getEvents()) + .extracting(LoggingEvent::getMessage) + .containsExactly( + "Attempt 0 failed with retryable exception", + "Attempt 1 failed with retryable exception"); + } + /** Simple test marshaler for testing purposes. */ private static class TestMessageWriter implements MessageWriter { @Override