Skip to content

Commit 3fcef21

Browse files
SNOW-3411804: proper logging for auto-config related messages with Easy Logging and the config key provenance added in 2625 (#2632)
1 parent 9b31421 commit 3fcef21

8 files changed

Lines changed: 200 additions & 24 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
- Enhancement: now parameters passed as `Properties()` are also considered when building connection. For conflicting items defined in multiple places, priority is: Properties > JDBC URL > `connections.toml`
1111
- Enhancement (supportability): added provenance tracking for config keys and log them once per connection on debug level
1212
- Fixed IllegalStateException when creating new Snowflake connections during JVM shutdown (SIGTERM); HeartbeatRegistry now skips heartbeat registration gracefully instead of throwing (snowflakedb/snowflake-jdbc#2617).
13+
- Fixed auto-config debug log messages (provenance, TOML parsing) not appearing in `client_config_file`-governed log file; messages are now replayed after logger initialization so they reach the FileHandler (snowflakedb/snowflake-jdbc#2632).
1314

1415
- v4.2.0
1516
- Extended the `SKIP_TOKEN_FILE_PERMISSIONS_VERIFICATION` environment variable to also bypass permission verification on the `connections.toml` config file and on the credential cache file (`credential_cache_v1.json`), unblocking driver use in SPCS environments where strict 0600/0700 ownership cannot be guaranteed (snowflakedb/snowflake-jdbc#2614)

src/main/java/net/snowflake/client/internal/config/ConnectionParameters.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
package net.snowflake.client.internal.config;
22

3+
import java.util.ArrayList;
4+
import java.util.List;
35
import java.util.Properties;
46

57
public class ConnectionParameters {
68
private final String url;
79
private final Properties params;
10+
private final List<String> deferredLogMessages = new ArrayList<>();
811

912
public ConnectionParameters(String uri, Properties params) {
1013
this.url = uri;
@@ -18,4 +21,12 @@ public String getUrl() {
1821
public Properties getParams() {
1922
return params;
2023
}
24+
25+
public void addDeferredLogMessage(String message) {
26+
deferredLogMessages.add(message);
27+
}
28+
29+
public List<String> getDeferredLogMessages() {
30+
return deferredLogMessages;
31+
}
2132
}

src/main/java/net/snowflake/client/internal/config/SFConnectionConfigParser.java

Lines changed: 41 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -51,24 +51,32 @@ public class SFConnectionConfigParser {
5151

5252
public static ConnectionParameters buildConnectionParameters(String connectionUrl)
5353
throws SnowflakeSQLException {
54-
return buildConnectionParameters(connectionUrl, new HashMap<>());
54+
return buildConnectionParameters(connectionUrl, new HashMap<>(), null);
5555
}
5656

5757
/**
5858
* Build connection parameters from URL and TOML file, optionally tracking provenance. When
59-
* provenance is non-null, each key's source ("TOML" or "URL") is recorded as it is resolved.
59+
* provenance is non-null, each key's source ("TOML" or "URL") is recorded as it is resolved. When
60+
* deferredMessages is non-null, debug log messages are also buffered for later replay.
6061
*/
6162
public static ConnectionParameters buildConnectionParameters(
62-
String connectionUrl, Map<String, String> provenance) throws SnowflakeSQLException {
63+
String connectionUrl, Map<String, String> provenance, List<String> deferredMessages)
64+
throws SnowflakeSQLException {
6365
Map<String, String> urlParameters = parseAutoConfigJdbcUrlParameters(connectionUrl);
6466
String defaultConnectionName = urlParameters.get("connectionName");
6567
if (isBlank(defaultConnectionName)) {
6668
defaultConnectionName =
6769
Optional.ofNullable(systemGetEnv(SNOWFLAKE_DEFAULT_CONNECTION_NAME_KEY)).orElse(DEFAULT);
6870
}
69-
logger.debug("Attempting to load the configuration {} from toml file.", defaultConnectionName);
71+
String attemptMsg =
72+
String.format(
73+
"Attempting to load the configuration %s from toml file.", defaultConnectionName);
74+
logger.debug(attemptMsg);
75+
if (deferredMessages != null) {
76+
deferredMessages.add(attemptMsg);
77+
}
7078
Map<String, String> fileConnectionConfiguration =
71-
loadDefaultConnectionConfiguration(defaultConnectionName);
79+
loadDefaultConnectionConfiguration(defaultConnectionName, deferredMessages);
7280

7381
if (fileConnectionConfiguration != null && !fileConnectionConfiguration.isEmpty()) {
7482
if (provenance != null) {
@@ -88,7 +96,12 @@ public static ConnectionParameters buildConnectionParameters(
8896
connectionProperties.putAll(fileConnectionConfiguration);
8997

9098
String url = createUrl(fileConnectionConfiguration);
91-
logger.debug("Url created using parameters from connection configuration file: {}", url);
99+
String urlMsg =
100+
String.format("Url created using parameters from connection configuration file: %s", url);
101+
logger.debug(urlMsg);
102+
if (deferredMessages != null) {
103+
deferredMessages.add(urlMsg);
104+
}
92105

93106
if ("oauth".equals(fileConnectionConfiguration.get("authenticator"))
94107
&& fileConnectionConfiguration.get("token") == null) {
@@ -177,7 +190,7 @@ private static void mergeUrlParametersIntoConfiguration(
177190
}
178191

179192
private static Map<String, String> loadDefaultConnectionConfiguration(
180-
String defaultConnectionName) throws SnowflakeSQLException {
193+
String defaultConnectionName, List<String> deferredMessages) throws SnowflakeSQLException {
181194
String configDirectory = systemGetEnv(SNOWFLAKE_HOME_KEY);
182195
if (configDirectory == null) {
183196
String homeDir = systemGetProperty("user.home");
@@ -190,18 +203,33 @@ private static Map<String, String> loadDefaultConnectionConfiguration(
190203
Path configFilePath = Paths.get(configDirectory, "connections.toml");
191204

192205
if (Files.exists(configFilePath)) {
193-
logger.debug(
194-
"Reading connection parameters from file {} using key: {}",
195-
configFilePath,
196-
defaultConnectionName);
206+
String readMsg =
207+
String.format(
208+
"Reading connection parameters from file %s using key: %s",
209+
configFilePath, defaultConnectionName);
210+
logger.debug(readMsg);
211+
if (deferredMessages != null) {
212+
deferredMessages.add(readMsg);
213+
}
197214
Map<String, Map> parametersMap = readParametersMap(configFilePath);
198215
Map<String, String> defaultConnectionParametersMap = parametersMap.get(defaultConnectionName);
199216
if (defaultConnectionParametersMap == null) {
200-
logger.debug("The Connection {} not found in connections.toml.", defaultConnectionName);
217+
String notFoundMsg =
218+
String.format(
219+
"The Connection %s not found in connections.toml.", defaultConnectionName);
220+
logger.debug(notFoundMsg);
221+
if (deferredMessages != null) {
222+
deferredMessages.add(notFoundMsg);
223+
}
201224
throw new SnowflakeSQLException(
202225
"The Connection " + defaultConnectionName + " not found in connections.toml file.");
203226
} else {
204-
logger.debug("The Connection {} found in connections.toml.", defaultConnectionName);
227+
String foundMsg =
228+
String.format("The Connection %s found in connections.toml.", defaultConnectionName);
229+
logger.debug(foundMsg);
230+
if (deferredMessages != null) {
231+
deferredMessages.add(foundMsg);
232+
}
205233
}
206234
return defaultConnectionParametersMap;
207235
} else {

src/main/java/net/snowflake/client/internal/driver/AutoConfigurationHelper.java

Lines changed: 34 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
package net.snowflake.client.internal.driver;
22

3+
import java.util.ArrayList;
34
import java.util.HashMap;
5+
import java.util.List;
46
import java.util.Map;
57
import java.util.Properties;
68
import java.util.StringJoiner;
9+
import java.util.TreeMap;
710
import net.snowflake.client.api.exception.SnowflakeSQLException;
811
import net.snowflake.client.internal.config.ConnectionParameters;
912
import net.snowflake.client.internal.config.SFConnectionConfigParser;
@@ -63,17 +66,30 @@ public static boolean isAutoConfigurationUrl(String url) {
6366
* @return ConnectionParameters with resolved configuration
6467
* @throws SnowflakeSQLException if auto-configuration is requested but fails
6568
*/
69+
/**
70+
* Internal key used by ConnectionFactory to carry deferred log messages on the Properties object.
71+
* The value is a {@code List<String>} (not a String), so it won't interfere with session property
72+
* parsing. ConnectionParameters is discarded before reaching DefaultSFConnectionHandler, so
73+
* ConnectionFactory transfers the messages onto Properties which does flow through.
74+
*/
75+
public static final String DEFERRED_LOG_MESSAGES_KEY = "_snowflake.internal.deferredLogMessages";
76+
6677
public static ConnectionParameters resolveConnectionParameters(String url, Properties info)
6778
throws SnowflakeSQLException {
6879

6980
if (isAutoConfigurationUrl(url)) {
70-
logger.debug(
71-
"JDBC connection initializing with URL '{}'. Autoconfiguration is enabled.",
72-
AUTO_CONNECTION_PREFIX);
81+
List<String> deferredMessages = new ArrayList<>();
82+
83+
String initMsg =
84+
String.format(
85+
"JDBC connection initializing with URL '%s'. Autoconfiguration is enabled.",
86+
AUTO_CONNECTION_PREFIX);
87+
logger.debug(initMsg);
88+
deferredMessages.add(initMsg);
7389

7490
Map<String, String> provenance = new HashMap<>();
7591
ConnectionParameters params =
76-
SFConnectionConfigParser.buildConnectionParameters(url, provenance);
92+
SFConnectionConfigParser.buildConnectionParameters(url, provenance, deferredMessages);
7793
if (params == null) {
7894
throw new SnowflakeSQLException(
7995
"Unavailable connection configuration parameters expected for "
@@ -89,7 +105,11 @@ public static ConnectionParameters resolveConnectionParameters(String url, Prope
89105
}
90106
}
91107

92-
logAutoConfigProvenance(provenance);
108+
logAutoConfigProvenance(provenance, deferredMessages);
109+
110+
for (String msg : deferredMessages) {
111+
params.addDeferredLogMessage(msg);
112+
}
93113

94114
return params;
95115
} else {
@@ -132,15 +152,18 @@ private static void mergeInfoPropertiesIntoParams(
132152

133153
// Only covers the jdbc:snowflake:auto path. The standard jdbc:snowflake:// path merges
134154
// URL+Properties in SnowflakeConnectString.parse without provenance tracking.
135-
private static void logAutoConfigProvenance(Map<String, String> provenance) {
136-
if (!logger.isDebugEnabled()) {
137-
return;
138-
}
155+
private static void logAutoConfigProvenance(
156+
Map<String, String> provenance, List<String> deferredMessages) {
139157
StringJoiner sj = new StringJoiner(", ");
140-
for (Map.Entry<String, String> entry : provenance.entrySet()) {
158+
for (Map.Entry<String, String> entry : new TreeMap<>(provenance).entrySet()) {
141159
sj.add(entry.getKey() + "(" + entry.getValue() + ")");
142160
}
143-
logger.debug("Auto-configuration resolved properties: [{}]", sj.toString());
161+
String provenanceMsg =
162+
String.format("Auto-configuration resolved properties: [%s]", sj.toString());
163+
logger.debug(provenanceMsg);
164+
if (deferredMessages != null) {
165+
deferredMessages.add(provenanceMsg);
166+
}
144167
provenance.clear();
145168
}
146169
}

src/main/java/net/snowflake/client/internal/driver/ConnectionFactory.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import java.sql.Connection;
44
import java.sql.SQLException;
5+
import java.util.List;
56
import java.util.Properties;
67
import net.snowflake.client.api.exception.SnowflakeSQLException;
78
import net.snowflake.client.internal.api.implementation.connection.SnowflakeConnectionImpl;
@@ -59,6 +60,13 @@ public static Connection createConnection(String url, Properties info) throws SQ
5960
throw new SnowflakeSQLException("Connection string is invalid. Unable to parse.");
6061
}
6162

63+
// Transfer deferred log messages from ConnectionParameters to Properties so they survive
64+
// into DefaultSFConnectionHandler.initialize() for replay after initLogger().
65+
List<String> deferred = params.getDeferredLogMessages();
66+
if (deferred != null && !deferred.isEmpty()) {
67+
params.getParams().put(AutoConfigurationHelper.DEFERRED_LOG_MESSAGES_KEY, deferred);
68+
}
69+
6270
// Create and return the connection implementation
6371
return new SnowflakeConnectionImpl(params.getUrl(), params.getParams());
6472
}

src/main/java/net/snowflake/client/internal/jdbc/DefaultSFConnectionHandler.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
import net.snowflake.client.internal.core.SFSession;
3939
import net.snowflake.client.internal.core.SFSessionProperty;
4040
import net.snowflake.client.internal.core.SFStatement;
41+
import net.snowflake.client.internal.driver.AutoConfigurationHelper;
4142
import net.snowflake.client.internal.exception.SnowflakeSQLLoggedException;
4243
import net.snowflake.client.internal.jdbc.telemetryOOB.TelemetryService;
4344
import net.snowflake.client.internal.jdbc.util.DriverUtil;
@@ -143,6 +144,7 @@ protected void initialize(
143144
initSessionProperties(conStr, appID, appVersion);
144145
setClientConfig();
145146
initLogger();
147+
replayDeferredLogMessages(properties);
146148
initHttpHeaderCustomizers(properties);
147149
logger.debug("Trying to establish session, JDBC driver: {}", DriverUtil.getJdbcJarname());
148150
if (!skipOpen) {
@@ -230,6 +232,26 @@ && systemGetProperty("java.util.logging.config.file") == null) {
230232
}
231233
}
232234

235+
@SuppressWarnings("unchecked")
236+
private void replayDeferredLogMessages(Properties properties) {
237+
if (properties == null) {
238+
return;
239+
}
240+
Object deferredObj = properties.remove(AutoConfigurationHelper.DEFERRED_LOG_MESSAGES_KEY);
241+
if (!(deferredObj instanceof List)) {
242+
return;
243+
}
244+
// Only replay under JDK14Logger: under SLF4J/external JUL, the immediate log calls already
245+
// reached the correct destination, so replaying would produce duplicates.
246+
if (!(logger instanceof JDK14Logger)) {
247+
return;
248+
}
249+
List<String> deferredMessages = (List<String>) deferredObj;
250+
for (String msg : deferredMessages) {
251+
logger.debug(msg);
252+
}
253+
}
254+
233255
private Path getLogPath(String logPathFromConfig) throws SnowflakeSQLLoggedException {
234256
if (JDK14Logger.STDOUT.equalsIgnoreCase(logPathFromConfig)) {
235257
return null;

src/test/java/net/snowflake/client/internal/config/SFConnectionConfigParserTest.java

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -618,6 +618,69 @@ public void testNonStringPrivateKeyPreservedInThreeWayMerge() throws Exception {
618618
assertEquals("TOML_USER", data.getParams().get("user"));
619619
}
620620

621+
// Verifies deferred log messages are populated and provenance is sorted alphabetically
622+
@Test
623+
public void testDeferredLogMessagesAndSortedProvenance()
624+
throws SnowflakeSQLException, IOException {
625+
SnowflakeUtil.systemSetEnv(SNOWFLAKE_HOME_KEY, tempPath.toString());
626+
SnowflakeUtil.systemSetEnv(SNOWFLAKE_DEFAULT_CONNECTION_NAME_KEY, "default");
627+
prepareTomlWithDatabase("TOML_DB", "TOML_WH", "TOML_ROLE");
628+
629+
Properties info = new Properties();
630+
info.setProperty("warehouse", "PROP_WH");
631+
info.setProperty("schema", "PROP_SCHEMA");
632+
633+
ConnectionParameters data =
634+
AutoConfigurationHelper.resolveConnectionParameters(
635+
"jdbc:snowflake:auto?connectionName=default&db=URL_DB&tracing=WARNING", info);
636+
assertNotNull(data);
637+
638+
List<String> deferred = data.getDeferredLogMessages();
639+
assertNotNull(deferred);
640+
assertFalse(deferred.isEmpty());
641+
642+
assertTrue(deferred.stream().anyMatch(m -> m.contains("Attempting to load the configuration")));
643+
assertTrue(
644+
deferred.stream().anyMatch(m -> m.contains("Reading connection parameters from file")));
645+
assertTrue(deferred.stream().anyMatch(m -> m.contains("found in connections.toml")));
646+
assertTrue(
647+
deferred.stream()
648+
.anyMatch(m -> m.contains("Url created using parameters from connection")));
649+
assertTrue(deferred.stream().anyMatch(m -> m.contains("Autoconfiguration is enabled")));
650+
651+
String provenanceLine =
652+
deferred.stream()
653+
.filter(m -> m.contains("Auto-configuration resolved properties"))
654+
.findFirst()
655+
.orElse(null);
656+
assertNotNull(provenanceLine);
657+
658+
// Provenance must be sorted alphabetically by key
659+
int accountIdx = provenanceLine.indexOf("account(");
660+
int dbIdx = provenanceLine.indexOf("db(");
661+
int passwordIdx = provenanceLine.indexOf("password(");
662+
int roleIdx = provenanceLine.indexOf("role(");
663+
int schemaIdx = provenanceLine.indexOf("schema(");
664+
int tracingIdx = provenanceLine.indexOf("tracing(");
665+
int userIdx = provenanceLine.indexOf("user(");
666+
int warehouseIdx = provenanceLine.indexOf("warehouse(");
667+
assertTrue(accountIdx < dbIdx);
668+
assertTrue(dbIdx < passwordIdx);
669+
assertTrue(passwordIdx < roleIdx);
670+
assertTrue(roleIdx < schemaIdx);
671+
assertTrue(schemaIdx < tracingIdx);
672+
assertTrue(tracingIdx < userIdx);
673+
assertTrue(userIdx < warehouseIdx);
674+
675+
// Verify provenance content
676+
assertTrue(provenanceLine.contains("db(URL(overrode TOML))"));
677+
assertTrue(provenanceLine.contains("warehouse(PROP(overrode TOML))"));
678+
assertTrue(provenanceLine.contains("schema(PROP)"));
679+
assertTrue(provenanceLine.contains("tracing(URL)"));
680+
assertTrue(provenanceLine.contains("role(TOML)"));
681+
assertTrue(provenanceLine.contains("account(TOML)"));
682+
}
683+
621684
private void prepareTomlWithDatabase(String databaseValue) throws IOException {
622685
prepareTomlWithDatabase(databaseValue, null, null, null);
623686
}

src/test/java/net/snowflake/client/internal/jdbc/DefaultSFConnectionHandlerTest.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import static net.snowflake.client.api.http.HttpHeadersCustomizer.HTTP_HEADER_CUSTOMIZERS_PROPERTY_KEY;
44
import static org.junit.jupiter.api.Assertions.assertEquals;
55
import static org.junit.jupiter.api.Assertions.assertFalse;
6+
import static org.junit.jupiter.api.Assertions.assertNull;
67
import static org.junit.jupiter.api.Assertions.assertTrue;
78
import static org.mockito.ArgumentMatchers.anyList;
89
import static org.mockito.Mockito.mock;
@@ -17,6 +18,7 @@
1718
import java.util.Properties;
1819
import net.snowflake.client.api.http.HttpHeadersCustomizer;
1920
import net.snowflake.client.internal.core.SFSession;
21+
import net.snowflake.client.internal.driver.AutoConfigurationHelper;
2022
import org.junit.jupiter.api.BeforeEach;
2123
import org.junit.jupiter.api.Test;
2224
import org.mockito.ArgumentCaptor;
@@ -114,4 +116,22 @@ void testEmptyHttpHeaderCustomizersWhenPropertyHasOnlyInvalidTypes() throws SQLE
114116
verify(mockSession).setHttpHeadersCustomizers(listCaptor.capture());
115117
assertTrue(listCaptor.getValue().isEmpty());
116118
}
119+
120+
// Verifies deferred log messages are removed from Properties after replay
121+
@Test
122+
void testReplayDeferredLogMessagesRemovesKeyFromProperties() throws SQLException {
123+
List<String> msgs = Arrays.asList("msg1", "msg2");
124+
properties.put(AutoConfigurationHelper.DEFERRED_LOG_MESSAGES_KEY, msgs);
125+
126+
connectionHandler.initialize(connectString, "", "", properties);
127+
128+
assertNull(properties.get(AutoConfigurationHelper.DEFERRED_LOG_MESSAGES_KEY));
129+
}
130+
131+
// Verifies no error when Properties has no deferred messages
132+
@Test
133+
void testReplayDeferredLogMessagesNoop() throws SQLException {
134+
connectionHandler.initialize(connectString, "", "", properties);
135+
assertNull(properties.get(AutoConfigurationHelper.DEFERRED_LOG_MESSAGES_KEY));
136+
}
117137
}

0 commit comments

Comments
 (0)