Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add server selection log messages #1221

Merged
merged 10 commits into from
Nov 1, 2023
208 changes: 150 additions & 58 deletions driver-core/src/main/com/mongodb/internal/connection/BaseCluster.java

Large diffs are not rendered by default.

40 changes: 37 additions & 3 deletions driver-core/src/main/com/mongodb/internal/logging/LogMessage.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,13 @@
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static com.mongodb.assertions.Assertions.assertNotNull;
import static java.util.function.Function.identity;

/**
* <p>This class is not part of the public API and may be removed or changed at any time</p>
Expand All @@ -41,11 +44,34 @@ public final class LogMessage {
private final String format;

public enum Component {
COMMAND,
CONNECTION
COMMAND("command"),
CONNECTION("connection"),
SERVER_SELECTION("serverSelection");

private static final Map<String, Component> INDEX;

static {
INDEX = Stream.of(Component.values()).collect(Collectors.toMap(Component::getValue, identity()));
}

private final String value;

Component(final String value) {
this.value = value;
}

public String getValue() {
return value;
}

public static Component of(final String value) {
Component result = INDEX.get(value);
return assertNotNull(result);
}
vbabanin marked this conversation as resolved.
Show resolved Hide resolved
}

public enum Level {
INFO,
DEBUG
}

Expand Down Expand Up @@ -73,6 +99,10 @@ public enum Name {
COMMAND_NAME("commandName"),
REQUEST_ID("requestId"),
OPERATION_ID("operationId"),
/**
* Not supported.
*/
OPERATION("operation"),
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The final decision on whether to support operation and to what extent will be done in https://jira.mongodb.org/browse/JAVA-5197.

