diff --git a/sdk/core/azure-core/src/main/java/com/azure/core/http/policy/HttpLoggingPolicy.java b/sdk/core/azure-core/src/main/java/com/azure/core/http/policy/HttpLoggingPolicy.java index 50f6a5f196ad..9e0b153593ba 100644 --- a/sdk/core/azure-core/src/main/java/com/azure/core/http/policy/HttpLoggingPolicy.java +++ b/sdk/core/azure-core/src/main/java/com/azure/core/http/policy/HttpLoggingPolicy.java @@ -10,11 +10,11 @@ import com.azure.core.http.HttpPipelineNextPolicy; import com.azure.core.http.HttpRequest; import com.azure.core.http.HttpResponse; -import com.azure.core.implementation.LogLevel; import com.azure.core.implementation.LoggingUtil; import com.azure.core.util.CoreUtils; import com.azure.core.util.UrlBuilder; import com.azure.core.util.logging.ClientLogger; +import com.azure.core.util.logging.LogLevel; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; import reactor.core.publisher.Mono; @@ -101,7 +101,8 @@ public Mono process(HttpPipelineCallContext context, HttpPipelineN * @return A Mono which will emit the string to log. */ private Mono logRequest(final ClientLogger logger, final HttpRequest request) { - int numericLogLevel = LoggingUtil.getEnvironmentLoggingLevel().toNumeric(); + int numericLogLevel = LoggingUtil.getEnvironmentLoggingLevel().getLogLevel(); + if (shouldLoggingBeSkipped(numericLogLevel)) { return Mono.empty(); } @@ -181,7 +182,7 @@ private Mono logRequest(final ClientLogger logger, final HttpRequest reque * @return A Mono containing the HTTP response. */ private Mono logResponse(final ClientLogger logger, final HttpResponse response, long startNs) { - int numericLogLevel = LoggingUtil.getEnvironmentLoggingLevel().toNumeric(); + int numericLogLevel = LoggingUtil.getEnvironmentLoggingLevel().getLogLevel(); if (shouldLoggingBeSkipped(numericLogLevel)) { return Mono.just(response); } @@ -260,7 +261,7 @@ private Mono logAndReturn(ClientLogger logger, StringBuilder logMessageBu * @return A flag indicating if logging should be skipped. */ private boolean shouldLoggingBeSkipped(int environmentLogLevel) { - return environmentLogLevel > LogLevel.INFORMATIONAL.toNumeric(); + return environmentLogLevel > LogLevel.INFORMATIONAL.getLogLevel(); } /* @@ -318,7 +319,7 @@ private String getAllowedQueryString(String queryString) { */ private void addHeadersToLogMessage(HttpHeaders headers, StringBuilder sb, int logLevel) { // Either headers shouldn't be logged or the logging level isn't set to VERBOSE, don't add headers. - if (!httpLogDetailLevel.shouldLogHeaders() || logLevel > LogLevel.VERBOSE.toNumeric()) { + if (!httpLogDetailLevel.shouldLogHeaders() || logLevel > LogLevel.VERBOSE.getLogLevel()) { return; } diff --git a/sdk/core/azure-core/src/main/java/com/azure/core/implementation/LogLevel.java b/sdk/core/azure-core/src/main/java/com/azure/core/implementation/LogLevel.java deleted file mode 100644 index 56c83b5c5909..000000000000 --- a/sdk/core/azure-core/src/main/java/com/azure/core/implementation/LogLevel.java +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.core.implementation; - -/** - * Enum which represent logging levels used in Azure SDKs. - */ -public enum LogLevel { - /** - * Indicates that log level is at verbose level. - */ - VERBOSE(1), - - /** - * Indicates that log level is at information level. - */ - INFORMATIONAL(2), - - /** - * Indicates that log level is at warning level. - */ - WARNING(3), - - /** - * Indicates that log level is at error level. - */ - ERROR(4), - - /** - * Indicates that logging is disabled. - */ - DISABLED(5); - - private final int numericValue; - - LogLevel(int numericValue) { - this.numericValue = numericValue; - } - - /** - * Converts the log level into a numeric representation used for comparisons. - * - * @return The numeric representation of the log level. - */ - public int toNumeric() { - return numericValue; - } -} diff --git a/sdk/core/azure-core/src/main/java/com/azure/core/implementation/LoggingUtil.java b/sdk/core/azure-core/src/main/java/com/azure/core/implementation/LoggingUtil.java index bfff5d531638..3f7eb257a99e 100644 --- a/sdk/core/azure-core/src/main/java/com/azure/core/implementation/LoggingUtil.java +++ b/sdk/core/azure-core/src/main/java/com/azure/core/implementation/LoggingUtil.java @@ -4,33 +4,24 @@ package com.azure.core.implementation; import com.azure.core.util.Configuration; -import com.azure.core.util.CoreUtils; - -import java.util.Arrays; -import java.util.Map; -import java.util.stream.Collectors; +import com.azure.core.util.logging.LogLevel; /** * This class contains utility methods useful for logging. */ public final class LoggingUtil { - private static final Map LOG_LEVEL_MAPPER = Arrays.stream(LogLevel.values()) - .collect(Collectors.toMap(LogLevel::toNumeric, logLevel -> logLevel)); - /** * Retrieve the environment logging level which is used to determine if and what we are allowed to log. * *

The value returned from this method should be used throughout a single logging event as it may change during * the logging operation, this will help prevent difficult to debug timing issues.

* - * @return Environment logging level if set, otherwise {@link LogLevel#DISABLED}. + * @return Environment logging level if set, otherwise {@link LogLevel#NOT_SET}. */ public static LogLevel getEnvironmentLoggingLevel() { String environmentLogLevel = Configuration.getGlobalConfiguration().get(Configuration.PROPERTY_AZURE_LOG_LEVEL); - return CoreUtils.isNullOrEmpty(environmentLogLevel) - ? LogLevel.DISABLED - : LOG_LEVEL_MAPPER.getOrDefault(Integer.parseInt(environmentLogLevel), LogLevel.DISABLED); + return LogLevel.fromString(environmentLogLevel); } // Private constructor diff --git a/sdk/core/azure-core/src/main/java/com/azure/core/util/logging/ClientLogger.java b/sdk/core/azure-core/src/main/java/com/azure/core/util/logging/ClientLogger.java index 27868d089e66..0938669d75a2 100644 --- a/sdk/core/azure-core/src/main/java/com/azure/core/util/logging/ClientLogger.java +++ b/sdk/core/azure-core/src/main/java/com/azure/core/util/logging/ClientLogger.java @@ -3,7 +3,6 @@ package com.azure.core.util.logging; -import com.azure.core.implementation.LogLevel; import com.azure.core.util.Configuration; import com.azure.core.util.CoreUtils; import org.slf4j.Logger; @@ -173,6 +172,16 @@ private RuntimeException logException(RuntimeException runtimeException, LogLeve return runtimeException; } + /** + * Determines if the environment and logger support logging at the given log level. + * + * @param logLevel The {@link LogLevel} being validated as supported. + * @return Flag indicating if the environment and logger support logging at the given log level. + */ + public boolean canLogAtLevel(LogLevel logLevel) { + return canLogAtLevel(logLevel, getEnvironmentLoggingLevel()); + } + /* * Performs the logging. * @@ -198,7 +207,7 @@ private void performLogging(LogLevel logLevel, LogLevel environmentLogLevel, boo * Environment is logging at a level higher than verbose, strip out the throwable as it would log its * stack trace which is only expected when logging at a verbose level. */ - if (environmentLogLevel.toNumeric() > LogLevel.VERBOSE.toNumeric()) { + if (environmentLogLevel.getLogLevel() > LogLevel.VERBOSE.getLogLevel()) { args = removeThrowable(args); } } @@ -236,8 +245,13 @@ private void performLogging(LogLevel logLevel, LogLevel environmentLogLevel, boo * @return Flag indicating if the environment and logger are configured to support logging at the given log level. */ private boolean canLogAtLevel(LogLevel logLevel, LogLevel environmentLoggingLevel) { + // Do not log if logLevel is null is not set. + if (logLevel == null) { + return false; + } + // Attempting to log at a level not supported by the environment. - if (logLevel.toNumeric() < environmentLoggingLevel.toNumeric()) { + if (logLevel.getLogLevel() < environmentLoggingLevel.getLogLevel()) { return false; } diff --git a/sdk/core/azure-core/src/main/java/com/azure/core/util/logging/LogLevel.java b/sdk/core/azure-core/src/main/java/com/azure/core/util/logging/LogLevel.java new file mode 100644 index 000000000000..83b603d17b1e --- /dev/null +++ b/sdk/core/azure-core/src/main/java/com/azure/core/util/logging/LogLevel.java @@ -0,0 +1,90 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.core.util.logging; + +import java.util.HashMap; +import java.util.Locale; + +/** + * Enum which represent logging levels used in Azure SDKs. + */ +public enum LogLevel { + /** + * Indicates that log level is at verbose level. + */ + VERBOSE(1, "1", "verbose", "debug"), + + /** + * Indicates that log level is at information level. + */ + INFORMATIONAL(2, "2", "info", "information", "informational"), + + /** + * Indicates that log level is at warning level. + */ + WARNING(3, "3", "warn", "warning"), + + /** + * Indicates that log level is at error level. + */ + ERROR(4, "4", "err", "error"), + + /** + * Indicates that no log level is set. + */ + NOT_SET(5); + + private final int numericValue; + private final String[] allowedLogLevelVariables; + private static final HashMap LOG_LEVEL_STRING_MAPPER = new HashMap<>(); + + static { + for (LogLevel logLevel: LogLevel.values()) { + for (String val: logLevel.allowedLogLevelVariables) { + LOG_LEVEL_STRING_MAPPER.put(val, logLevel); + } + } + } + + LogLevel(int numericValue, String... allowedLogLevelVariables) { + this.numericValue = numericValue; + this.allowedLogLevelVariables = allowedLogLevelVariables; + } + + /** + * Converts the log level into a numeric representation used for comparisons. + * + * @return The numeric representation of the log level. + */ + public int getLogLevel() { + return numericValue; + } + + /** + * Converts the passed log level string to the corresponding {@link LogLevel}. + * + * @param logLevelVal The log level value which needs to convert + * @return The LogLevel Enum if pass in the valid string. + * The valid strings for {@link LogLevel} are: + *
    + *
  • VERBOSE: "verbose", "debug"
  • + *
  • INFO: "info", "information", "informational"
  • + *
  • WARNING: "warn", "warning"
  • + *
  • ERROR: "err", "error"
  • + *
+ * Returns NOT_SET if null is passed in. + * @throws IllegalArgumentException if the log level value is invalid. + */ + public static LogLevel fromString(String logLevelVal) { + if (logLevelVal == null) { + return LogLevel.NOT_SET; + } + String caseInsensitiveLogLevel = logLevelVal.toLowerCase(Locale.ROOT); + if (!LOG_LEVEL_STRING_MAPPER.containsKey(caseInsensitiveLogLevel)) { + throw new IllegalArgumentException("We currently do not support the log level you set. LogLevel: " + + logLevelVal); + } + return LOG_LEVEL_STRING_MAPPER.get(caseInsensitiveLogLevel); + } +} diff --git a/sdk/core/azure-core/src/test/java/com/azure/core/util/logging/ClientLoggerTests.java b/sdk/core/azure-core/src/test/java/com/azure/core/util/logging/ClientLoggerTests.java index be652413517d..f7794a541168 100644 --- a/sdk/core/azure-core/src/test/java/com/azure/core/util/logging/ClientLoggerTests.java +++ b/sdk/core/azure-core/src/test/java/com/azure/core/util/logging/ClientLoggerTests.java @@ -9,13 +9,16 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; import org.junit.jupiter.params.provider.ValueSource; import java.io.ByteArrayOutputStream; import java.io.PrintStream; import java.nio.charset.StandardCharsets; +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; /** @@ -103,15 +106,19 @@ public void logAtUnsupportedLevel(int logLevel) { */ @ParameterizedTest(name = PARAMETERIZED_TEST_NAME_TEMPLATE) @ValueSource(ints = { 1, 2, 3, 4 }) - public void logWhenLoggingDisabled(int logLevel) { + public void logWhenLoggingInvalidNumeric(int logLevel) { String logMessage = "This is a test"; + setupLogLevel(5); + assertThrows(IllegalArgumentException.class, () -> + logMessage(new ClientLogger(ClientLoggerTests.class), logLevel, logMessage)); + } - String originalLogLevel = setupLogLevel(5); - logMessage(new ClientLogger(ClientLoggerTests.class), logLevel, logMessage); - setPropertyToOriginalOrClear(Configuration.PROPERTY_AZURE_LOG_LEVEL, originalLogLevel); - - String logValues = new String(logCaptureStream.toByteArray(), StandardCharsets.UTF_8); - assertFalse(logValues.contains(logMessage)); + /** + * Tests that logging when the environment log level is disabled nothing is logged. + */ + @Test + public void logWhenLoggingNotSet() { + assertEquals(LogLevel.NOT_SET, LogLevel.fromString(null)); } /** @@ -276,10 +283,24 @@ public void logExceptionAsErrorStackTrace() { assertTrue(logValues.contains(runtimeException.getStackTrace()[0].toString())); } + @ParameterizedTest(name = "{index} from logLevelToConfigure = {0}, logLevelToValidate = {1}, expected = {2}") + @CsvSource({"1, 1, true", "1, 2, true", "1, 3, true", "1, 4, true", "2, 1, false", "1, VERBOSE, true", "1, info, true", "1, warning, true", "1, error, true", "2, verbose, false"}) + public void canLogAtLevel(int logLevelToConfigure, String logLevelToValidate, boolean expected) { + setupLogLevel(logLevelToConfigure); + LogLevel logLevel = LogLevel.fromString(logLevelToValidate); + assertEquals(new ClientLogger(ClientLoggerTests.class).canLogAtLevel(logLevel), expected); + } + + @ParameterizedTest(name = PARAMETERIZED_TEST_NAME_TEMPLATE) + @ValueSource(strings = {"5", "invalid"}) + public void canLogAtLevelInvalid(String logLevelToValidate) { + setupLogLevel(2); + assertThrows(IllegalArgumentException.class, () -> LogLevel.fromString(logLevelToValidate)); + } + private String setupLogLevel(int logLevelToSet) { String originalLogLevel = Configuration.getGlobalConfiguration().get(Configuration.PROPERTY_AZURE_CLOUD); System.setProperty(Configuration.PROPERTY_AZURE_LOG_LEVEL, Integer.toString(logLevelToSet)); - return originalLogLevel; }