Skip to content

Commit

Permalink
SNOW-1652680: Add threshold for j.u.l. stderr stdout logging (#1988)
Browse files Browse the repository at this point in the history
  • Loading branch information
sfc-gh-dprzybysz authored Dec 9, 2024
1 parent 6da3ca3 commit c9bf0e7
Show file tree
Hide file tree
Showing 7 changed files with 253 additions and 17 deletions.
19 changes: 17 additions & 2 deletions src/main/java/net/snowflake/client/core/SFSession.java
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
35 changes: 32 additions & 3 deletions src/main/java/net/snowflake/client/log/JDK14Logger.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
}

0 comments on commit c9bf0e7

Please sign in to comment.