From 9e72683ba452d74df42d359c2a979442ffce3ea6 Mon Sep 17 00:00:00 2001 From: Luca Cavanna Date: Sun, 8 Sep 2013 10:13:26 +0200 Subject: [PATCH] Added @TestLogging annotation to set a specific level per test method It supports multiple logger:level comma separated key value pairs Use the _root keyword to set the root logger level e.g. @Logging("_root:DEBUG,org.elasticsearch.cluster.metadata:TRACE") or just @TestLogging("_root:DEBUG,cluster.metadata:TRACE") since we start the test with -Des.logger.prefix= --- .../common/logging/ESLogger.java | 14 ++++ .../common/logging/jdk/JdkESLogger.java | 12 ++- .../common/logging/log4j/Log4jESLogger.java | 12 ++- .../common/logging/slf4j/Slf4jESLogger.java | 6 ++ .../junit/annotations/TestLogging.java | 38 +++++++++ .../junit/listerners/LoggingListener.java | 80 +++++++++++++++++++ .../integration/ElasticsearchTestCase.java | 3 + 7 files changed, 163 insertions(+), 2 deletions(-) create mode 100644 src/test/java/org/elasticsearch/junit/annotations/TestLogging.java create mode 100644 src/test/java/org/elasticsearch/junit/listerners/LoggingListener.java diff --git a/src/main/java/org/elasticsearch/common/logging/ESLogger.java b/src/main/java/org/elasticsearch/common/logging/ESLogger.java index 76b309d431d..7cdbac882f5 100644 --- a/src/main/java/org/elasticsearch/common/logging/ESLogger.java +++ b/src/main/java/org/elasticsearch/common/logging/ESLogger.java @@ -28,8 +28,22 @@ public interface ESLogger { String getName(); + /** + * Allows to set the logger level + * If the new level is null, the logger will inherit its level + * from its nearest ancestor with a specific (non-null) level value. + * @param level the new level + */ void setLevel(String level); + /** + * Returns the current logger level + * If the level is null, it means that the logger inherits its level + * from its nearest ancestor with a specific (non-null) level value. + * @return the logger level + */ + String getLevel(); + /** * Returns {@code true} if a TRACE level message is logged. */ diff --git a/src/main/java/org/elasticsearch/common/logging/jdk/JdkESLogger.java b/src/main/java/org/elasticsearch/common/logging/jdk/JdkESLogger.java index 04b6c9c94bf..491edd6b74c 100644 --- a/src/main/java/org/elasticsearch/common/logging/jdk/JdkESLogger.java +++ b/src/main/java/org/elasticsearch/common/logging/jdk/JdkESLogger.java @@ -41,7 +41,9 @@ public class JdkESLogger extends AbstractESLogger { @Override public void setLevel(String level) { - if ("error".equalsIgnoreCase(level)) { + if (level == null) { + logger.setLevel(null); + } else if ("error".equalsIgnoreCase(level)) { logger.setLevel(Level.SEVERE); } else if ("warn".equalsIgnoreCase(level)) { logger.setLevel(Level.WARNING); @@ -54,6 +56,14 @@ public class JdkESLogger extends AbstractESLogger { } } + @Override + public String getLevel() { + if (logger.getLevel() == null) { + return null; + } + return logger.getLevel().toString(); + } + @Override public String getName() { return logger.getName(); diff --git a/src/main/java/org/elasticsearch/common/logging/log4j/Log4jESLogger.java b/src/main/java/org/elasticsearch/common/logging/log4j/Log4jESLogger.java index 349b897cf83..97efaf05219 100644 --- a/src/main/java/org/elasticsearch/common/logging/log4j/Log4jESLogger.java +++ b/src/main/java/org/elasticsearch/common/logging/log4j/Log4jESLogger.java @@ -36,7 +36,9 @@ public class Log4jESLogger extends AbstractESLogger { } public void setLevel(String level) { - if ("error".equalsIgnoreCase(level)) { + if (level == null) { + logger.setLevel(null); + } else if ("error".equalsIgnoreCase(level)) { logger.setLevel(Level.ERROR); } else if ("warn".equalsIgnoreCase(level)) { logger.setLevel(Level.WARN); @@ -49,6 +51,14 @@ public class Log4jESLogger extends AbstractESLogger { } } + @Override + public String getLevel() { + if (logger.getLevel() == null) { + return null; + } + return logger.getLevel().toString(); + } + @Override public String getName() { return logger.getName(); diff --git a/src/main/java/org/elasticsearch/common/logging/slf4j/Slf4jESLogger.java b/src/main/java/org/elasticsearch/common/logging/slf4j/Slf4jESLogger.java index 21c046d41f9..de4d49cab40 100644 --- a/src/main/java/org/elasticsearch/common/logging/slf4j/Slf4jESLogger.java +++ b/src/main/java/org/elasticsearch/common/logging/slf4j/Slf4jESLogger.java @@ -39,6 +39,12 @@ public class Slf4jESLogger extends AbstractESLogger { // can't set it in slf4j... } + @Override + public String getLevel() { + // can't get it in slf4j... + return null; + } + @Override public String getName() { return logger.getName(); diff --git a/src/test/java/org/elasticsearch/junit/annotations/TestLogging.java b/src/test/java/org/elasticsearch/junit/annotations/TestLogging.java new file mode 100644 index 00000000000..c60499ef3a2 --- /dev/null +++ b/src/test/java/org/elasticsearch/junit/annotations/TestLogging.java @@ -0,0 +1,38 @@ +/* + * Licensed to ElasticSearch and Shay Banon under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. ElasticSearch 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 org.elasticsearch.junit.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotation used to set a custom log level for a specific test method. + * + * It supports multiple logger:level comma separated key value pairs + * Use the _root keyword to set the root logger level + * e.g. @TestLogging("_root:DEBUG,org.elasticsearch.cluster.metadata:TRACE") + * or just @TestLogging("_root:DEBUG,cluster.metadata:TRACE") since we start the test with -Des.logger.prefix= + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface TestLogging { + String value(); +} diff --git a/src/test/java/org/elasticsearch/junit/listerners/LoggingListener.java b/src/test/java/org/elasticsearch/junit/listerners/LoggingListener.java new file mode 100644 index 00000000000..d3eabf7c762 --- /dev/null +++ b/src/test/java/org/elasticsearch/junit/listerners/LoggingListener.java @@ -0,0 +1,80 @@ +/* + * Licensed to ElasticSearch and Shay Banon under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. ElasticSearch 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 org.elasticsearch.junit.listerners; + +import org.elasticsearch.common.logging.ESLogger; +import org.elasticsearch.common.logging.ESLoggerFactory; +import org.elasticsearch.junit.annotations.TestLogging; +import org.junit.runner.Description; +import org.junit.runner.notification.RunListener; + +import java.util.HashMap; +import java.util.Map; + +/** + * A {@link RunListener} that allows to change the log level for a specific test method. + * When a test method is annotated with the {@link org.elasticsearch.junit.annotations.TestLogging} annotation, the level for the specified loggers + * will be internally saved before the test method execution and overridden with the specified ones. + * At the end of the test method execution the original loggers levels will be restored. + * + * Note: This class is not thread-safe. Given the static nature of the logging api, it assumes that tests + * are never run concurrently in the same jvm. For the very same reason no synchronization has been implemented + * regarding the save/restore process of the original loggers levels. + */ +public class LoggingListener extends RunListener { + + private Map previousLoggingMap; + + @Override + public void testStarted(Description description) throws Exception { + TestLogging testLogging = description.getAnnotation(TestLogging.class); + if (testLogging != null) { + this.previousLoggingMap = new HashMap(); + String[] loggersAndLevels = testLogging.value().split(","); + for (String loggerAndLevel : loggersAndLevels) { + String[] loggerAndLevelArray = loggerAndLevel.split(":"); + if (loggerAndLevelArray.length >=2) { + String loggerName = loggerAndLevelArray[0]; + String level = loggerAndLevelArray[1]; + ESLogger esLogger = resolveLogger(loggerName); + this.previousLoggingMap.put(loggerName, esLogger.getLevel()); + esLogger.setLevel(level); + } + } + } + } + + @Override + public void testFinished(Description description) throws Exception { + if (this.previousLoggingMap != null) { + for (Map.Entry previousLogger : previousLoggingMap.entrySet()) { + ESLogger esLogger = resolveLogger(previousLogger.getKey()); + esLogger.setLevel(previousLogger.getValue()); + } + this.previousLoggingMap = null; + } + } + + private static ESLogger resolveLogger(String loggerName) { + if (loggerName.equalsIgnoreCase("_root")) { + return ESLoggerFactory.getRootLogger(); + } + return ESLoggerFactory.getLogger(loggerName); + } +} diff --git a/src/test/java/org/elasticsearch/test/integration/ElasticsearchTestCase.java b/src/test/java/org/elasticsearch/test/integration/ElasticsearchTestCase.java index 103b50041d1..fb3e809d580 100644 --- a/src/test/java/org/elasticsearch/test/integration/ElasticsearchTestCase.java +++ b/src/test/java/org/elasticsearch/test/integration/ElasticsearchTestCase.java @@ -18,6 +18,7 @@ */ package org.elasticsearch.test.integration; +import com.carrotsearch.randomizedtesting.annotations.Listeners; import com.carrotsearch.randomizedtesting.annotations.ThreadLeakFilters; import com.carrotsearch.randomizedtesting.annotations.ThreadLeakScope; import com.carrotsearch.randomizedtesting.annotations.ThreadLeakScope.Scope; @@ -27,6 +28,7 @@ import org.apache.lucene.util.AbstractRandomizedTest; import org.apache.lucene.util.TimeUnits; import org.elasticsearch.common.logging.ESLogger; import org.elasticsearch.common.logging.Loggers; +import org.elasticsearch.junit.listerners.LoggingListener; import java.io.File; import java.net.URI; @@ -36,6 +38,7 @@ import java.util.concurrent.TimeUnit; @ThreadLeakFilters(defaultFilters = true, filters = {ElasticSearchThreadFilter.class}) @ThreadLeakScope(Scope.NONE) @TimeoutSuite(millis = TimeUnits.HOUR) // timeout the suite after 1h and fail the test. +@Listeners(LoggingListener.class) public abstract class ElasticsearchTestCase extends AbstractRandomizedTest { protected final ESLogger logger = Loggers.getLogger(getClass());