diff --git a/driver/src/main/java/org/neo4j/driver/internal/cluster/RediscoveryImpl.java b/driver/src/main/java/org/neo4j/driver/internal/cluster/RediscoveryImpl.java index 52c76d784c..7f18f02282 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/cluster/RediscoveryImpl.java +++ b/driver/src/main/java/org/neo4j/driver/internal/cluster/RediscoveryImpl.java @@ -60,6 +60,9 @@ public class RediscoveryImpl implements Rediscovery { private static final String NO_ROUTERS_AVAILABLE = "Could not perform discovery for database '%s'. No routing server available."; private static final String RECOVERABLE_ROUTING_ERROR = "Failed to update routing table with server '%s'."; + private static final String RECOVERABLE_DISCOVERY_ERROR_WITH_SERVER = "Received a recoverable discovery error with server '%s', " + + "will continue discovery with other routing servers if available. " + + "Complete failure is reported separately from this entry."; private final BoltServerAddress initialRouter; private final RoutingSettings settings; @@ -291,8 +294,9 @@ private ClusterComposition handleRoutingProcedureError( Throwable error, Routing // Retriable error happened during discovery. DiscoveryException discoveryError = new DiscoveryException( format( RECOVERABLE_ROUTING_ERROR, routerAddress ), error ); Futures.combineErrors( baseError, discoveryError ); // we record each failure here - logger.warn( format( "Received a recoverable discovery error with server '%s', will continue discovery with other routing servers if available.", - routerAddress ), discoveryError ); + String warningMessage = format( RECOVERABLE_DISCOVERY_ERROR_WITH_SERVER, routerAddress ); + logger.warn( warningMessage ); + logger.debug( warningMessage, discoveryError ); routingTable.forget( routerAddress ); return null; } diff --git a/driver/src/main/java/org/neo4j/driver/internal/cluster/loadbalancing/LoadBalancer.java b/driver/src/main/java/org/neo4j/driver/internal/cluster/loadbalancing/LoadBalancer.java index 3a6bd6683f..7174fdf166 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/cluster/loadbalancing/LoadBalancer.java +++ b/driver/src/main/java/org/neo4j/driver/internal/cluster/loadbalancing/LoadBalancer.java @@ -20,6 +20,7 @@ import io.netty.util.concurrent.EventExecutorGroup; +import java.util.ArrayList; import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; @@ -62,6 +63,11 @@ public class LoadBalancer implements ConnectionProvider { private static final String LOAD_BALANCER_LOG_NAME = "LoadBalancer"; + private static final String CONNECTION_ACQUISITION_COMPLETION_FAILURE_MESSAGE = "Connection acquisition failed for all available addresses."; + private static final String CONNECTION_ACQUISITION_COMPLETION_EXCEPTION_MESSAGE = + "Failed to obtain connection towards %s server. Known routing table is: %s"; + private static final String CONNECTION_ACQUISITION_ATTEMPT_FAILURE_MESSAGE = + "Failed to obtain a connection towards address %s, will try other addresses if available. Complete failure is reported separately from this entry."; private final ConnectionPool connectionPool; private final RoutingTableRegistry routingTables; private final LoadBalancingStrategy loadBalancingStrategy; @@ -181,19 +187,23 @@ private CompletionStage acquire( AccessMode mode, RoutingTable routi { AddressSet addresses = addressSet( mode, routingTable ); CompletableFuture result = new CompletableFuture<>(); - acquire( mode, routingTable, addresses, result ); + List attemptExceptions = new ArrayList<>(); + acquire( mode, routingTable, addresses, result, attemptExceptions ); return result; } - private void acquire( AccessMode mode, RoutingTable routingTable, AddressSet addresses, CompletableFuture result ) + private void acquire( AccessMode mode, RoutingTable routingTable, AddressSet addresses, CompletableFuture result, + List attemptErrors ) { BoltServerAddress address = selectAddress( mode, addresses ); if ( address == null ) { - result.completeExceptionally( new SessionExpiredException( - "Failed to obtain connection towards " + mode + " server. " + - "Known routing table is: " + routingTable ) ); + SessionExpiredException completionError = + new SessionExpiredException( format( CONNECTION_ACQUISITION_COMPLETION_EXCEPTION_MESSAGE, mode, routingTable ) ); + attemptErrors.forEach( completionError::addSuppressed ); + log.error( CONNECTION_ACQUISITION_COMPLETION_FAILURE_MESSAGE, completionError ); + result.completeExceptionally( completionError ); return; } @@ -204,10 +214,12 @@ private void acquire( AccessMode mode, RoutingTable routingTable, AddressSet add { if ( error instanceof ServiceUnavailableException ) { - SessionExpiredException errorToLog = new SessionExpiredException( format( "Server at %s is no longer available", address ), error ); - log.warn( "Failed to obtain a connection towards address " + address, errorToLog ); + String attemptMessage = format( CONNECTION_ACQUISITION_ATTEMPT_FAILURE_MESSAGE, address ); + log.warn( attemptMessage ); + log.debug( attemptMessage, error ); + attemptErrors.add( error ); routingTable.forget( address ); - eventExecutorGroup.next().execute( () -> acquire( mode, routingTable, addresses, result ) ); + eventExecutorGroup.next().execute( () -> acquire( mode, routingTable, addresses, result, attemptErrors ) ); } else { diff --git a/driver/src/test/java/org/neo4j/driver/internal/cluster/RediscoveryTest.java b/driver/src/test/java/org/neo4j/driver/internal/cluster/RediscoveryTest.java index 536684547d..0987774f0b 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/cluster/RediscoveryTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/cluster/RediscoveryTest.java @@ -57,7 +57,6 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.startsWith; @@ -187,9 +186,14 @@ void shouldFailImmediatelyWhenClusterCompositionProviderReturnsFailure() ClusterComposition composition = await( rediscovery.lookupClusterComposition( table, pool, empty() ) ).getClusterComposition(); assertEquals( validComposition, composition ); - ArgumentCaptor argument = ArgumentCaptor.forClass( DiscoveryException.class ); - verify( logger ).warn( anyString(), argument.capture() ); - assertThat( argument.getValue().getCause(), equalTo( protocolError ) ); + ArgumentCaptor warningMessageCaptor = ArgumentCaptor.forClass( String.class ); + ArgumentCaptor debugMessageCaptor = ArgumentCaptor.forClass( String.class ); + ArgumentCaptor debugThrowableCaptor = ArgumentCaptor.forClass( DiscoveryException.class ); + verify( logger ).warn( warningMessageCaptor.capture() ); + verify( logger ).debug( debugMessageCaptor.capture(), debugThrowableCaptor.capture() ); + assertNotNull( warningMessageCaptor.getValue() ); + assertEquals( warningMessageCaptor.getValue(), debugMessageCaptor.getValue() ); + assertThat( debugThrowableCaptor.getValue().getCause(), equalTo( protocolError ) ); } @Test