diff --git a/src/main/java/net/snowflake/client/core/SFSession.java b/src/main/java/net/snowflake/client/core/SFSession.java index 5e583de30..ecef55de3 100644 --- a/src/main/java/net/snowflake/client/core/SFSession.java +++ b/src/main/java/net/snowflake/client/core/SFSession.java @@ -153,6 +153,9 @@ public class SFSession extends SFBaseSession { */ private Duration browserResponseTimeout = Duration.ofSeconds(120); + private boolean javaUtilLoggingConsoleOut = false; + private String javaUtilLoggingConsoleOutThreshold = null; + // This constructor is used only by tests with no real connection. // For real connections, the other constructor is always used. @VisibleForTesting @@ -437,8 +440,13 @@ public void addSFSessionProperty(String propertyName, Object propertyValue) thro } break; case JAVA_LOGGING_CONSOLE_STD_OUT: - if (propertyValue != null && (Boolean) propertyValue) { - JDK14Logger.useStdOutConsoleHandler(); + if (propertyValue != null) { + javaUtilLoggingConsoleOut = (Boolean) propertyValue; + } + break; + case JAVA_LOGGING_CONSOLE_STD_OUT_THRESHOLD: + if (propertyValue != null) { + javaUtilLoggingConsoleOutThreshold = (String) propertyValue; } break; @@ -562,6 +570,13 @@ public void addSFSessionProperty(String propertyName, Object propertyValue) thro } } + @SnowflakeJdbcInternalApi + public void overrideConsoleHandlerWhenNecessary() { + if (javaUtilLoggingConsoleOut) { + JDK14Logger.useStdOutConsoleHandler(javaUtilLoggingConsoleOutThreshold); + } + } + public boolean containProperty(String key) { return sessionParametersMap.containsKey(key); } diff --git a/src/main/java/net/snowflake/client/core/SFSessionProperty.java b/src/main/java/net/snowflake/client/core/SFSessionProperty.java index 6a6730c49..a5e7276c5 100644 --- a/src/main/java/net/snowflake/client/core/SFSessionProperty.java +++ b/src/main/java/net/snowflake/client/core/SFSessionProperty.java @@ -114,7 +114,9 @@ public enum SFSessionProperty { HTTP_CLIENT_SOCKET_TIMEOUT("HTTP_CLIENT_SOCKET_TIMEOUT", false, Integer.class), - JAVA_LOGGING_CONSOLE_STD_OUT("JAVA_LOGGING_CONSOLE_STD_OUT", false, Boolean.class); + JAVA_LOGGING_CONSOLE_STD_OUT("JAVA_LOGGING_CONSOLE_STD_OUT", false, Boolean.class), + JAVA_LOGGING_CONSOLE_STD_OUT_THRESHOLD( + "JAVA_LOGGING_CONSOLE_STD_OUT_THRESHOLD", false, String.class); // property key in string private String propertyKey; diff --git a/src/main/java/net/snowflake/client/jdbc/DefaultSFConnectionHandler.java b/src/main/java/net/snowflake/client/jdbc/DefaultSFConnectionHandler.java index ee9d5f307..48d5e0734 100644 --- a/src/main/java/net/snowflake/client/jdbc/DefaultSFConnectionHandler.java +++ b/src/main/java/net/snowflake/client/jdbc/DefaultSFConnectionHandler.java @@ -337,6 +337,7 @@ private void initSessionProperties(SnowflakeConnectString conStr, String appID, } sfSession.addSFSessionProperty(property.getKey(), property.getValue()); } + sfSession.overrideConsoleHandlerWhenNecessary(); // populate app id and version sfSession.addProperty(SFSessionProperty.APP_ID, appID); diff --git a/src/main/java/net/snowflake/client/log/JDK14Logger.java b/src/main/java/net/snowflake/client/log/JDK14Logger.java index 466031af2..7f2762fe1 100644 --- a/src/main/java/net/snowflake/client/log/JDK14Logger.java +++ b/src/main/java/net/snowflake/client/log/JDK14Logger.java @@ -50,17 +50,46 @@ public JDK14Logger(String name) { String javaLoggingConsoleStdOut = System.getProperty(SFSessionProperty.JAVA_LOGGING_CONSOLE_STD_OUT.getPropertyKey()); if ("true".equalsIgnoreCase(javaLoggingConsoleStdOut)) { - useStdOutConsoleHandler(); + String javaLoggingConsoleStdOutThreshold = + System.getProperty( + SFSessionProperty.JAVA_LOGGING_CONSOLE_STD_OUT_THRESHOLD.getPropertyKey()); + useStdOutConsoleHandler(javaLoggingConsoleStdOutThreshold); } } @SnowflakeJdbcInternalApi - public static void useStdOutConsoleHandler() { + public static void useStdOutConsoleHandler(String threshold) { + Level thresholdLevel = threshold != null ? tryParse(threshold) : null; Logger rootLogger = Logger.getLogger(""); for (Handler handler : rootLogger.getHandlers()) { if (handler instanceof ConsoleHandler) { rootLogger.removeHandler(handler); - rootLogger.addHandler(STD_OUT_CONSOLE_HANDLER); + if (thresholdLevel != null) { + rootLogger.addHandler(new StdErrOutThresholdAwareConsoleHandler(thresholdLevel)); + } else { + rootLogger.addHandler(new StdOutConsoleHandler()); + } + break; + } + } + } + + private static Level tryParse(String threshold) { + try { + return Level.parse(threshold); + } catch (Exception e) { + throw new UnknownJavaUtilLoggingLevelException(threshold); + } + } + + @SnowflakeJdbcInternalApi + static void resetToDefaultConsoleHandler() { + Logger rootLogger = Logger.getLogger(""); + for (Handler handler : rootLogger.getHandlers()) { + if (handler instanceof StdErrOutThresholdAwareConsoleHandler + || handler instanceof StdOutConsoleHandler) { + rootLogger.removeHandler(handler); + rootLogger.addHandler(new ConsoleHandler()); break; } } diff --git a/src/main/java/net/snowflake/client/log/StdErrOutThresholdAwareConsoleHandler.java b/src/main/java/net/snowflake/client/log/StdErrOutThresholdAwareConsoleHandler.java new file mode 100644 index 000000000..39ffff16a --- /dev/null +++ b/src/main/java/net/snowflake/client/log/StdErrOutThresholdAwareConsoleHandler.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2012-2019 Snowflake Computing Inc. All rights reserved. + */ +package net.snowflake.client.log; + +import java.util.logging.ConsoleHandler; +import java.util.logging.Level; +import java.util.logging.LogRecord; +import java.util.logging.SimpleFormatter; +import java.util.logging.StreamHandler; + +class StdErrOutThresholdAwareConsoleHandler extends StreamHandler { + private final ConsoleHandler stdErrConsoleHandler = new ConsoleHandler(); + private final Level threshold; + + public StdErrOutThresholdAwareConsoleHandler(Level threshold) { + super(System.out, new SimpleFormatter()); + this.threshold = threshold; + } + + @Override + public void publish(LogRecord record) { + if (record.getLevel().intValue() > threshold.intValue()) { + stdErrConsoleHandler.publish(record); + } else { + super.publish(record); + flush(); + } + } + + @Override + public void close() { + flush(); + stdErrConsoleHandler.close(); + } + + Level getThreshold() { + return threshold; + } +} diff --git a/src/main/java/net/snowflake/client/log/UnknownJavaUtilLoggingLevelException.java b/src/main/java/net/snowflake/client/log/UnknownJavaUtilLoggingLevelException.java new file mode 100644 index 000000000..7424b4cf0 --- /dev/null +++ b/src/main/java/net/snowflake/client/log/UnknownJavaUtilLoggingLevelException.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2012-2019 Snowflake Computing Inc. All rights reserved. + */ +package net.snowflake.client.log; + +import java.util.logging.Level; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +class UnknownJavaUtilLoggingLevelException extends RuntimeException { + private static final String AVAILABLE_LEVELS = + Stream.of( + Level.OFF, + Level.SEVERE, + Level.WARNING, + Level.INFO, + Level.CONFIG, + Level.FINE, + Level.FINER, + Level.FINEST, + Level.ALL) + .map(Level::getName) + .collect(Collectors.joining(", ")); + + UnknownJavaUtilLoggingLevelException(String threshold) { + super( + "Unknown java util logging level: " + threshold + ", expected one of: " + AVAILABLE_LEVELS); + } +} diff --git a/src/test/java/net/snowflake/client/log/JDK14LoggerConsoleHandlerOverrideLatestIT.java b/src/test/java/net/snowflake/client/log/JDK14LoggerConsoleHandlerOverrideLatestIT.java index 2e91e069d..2afa69931 100644 --- a/src/test/java/net/snowflake/client/log/JDK14LoggerConsoleHandlerOverrideLatestIT.java +++ b/src/test/java/net/snowflake/client/log/JDK14LoggerConsoleHandlerOverrideLatestIT.java @@ -3,37 +3,157 @@ */ package net.snowflake.client.log; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; import java.sql.Connection; -import java.sql.ResultSet; -import java.sql.Statement; +import java.sql.SQLException; import java.util.Arrays; import java.util.Properties; import java.util.logging.ConsoleHandler; import java.util.logging.Handler; +import java.util.logging.Level; import java.util.logging.Logger; import net.snowflake.client.category.TestTags; import net.snowflake.client.jdbc.BaseJDBCTest; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; @Tag(TestTags.CORE) public class JDK14LoggerConsoleHandlerOverrideLatestIT extends BaseJDBCTest { + private static final PrintStream standardOut = System.out; + private static final PrintStream standardErr = System.err; + private static final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + private static final ByteArrayOutputStream errStream = new ByteArrayOutputStream(); + private static final String errorMessage = "error message 1"; + private static final String warningMessage = "warning message 1"; + private static final String infoMessage = "info message 1"; + private static final String debugMessage = "debug message 1"; + + @BeforeAll + public static void replaceStreams() { + System.setOut(new PrintStream(outputStream)); + System.setErr(new PrintStream(errStream)); + } + + @AfterAll + public static void resetStreams() { + System.setOut(standardOut); + System.setErr(standardErr); + } + + /** Added in > 3.20.0 */ + @Test + public void shouldLogAllToStdErr() throws Exception { + Properties paramProperties = new Properties(); + + connectAndLog(paramProperties); + + Handler[] handlers = Logger.getLogger("").getHandlers(); + assertTrue(handlers.length > 0); + assertTrue(Arrays.stream(handlers).anyMatch(h -> h instanceof ConsoleHandler)); + + System.out.flush(); + System.err.flush(); + assertEquals("", outputStream.toString()); + // overriding stderr does not work correctly with maven + // String errString = errStream.toString(); + // assertTrue(errString.contains(errorMessage), () -> "STDERR: " + errString); + // assertTrue(errString.contains(warningMessage), () -> "STDERR: " + errString); + // assertTrue(errString.contains(infoMessage), () -> "STDERR: " + errString); + // assertFalse(errString.contains(debugMessage), () -> "STDERR: " + errString); + } + /** Added in > 3.20.0 */ @Test - public void shouldOverrideConsoleLogger() throws Exception { + public void shouldOverrideConsoleLoggerToStdOut() throws Exception { Properties paramProperties = new Properties(); paramProperties.put("JAVA_LOGGING_CONSOLE_STD_OUT", true); - try (Connection connection = getConnection(paramProperties); - Statement statement = connection.createStatement(); - ResultSet resultSet = statement.executeQuery("select 1")) { - assertTrue(resultSet.next()); - Handler[] handlers = Logger.getLogger("").getHandlers(); - assertTrue(handlers.length > 0); - assertFalse(Arrays.stream(handlers).anyMatch(h -> h instanceof ConsoleHandler)); - assertTrue(Arrays.stream(handlers).anyMatch(h -> h instanceof StdOutConsoleHandler)); + + connectAndLog(paramProperties); + + Handler[] handlers = Logger.getLogger("").getHandlers(); + assertTrue(handlers.length > 0); + assertFalse(Arrays.stream(handlers).anyMatch(h -> h instanceof ConsoleHandler)); + assertTrue(Arrays.stream(handlers).anyMatch(h -> h instanceof StdOutConsoleHandler)); + + System.out.flush(); + System.err.flush(); + String outString = outputStream.toString(); + assertTrue(outString.contains(errorMessage), () -> "STDOUT: " + outString); + assertTrue(outString.contains(warningMessage), () -> "STDOUT: " + outString); + assertTrue(outString.contains(infoMessage), () -> "STDOUT: " + outString); + assertFalse(outString.contains(debugMessage), () -> "STDOUT: " + outString); + // overriding stderr does not work correctly with maven + // assertEquals("", errStream.toString()); + } + + /** Added in > 3.20.0 */ + @Test + public void shouldOverrideConsoleLoggerWithSpecificThreshold() throws Exception { + Properties paramProperties = new Properties(); + paramProperties.put("JAVA_LOGGING_CONSOLE_STD_OUT", true); + paramProperties.put("JAVA_LOGGING_CONSOLE_STD_OUT_THRESHOLD", "WARNING"); + + connectAndLog(paramProperties); + + Handler[] handlers = Logger.getLogger("").getHandlers(); + assertTrue(handlers.length > 0); + assertFalse(Arrays.stream(handlers).anyMatch(h -> h instanceof ConsoleHandler)); + assertTrue( + Arrays.stream(handlers) + .anyMatch( + h -> + h instanceof StdErrOutThresholdAwareConsoleHandler + && ((StdErrOutThresholdAwareConsoleHandler) h) + .getThreshold() + .equals(Level.WARNING))); + + System.out.flush(); + System.err.flush(); + String outString = outputStream.toString(); + assertFalse(outString.contains(errorMessage), () -> "STDOUT: " + outString); + assertTrue(outString.contains(warningMessage), () -> "STDOUT: " + outString); + assertTrue(outString.contains(infoMessage), () -> "STDOUT: " + outString); + assertFalse(outString.contains(debugMessage), () -> "STDOUT: " + outString); + // overriding stderr does not work correctly with maven + // String errString = errStream.toString(); + // assertTrue(errString.contains(errorMessage), () -> "STDERR: " + errString); + // assertFalse(errString.contains(warningMessage), () -> "STDERR: " + errString); + // assertFalse(errString.contains(infoMessage), () -> "STDERR: " + errString); + // assertFalse(errString.contains(debugMessage), () -> "STDERR: " + errString); + } + + private static void connectAndLog(Properties paramProperties) throws SQLException { + try (Connection con = getConnection(paramProperties)) { + SFLogger logger = SFLoggerFactory.getLogger(JDK14LoggerConsoleHandlerOverrideLatestIT.class); + logger.error(errorMessage); + logger.warn(warningMessage); + logger.info(infoMessage); + logger.debug(debugMessage); } } + + /** Added in > 3.20.0 */ + @Test + public void shouldThrowExceptionOnUnknownLevel() throws Exception { + Properties paramProperties = new Properties(); + paramProperties.put("JAVA_LOGGING_CONSOLE_STD_OUT", true); + paramProperties.put("JAVA_LOGGING_CONSOLE_STD_OUT_THRESHOLD", "UNKNOWN"); + assertThrows(UnknownJavaUtilLoggingLevelException.class, () -> getConnection(paramProperties)); + } + + @BeforeEach + public void reset() { + JDK14Logger.resetToDefaultConsoleHandler(); + outputStream.reset(); + errStream.reset(); + } }