SERVICE_ID("serviceId"),
SERVER_CONNECTION_ID("serverConnectionId"),
DRIVER_CONNECTION_ID("driverConnectionId"),
Expand All @@ -82,11 +112,15 @@ public enum Name {
COMMAND_CONTENT("command"),
REASON_DESCRIPTION("reason"),
ERROR_DESCRIPTION("error"),
FAILURE("failure"),
MAX_IDLE_TIME_MS("maxIdleTimeMS"),
MIN_POOL_SIZE("minPoolSize"),
MAX_POOL_SIZE("maxPoolSize"),
MAX_CONNECTING("maxConnecting"),
WAIT_QUEUE_TIMEOUT_MS("waitQueueTimeoutMS");
WAIT_QUEUE_TIMEOUT_MS("waitQueueTimeoutMS"),
SELECTOR("selector"),
TOPOLOGY_DESCRIPTION("topologyDescription"),
REMAINING_TIME_MS("remainingTimeMS");

private final String value;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
import com.mongodb.lang.Nullable;

import java.util.concurrent.ConcurrentHashMap;
import java.util.function.BiConsumer;
import java.util.function.Predicate;

import static com.mongodb.internal.VisibleForTesting.AccessModifier.PRIVATE;

Expand Down Expand Up @@ -68,10 +70,11 @@ public boolean isRequired(final Level level, final ClusterId clusterId) {
return true;
}

//noinspection SwitchStatementWithTooFewBranches
switch (level) {
case DEBUG:
return logger.isDebugEnabled();
case INFO:
return logger.isInfoEnabled();
default:
throw new UnsupportedOperationException();
}
Expand All @@ -82,22 +85,37 @@ public void log(final LogMessage logMessage) {
if (interceptor != null) {
interceptor.intercept(logMessage);
}
//noinspection SwitchStatementWithTooFewBranches
switch (logMessage.getLevel()) {
case DEBUG:
if (logger.isDebugEnabled()) {
LogMessage.UnstructuredLogMessage unstructuredLogMessage = logMessage.toUnstructuredLogMessage();
String message = unstructuredLogMessage.interpolate();
Throwable exception = logMessage.getException();
if (exception == null) {
logger.debug(message);
} else {
logger.debug(message, exception);
}
}
logUnstructured(logMessage, Logger::isDebugEnabled, Logger::debug, Logger::debug);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The inference magic is strong in this code, when it comes to the last two arguments.

break;
case INFO:
logUnstructured(logMessage, Logger::isInfoEnabled, Logger::info, Logger::info);
break;
default:
throw new UnsupportedOperationException();
}
}

private void logUnstructured(
final LogMessage logMessage,
final Predicate<Logger> loggingEnabled,
final BiConsumer<Logger, String> doLog,
final TriConsumer<Logger, String, Throwable> doLogWithException) {
if (loggingEnabled.test(logger)) {
LogMessage.UnstructuredLogMessage unstructuredLogMessage = logMessage.toUnstructuredLogMessage();
String message = unstructuredLogMessage.interpolate();
Throwable exception = logMessage.getException();
if (exception == null) {
doLog.accept(logger, message);
} else {
doLogWithException.accept(logger, message, exception);
}
}
}

@FunctionalInterface
private interface TriConsumer<A, B, C> {
vbabanin marked this conversation as resolved.
Show resolved Hide resolved
void accept(A a, B b, C c);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import com.mongodb.MongoException;
import com.mongodb.ReadPreference;
import com.mongodb.ServerApi;
import com.mongodb.connection.ClusterDescription;
import com.mongodb.connection.ServerDescription;
import com.mongodb.internal.IgnorableRequestContext;
import com.mongodb.internal.binding.StaticBindingContext;
Expand All @@ -29,6 +30,7 @@
import com.mongodb.internal.selector.ReadPreferenceServerSelector;
import com.mongodb.internal.validator.NoOpFieldNameValidator;
import com.mongodb.lang.Nullable;
import com.mongodb.selector.ServerSelector;
import com.mongodb.session.ServerSession;
import org.bson.BsonArray;
import org.bson.BsonBinary;
Expand Down Expand Up @@ -114,24 +116,40 @@ private void endClosedSessions() {
return;
}

List<ServerDescription> primaryPreferred = new ReadPreferenceServerSelector(ReadPreference.primaryPreferred())
ReadPreference primaryPreferred = ReadPreference.primaryPreferred();
List<ServerDescription> primaryPreferredServers = new ReadPreferenceServerSelector(primaryPreferred)
.select(cluster.getCurrentDescription());
if (primaryPreferred.isEmpty()) {
if (primaryPreferredServers.isEmpty()) {
// Skip doing server selection if we anticipate that no server is readily selectable.
// This approach is racy, and it is still possible to become blocked selecting a server
// even if `primaryPreferredServers` is not empty.
return;
}

Connection connection = null;
try {
StaticBindingContext context = new StaticBindingContext(NoOpSessionContext.INSTANCE, serverApi,
IgnorableRequestContext.INSTANCE, new OperationContext());
connection = cluster.selectServer(clusterDescription -> {
for (ServerDescription cur : clusterDescription.getServerDescriptions()) {
if (cur.getAddress().equals(primaryPreferred.get(0).getAddress())) {
return Collections.singletonList(cur);
}
}
return Collections.emptyList();
}, context.getOperationContext()).getServer().getConnection(context.getOperationContext());
connection = cluster.selectServer(
new ServerSelector() {
@Override
public List<ServerDescription> select(final ClusterDescription clusterDescription) {
for (ServerDescription cur : clusterDescription.getServerDescriptions()) {
if (cur.getAddress().equals(primaryPreferredServers.get(0).getAddress())) {
return Collections.singletonList(cur);
}
}
return Collections.emptyList();
}

@Override
public String toString() {
return "ReadPreferenceServerSelector{"
+ "readPreference=" + primaryPreferred
+ '}';
}
},
context.getOperationContext()).getServer().getConnection(context.getOperationContext());
Comment on lines +119 to +152
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change is just to implement toString for this custom ServerSelector. Also left a comment explaining the idea behind this custom selector and the issue with it.


connection.command("admin",
new BsonDocument("endSessions", new BsonArray(identifiers)), new NoOpFieldNameValidator(),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
{
"description": "server-selection-logging",
"schemaVersion": "1.13",
"runOnRequirements": [
{
"topologies": [
"load-balanced"
]
}
],
"createEntities": [
{
"client": {
"id": "client",
"uriOptions": {
"heartbeatFrequencyMS": 500
},
"observeLogMessages": {
"serverSelection": "debug"
},
"observeEvents": [
"serverDescriptionChangedEvent"
]
}
},
{
"database": {
"id": "database",
"client": "client",
"databaseName": "logging-tests"
}
},
{
"collection": {
"id": "collection",
"database": "database",
"collectionName": "server-selection"
}
}
],
"tests": [
{
"description": "A successful operation - load balanced cluster",
"operations": [
{
"name": "waitForEvent",
"object": "testRunner",
"arguments": {
"client": "client",
"event": {
"serverDescriptionChangedEvent": {
"newDescription": {
"type": "LoadBalancer"
}
}
},
"count": 1
}
},
{
"name": "insertOne",
"object": "collection",
"arguments": {
"document": {
"x": 1
}
}
}
],
"expectLogMessages": [
{
"client": "client",
"messages": [
{
"level": "debug",
"component": "serverSelection",
"data": {
"message": "Server selection started",
"selector": {
"$$exists": true
},
"operation": "insert",
"topologyDescription": {
"$$exists": true
}
}
},
{
"level": "debug",
"component": "serverSelection",
"data": {
"message": "Server selection succeeded",
"selector": {
"$$exists": true
},
"operation": "insert",
"topologyDescription": {
"$$exists": true
}
}
}
]
}
]
}
]
}