diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/config/DefaultDriverOption.java b/core/src/main/java/com/datastax/oss/driver/api/core/config/DefaultDriverOption.java
index 56a6fe9c2be..3a6e4ed69bb 100644
--- a/core/src/main/java/com/datastax/oss/driver/api/core/config/DefaultDriverOption.java
+++ b/core/src/main/java/com/datastax/oss/driver/api/core/config/DefaultDriverOption.java
@@ -1173,7 +1173,22 @@ public enum DefaultDriverOption implements DriverOption {
*
*
Value-Type: boolean
*/
- ADDRESS_TRANSLATOR_RESOLVE_ADDRESSES("advanced.address-translator.resolve-addresses");
+ ADDRESS_TRANSLATOR_RESOLVE_ADDRESSES("advanced.address-translator.resolve-addresses"),
+ /**
+ * Whether the driver reports its effective configuration to ScyllaDB at connection time.
+ *
+ *
When {@code true}, the driver adds two entries to the CQL {@code STARTUP} options, which
+ * ScyllaDB stores in {@code system.clients.client_options} so operators can inspect driver
+ * settings while investigating incidents: a {@code SESSION_ID} on every connection (so the server
+ * can group a session's connections) and a compact JSON payload under the {@code DRIVER_CONFIG}
+ * key on the control connection only. At this stage the {@code DRIVER_CONFIG} payload carries
+ * only schema-version metadata ({"version":1}); reporting of the effective
+ * configuration fields is planned for a later stage. When {@code false}, neither entry is sent
+ * and there is no change on the wire.
+ *
+ *
Value type: boolean
+ */
+ DRIVER_CONFIG_REPORTING_ENABLED("advanced.driver-config-reporting.enabled");
private final String path;
diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/config/OptionsMap.java b/core/src/main/java/com/datastax/oss/driver/api/core/config/OptionsMap.java
index 60190fc1cce..1d906caa985 100644
--- a/core/src/main/java/com/datastax/oss/driver/api/core/config/OptionsMap.java
+++ b/core/src/main/java/com/datastax/oss/driver/api/core/config/OptionsMap.java
@@ -400,6 +400,7 @@ protected static void fillWithDriverDefaults(OptionsMap map) {
// values) with no sensible scalar default, analogous to how CONFIG_RELOAD_INTERVAL is omitted.
map.put(TypedDriverOption.CLIENT_ROUTES_NATIVE_TRANSPORT_PORT, 9042);
map.put(TypedDriverOption.CLIENT_ROUTES_SHARD_AWARENESS_ENABLED, false);
+ map.put(TypedDriverOption.DRIVER_CONFIG_REPORTING_ENABLED, false);
}
@Immutable
diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/config/TypedDriverOption.java b/core/src/main/java/com/datastax/oss/driver/api/core/config/TypedDriverOption.java
index e7c606cade8..e412b99b404 100644
--- a/core/src/main/java/com/datastax/oss/driver/api/core/config/TypedDriverOption.java
+++ b/core/src/main/java/com/datastax/oss/driver/api/core/config/TypedDriverOption.java
@@ -976,6 +976,11 @@ public String toString() {
new TypedDriverOption<>(
DefaultDriverOption.CLIENT_ROUTES_SHARD_AWARENESS_ENABLED, GenericType.BOOLEAN);
+ /** Whether the driver reports its configuration to ScyllaDB at connection time. */
+ public static final TypedDriverOption DRIVER_CONFIG_REPORTING_ENABLED =
+ new TypedDriverOption<>(
+ DefaultDriverOption.DRIVER_CONFIG_REPORTING_ENABLED, GenericType.BOOLEAN);
+
private static Iterable> introspectBuiltInValues() {
try {
ImmutableList.Builder> result = ImmutableList.builder();
diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/DriverChannelOptions.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/DriverChannelOptions.java
index 208cf52ac22..378fd2dc0b8 100644
--- a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/DriverChannelOptions.java
+++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/DriverChannelOptions.java
@@ -47,15 +47,24 @@ public static Builder builder() {
public final String ownerLogPrefix;
+ /**
+ * Whether the channel should report the driver configuration ({@code DRIVER_CONFIG}) in its
+ * {@code STARTUP} options. Set only for the control connection, so the (potentially large) config
+ * blob is sent once per session rather than on every pooled connection.
+ */
+ public final boolean reportConfig;
+
private DriverChannelOptions(
CqlIdentifier keyspace,
List eventTypes,
EventCallback eventCallback,
- String ownerLogPrefix) {
+ String ownerLogPrefix,
+ boolean reportConfig) {
this.keyspace = keyspace;
this.eventTypes = eventTypes;
this.eventCallback = eventCallback;
this.ownerLogPrefix = ownerLogPrefix;
+ this.reportConfig = reportConfig;
}
public static class Builder {
@@ -63,6 +72,7 @@ public static class Builder {
private List eventTypes = Collections.emptyList();
private EventCallback eventCallback = null;
private String ownerLogPrefix = null;
+ private boolean reportConfig = false;
public Builder withKeyspace(CqlIdentifier keyspace) {
this.keyspace = keyspace;
@@ -82,8 +92,17 @@ public Builder withOwnerLogPrefix(String ownerLogPrefix) {
return this;
}
+ /**
+ * Marks this channel as the one that reports {@code DRIVER_CONFIG} (the control connection).
+ */
+ public Builder reportConfig(boolean reportConfig) {
+ this.reportConfig = reportConfig;
+ return this;
+ }
+
public DriverChannelOptions build() {
- return new DriverChannelOptions(keyspace, eventTypes, eventCallback, ownerLogPrefix);
+ return new DriverChannelOptions(
+ keyspace, eventTypes, eventCallback, ownerLogPrefix, reportConfig);
}
}
}
diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandler.java
index 85bffdb016e..cf782096f18 100644
--- a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandler.java
+++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandler.java
@@ -194,6 +194,11 @@ Message getRequest() {
if (featureStore != null) {
featureStore.populateStartupOptions(startupOptions);
}
+ // Adds SESSION_ID on every connection and DRIVER_CONFIG on the control connection
+ // (options.reportConfig); no-op when driver config reporting is disabled.
+ context
+ .getDriverConfigReporter()
+ .populateStartupOptions(startupOptions, options.reportConfig);
return request = new Startup(startupOptions);
case GET_CLUSTER_NAME:
return request = CLUSTER_NAME_QUERY;
diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverConfigReporter.java b/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverConfigReporter.java
new file mode 100644
index 00000000000..2cd1b5a8560
--- /dev/null
+++ b/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverConfigReporter.java
@@ -0,0 +1,131 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.datastax.oss.driver.internal.core.context;
+
+import com.datastax.oss.driver.api.core.config.DefaultDriverOption;
+import com.datastax.oss.driver.api.core.config.DriverExecutionProfile;
+import com.datastax.oss.driver.api.core.uuid.Uuids;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import java.util.Map;
+import java.util.UUID;
+import net.jcip.annotations.ThreadSafe;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Default {@link DriverConfigReporter}: serializes the driver configuration to the cross-driver
+ * {@code DRIVER_CONFIG} JSON shape and adds it to the control connection's {@code STARTUP} options.
+ *
+ * The blob is (re)built on demand every time the control connection initializes, so it always
+ * reflects the current (possibly reloaded) configuration without any caching.
+ */
+@ThreadSafe
+public class DefaultDriverConfigReporter implements DriverConfigReporter {
+
+ private static final Logger LOG = LoggerFactory.getLogger(DefaultDriverConfigReporter.class);
+
+ /** STARTUP option key under which the config JSON is sent. */
+ public static final String DRIVER_CONFIG_KEY = "DRIVER_CONFIG";
+
+ /** STARTUP option key under which the per-session identifier is sent. */
+ public static final String SESSION_ID_KEY = "SESSION_ID";
+
+ /**
+ * Major schema version. Adding keys is backward-compatible and does not bump this; only
+ * changing/removing the meaning of an existing key does.
+ */
+ static final int SCHEMA_VERSION = 1;
+
+ private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
+
+ protected final InternalDriverContext context;
+
+ // Dedicated, driver-generated identifier for this session. Not derived from the (user-settable,
+ // Insights-oriented) CLIENT_ID, so that it is guaranteed unique per session as the grouping key
+ // requires. The reporter is a per-session singleton (built once via LazyReference), so this value
+ // is stable and shared across all of the session's connections.
+ private final UUID sessionId = Uuids.random();
+
+ public DefaultDriverConfigReporter(InternalDriverContext context) {
+ this.context = context;
+ }
+
+ @Override
+ public void populateStartupOptions(
+ Map startupOptions, boolean reportDriverConfig) {
+ // Configuration reporting is a best-effort diagnostic aid: it runs on the connection
+ // initialization path, so any failure here (a bad config read, a misbehaving policy while
+ // introspecting, a serialization error) must be swallowed rather than allowed to break the
+ // connection — which would prevent the session from establishing or reconnecting.
+ try {
+ if (!isEnabled()) {
+ return;
+ }
+ // SESSION_ID on every connection so the server can group a session's connections.
+ startupOptions.put(SESSION_ID_KEY, sessionId.toString());
+ // DRIVER_CONFIG blob only on the control connection.
+ if (reportDriverConfig) {
+ String json = buildJson();
+ if (json != null) {
+ startupOptions.put(DRIVER_CONFIG_KEY, json);
+ }
+ }
+ } catch (RuntimeException e) {
+ LOG.warn(
+ "Error while building the driver configuration report; skipping driver config reporting",
+ e);
+ }
+ }
+
+ private boolean isEnabled() {
+ return context
+ .getConfig()
+ .getDefaultProfile()
+ .getBoolean(DefaultDriverOption.DRIVER_CONFIG_REPORTING_ENABLED, false);
+ }
+
+ /**
+ * Builds the compact, single-line JSON configuration report.
+ *
+ * Stage 1 emits only the schema {@code version}; the individual configuration groups are
+ * populated in {@link #populateConfig(ObjectNode, DriverExecutionProfile)} in a later stage.
+ */
+ protected String buildJson() {
+ ObjectNode root = OBJECT_MAPPER.createObjectNode();
+ root.put("version", SCHEMA_VERSION);
+ populateConfig(root, context.getConfig().getDefaultProfile());
+ try {
+ return OBJECT_MAPPER.writeValueAsString(root);
+ } catch (JsonProcessingException e) {
+ // An in-memory node tree should never fail to serialize; never let it break connection setup.
+ LOG.warn("Failed to serialize driver configuration report; skipping DRIVER_CONFIG", e);
+ return null;
+ }
+ }
+
+ /**
+ * Populates the configuration groups onto the report root. Placeholder in Stage 1; Stage 2 fills
+ * in {@code connection}, {@code socket}, the policy groups, {@code query_defaults}, {@code tls},
+ * etc.
+ */
+ protected void populateConfig(ObjectNode root, DriverExecutionProfile config) {
+ // Stage 2: populate configuration groups from `config` and the context's policies.
+ }
+}
diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java b/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java
index 52d9cdb264c..3d1d5b82b87 100644
--- a/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java
+++ b/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java
@@ -232,6 +232,8 @@ public class DefaultDriverContext implements InternalDriverContext {
new LazyReference<>("requestThrottler", this::buildRequestThrottler, cycleDetector);
private final LazyReference