aboutsummaryrefslogtreecommitdiffstats
path: root/server/sonar-process
diff options
context:
space:
mode:
authorSébastien Lesaint <sebastien.lesaint@sonarsource.com>2016-11-29 13:48:00 +0100
committerSébastien Lesaint <sebastien.lesaint@sonarsource.com>2016-12-01 12:29:40 +0100
commit1f9bb0b02f646739bfbdfe9dc9e8ed15c86e4e52 (patch)
tree2338a6b5d034cc174fd18cf36c72970d2004d337 /server/sonar-process
parent93ea99696bfed149fa1f46888e864b3bde4b4328 (diff)
downloadsonarqube-1f9bb0b02f646739bfbdfe9dc9e8ed15c86e4e52.tar.gz
sonarqube-1f9bb0b02f646739bfbdfe9dc9e8ed15c86e4e52.zip
SONAR-8335 make changing root level change level of all loggers
Diffstat (limited to 'server/sonar-process')
-rw-r--r--server/sonar-process/src/main/java/org/sonar/process/logging/LogDomain.java36
-rw-r--r--server/sonar-process/src/main/java/org/sonar/process/logging/LogLevelConfig.java133
-rw-r--r--server/sonar-process/src/main/java/org/sonar/process/logging/LogbackHelper.java147
-rw-r--r--server/sonar-process/src/test/java/org/sonar/process/logging/LogLevelConfigTest.java191
-rw-r--r--server/sonar-process/src/test/java/org/sonar/process/logging/LogbackHelperTest.java179
5 files changed, 585 insertions, 101 deletions
diff --git a/server/sonar-process/src/main/java/org/sonar/process/logging/LogDomain.java b/server/sonar-process/src/main/java/org/sonar/process/logging/LogDomain.java
new file mode 100644
index 00000000000..a4a19a64ca7
--- /dev/null
+++ b/server/sonar-process/src/main/java/org/sonar/process/logging/LogDomain.java
@@ -0,0 +1,36 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.process.logging;
+
+public enum LogDomain {
+ SQL("sql"),
+ ES_CLIENT("es"),
+ JMX("jmx");
+
+ private final String key;
+
+ LogDomain(String key) {
+ this.key = key;
+ }
+
+ public String getKey() {
+ return key;
+ }
+}
diff --git a/server/sonar-process/src/main/java/org/sonar/process/logging/LogLevelConfig.java b/server/sonar-process/src/main/java/org/sonar/process/logging/LogLevelConfig.java
new file mode 100644
index 00000000000..cbef4a4449b
--- /dev/null
+++ b/server/sonar-process/src/main/java/org/sonar/process/logging/LogLevelConfig.java
@@ -0,0 +1,133 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.process.logging;
+
+import ch.qos.logback.classic.Level;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import org.sonar.process.ProcessId;
+
+import static java.util.Objects.requireNonNull;
+import static org.slf4j.Logger.ROOT_LOGGER_NAME;
+
+public final class LogLevelConfig {
+ private static final String SONAR_LOG_LEVEL_PROPERTY = "sonar.log.level";
+ private static final String PROCESS_NAME_PLACEHOLDER = "XXXX";
+ private static final String SONAR_PROCESS_LOG_LEVEL_PROPERTY = "sonar.log.level." + PROCESS_NAME_PLACEHOLDER;
+
+ private final Map<String, List<String>> configuredByProperties;
+ private final Map<String, Level> configuredByHardcodedLevel;
+
+ private LogLevelConfig(Builder builder) {
+ this.configuredByProperties = Collections.unmodifiableMap(builder.configuredByProperties);
+ this.configuredByHardcodedLevel = Collections.unmodifiableMap(builder.configuredByHardcodedLevel);
+ }
+
+ Map<String, List<String>> getConfiguredByProperties() {
+ return configuredByProperties;
+ }
+
+ Map<String, Level> getConfiguredByHardcodedLevel() {
+ return configuredByHardcodedLevel;
+ }
+
+ public static Builder newBuilder() {
+ return new Builder();
+ }
+
+ public static final class Builder {
+ private final Map<String, List<String>> configuredByProperties = new HashMap<>();
+ private final Map<String, Level> configuredByHardcodedLevel = new HashMap<>();
+
+ private Builder() {
+ // use static factory method
+ }
+
+ /**
+ * Configure the log level of the root logger to be read from the value of properties {@link #SONAR_LOG_LEVEL_PROPERTY} and
+ * {@link #SONAR_PROCESS_LOG_LEVEL_PROPERTY}.
+ */
+ public Builder rootLevelFor(ProcessId processId) {
+ checkProcessId(processId);
+
+ levelByProperty(ROOT_LOGGER_NAME, SONAR_LOG_LEVEL_PROPERTY, SONAR_PROCESS_LOG_LEVEL_PROPERTY.replace(PROCESS_NAME_PLACEHOLDER, processId.getKey()));
+ return this;
+ }
+
+ /**
+ * Configure the log level of the logger with the specified name to be read from the value of properties
+ * {@code sonar.log.level}, {@code sonar.log.level.[process_name]} and {@code sonar.log.level.[process_name].[LogDomain#getKey()]}.
+ */
+ public Builder levelByDomain(String loggerName, ProcessId processId, LogDomain domain) {
+ checkLoggerName(loggerName);
+ checkProcessId(processId);
+ requireNonNull(domain, "LogDomain can't be null");
+ String processProperty = SONAR_PROCESS_LOG_LEVEL_PROPERTY.replace(PROCESS_NAME_PLACEHOLDER, processId.getKey());
+ levelByProperty(loggerName, SONAR_LOG_LEVEL_PROPERTY, processProperty, processProperty + "." + domain.getKey());
+ return this;
+ }
+
+ private void levelByProperty(String loggerName, String property, String... otherProperties) {
+ ensureUniqueConfiguration(loggerName);
+ configuredByProperties.put(loggerName, Stream.concat(Stream.of(property), Arrays.stream(otherProperties)).collect(Collectors.toList()));
+ }
+
+ /**
+ * Configure the log level of the logger with the specified name to be the specified one and it should never be
+ * changed.
+ */
+ public Builder immutableLevel(String loggerName, Level level) {
+ checkLoggerName(loggerName);
+ requireNonNull(level, "level can't be null");
+ ensureUniqueConfiguration(loggerName);
+ configuredByHardcodedLevel.put(loggerName, level);
+ return this;
+ }
+
+ private void ensureUniqueConfiguration(String loggerName) {
+ if (configuredByProperties.containsKey(loggerName)) {
+ throw new IllegalStateException("Configuration by property already registered for " + loggerName);
+ }
+ if (configuredByHardcodedLevel.containsKey(loggerName)) {
+ throw new IllegalStateException("Configuration hardcoded level already registered for " + loggerName);
+ }
+ }
+
+ private static void checkProcessId(ProcessId processId) {
+ requireNonNull(processId, "ProcessId can't be null");
+ }
+
+ private static void checkLoggerName(String loggerName) {
+ requireNonNull(loggerName, "loggerName can't be null");
+ if (loggerName.isEmpty()) {
+ throw new IllegalArgumentException("loggerName can't be empty");
+ }
+ }
+
+ public LogLevelConfig build() {
+ return new LogLevelConfig(this);
+ }
+ }
+}
diff --git a/server/sonar-process/src/main/java/org/sonar/process/logging/LogbackHelper.java b/server/sonar-process/src/main/java/org/sonar/process/logging/LogbackHelper.java
index 6d7589a618a..5a55bade6d3 100644
--- a/server/sonar-process/src/main/java/org/sonar/process/logging/LogbackHelper.java
+++ b/server/sonar-process/src/main/java/org/sonar/process/logging/LogbackHelper.java
@@ -38,13 +38,11 @@ import ch.qos.logback.core.rolling.TimeBasedRollingPolicy;
import java.io.File;
import java.util.Arrays;
import java.util.Collection;
-import java.util.Collections;
-import java.util.Set;
+import java.util.List;
import javax.annotation.CheckForNull;
import org.apache.commons.lang.StringUtils;
import org.slf4j.LoggerFactory;
import org.sonar.process.MessageException;
-import org.sonar.process.ProcessId;
import org.sonar.process.ProcessProperties;
import org.sonar.process.Props;
@@ -59,8 +57,6 @@ public class LogbackHelper {
private static final String ALL_LOGS_TO_CONSOLE_PROPERTY = "sonar.log.console";
private static final String PROCESS_NAME_PLACEHOLDER = "XXXX";
private static final String THREAD_ID_PLACEHOLDER = "ZZZZ";
- private static final String SONAR_LOG_LEVEL_PROPERTY = "sonar.log.level";
- private static final String SONAR_PROCESS_LOG_LEVEL_PROPERTY = "sonar.log.level." + PROCESS_NAME_PLACEHOLDER;
private static final String ROLLING_POLICY_PROPERTY = "sonar.log.rollingPolicy";
private static final String MAX_FILES_PROPERTY = "sonar.log.maxFiles";
private static final String LOG_FORMAT = "%d{yyyy.MM.dd HH:mm:ss} %-5level " + PROCESS_NAME_PLACEHOLDER + "[" + THREAD_ID_PLACEHOLDER + "][%logger{20}] %msg%n";
@@ -96,6 +92,60 @@ public class LogbackHelper {
return propagator;
}
+ /**
+ * Applies the specified {@link LogLevelConfig} reading the specified {@link Props}.
+ *
+ * @throws IllegalArgumentException if the any level specified in a property is not one of {@link #ALLOWED_ROOT_LOG_LEVELS}
+ */
+ public LoggerContext apply(LogLevelConfig logLevelConfig, Props props) {
+ LoggerContext rootContext = getRootContext();
+ logLevelConfig.getConfiguredByProperties().entrySet().forEach(entry -> applyLevelByProperty(props, rootContext.getLogger(entry.getKey()), entry.getValue()));
+ logLevelConfig.getConfiguredByHardcodedLevel().entrySet().forEach(entry -> applyHardcodedLevel(rootContext, entry.getKey(), entry.getValue()));
+ return rootContext;
+ }
+
+ private void applyLevelByProperty(Props props, Logger logger, List<String> properties) {
+ logger.setLevel(resolveLevel(props, properties.stream().toArray(String[]::new)));
+ }
+
+ /**
+ * Resolve a log level reading the value of specified properties.
+ * <p>
+ * To compute the applied log level the following rules will be followed:
+ * <ul>the last property with a defined and valid value in the order of the {@code propertyKeys} argument will be applied</ul>
+ * <ul>if there is none, {@link Level#INFO INFO} will be returned</ul>
+ * </p>
+ *
+ * @throws IllegalArgumentException if the value of the specified property is not one of {@link #ALLOWED_ROOT_LOG_LEVELS}
+ */
+ private static Level resolveLevel(Props props, String... propertyKeys) {
+ Level newLevel = Level.INFO;
+ for (String propertyKey : propertyKeys) {
+ Level level = getPropertyValueAsLevel(props, propertyKey);
+ if (level != null) {
+ newLevel = level;
+ }
+ }
+ return newLevel;
+ }
+
+ private static void applyHardcodedLevel(LoggerContext rootContext, String loggerName, Level newLevel) {
+ rootContext.getLogger(loggerName).setLevel(newLevel);
+ }
+
+ public void changeRoot(LogLevelConfig logLevelConfig, Level newLevel) {
+ ensureSupportedLevel(newLevel);
+ LoggerContext rootContext = getRootContext();
+ rootContext.getLogger(ROOT_LOGGER_NAME).setLevel(newLevel);
+ logLevelConfig.getConfiguredByProperties().entrySet().forEach(entry -> rootContext.getLogger(entry.getKey()).setLevel(newLevel));
+ }
+
+ private static void ensureSupportedLevel(Level newLevel) {
+ if (!isAllowed(newLevel)) {
+ throw new IllegalArgumentException(format("%s log level is not supported (allowed levels are %s)", newLevel, Arrays.toString(ALLOWED_ROOT_LOG_LEVELS)));
+ }
+ }
+
public String buildLogPattern(RootLoggerConfig config) {
return LOG_FORMAT
.replace(PROCESS_NAME_PLACEHOLDER, config.getProcessId().getKey())
@@ -176,38 +226,6 @@ public class LogbackHelper {
return props.valueAsBoolean(ALL_LOGS_TO_CONSOLE_PROPERTY, false);
}
- /**
- * Configure the log level of the root logger reading the value of property {@link #SONAR_LOG_LEVEL_PROPERTY}.
- *
- * @throws IllegalArgumentException if the value of {@link #SONAR_LOG_LEVEL_PROPERTY} is not one of {@link #ALLOWED_ROOT_LOG_LEVELS}
- */
- public Level configureRootLogLevel(Props props, ProcessId processId) {
- Level newLevel = resolveLevel(props, SONAR_LOG_LEVEL_PROPERTY, SONAR_PROCESS_LOG_LEVEL_PROPERTY.replace(PROCESS_NAME_PLACEHOLDER, processId.getKey()));
- getRootContext().getLogger(ROOT_LOGGER_NAME).setLevel(newLevel);
- return newLevel;
- }
-
- /**
- * Resolve a log level reading the value of specified properties.
- * <p>
- * To compute the applied log level the following rules will be followed:
- * <ul>the last property with a defined and valid value in the order of the {@code propertyKeys} argument will be applied</ul>
- * <ul>if there is none, {@link Level#INFO INFO} will be returned</ul>
- * </p>
- *
- * @throws IllegalArgumentException if the value of the specified property is not one of {@link #ALLOWED_ROOT_LOG_LEVELS}
- */
- private static Level resolveLevel(Props props, String... propertyKeys) {
- Level newLevel = Level.INFO;
- for (String propertyKey : propertyKeys) {
- Level level = getPropertyValueAsLevel(props, propertyKey);
- if (level != null) {
- newLevel = level;
- }
- }
- return newLevel;
- }
-
@CheckForNull
private static Level getPropertyValueAsLevel(Props props, String propertyKey) {
String value = props.value(propertyKey);
@@ -232,50 +250,6 @@ public class LogbackHelper {
return false;
}
- /**
- * Configure the log level of the root logger to the specified level.
- *
- * @throws IllegalArgumentException if the specified level is not one of {@link #ALLOWED_ROOT_LOG_LEVELS}
- */
- public Level configureRootLogLevel(Level newLevel) {
- Logger rootLogger = getRootContext().getLogger(ROOT_LOGGER_NAME);
- ensureSupportedLevel(newLevel);
- rootLogger.setLevel(newLevel);
- return newLevel;
- }
-
- private static void ensureSupportedLevel(Level newLevel) {
- if (!isAllowed(newLevel)) {
- throw new IllegalArgumentException(format("%s log level is not supported (allowed levels are %s)", newLevel, Arrays.toString(ALLOWED_ROOT_LOG_LEVELS)));
- }
- }
-
- public Level configureLoggerLogLevelFromDomain(String loggerName, Props props, ProcessId processId, LogDomain domain) {
- return configureLoggersLogLevelFromDomain(Collections.singleton(loggerName), props, processId, domain);
- }
-
- public Level configureLoggersLogLevelFromDomain(Set<String> loggerNames, Props props, ProcessId processId, LogDomain domain) {
- String processProperty = SONAR_PROCESS_LOG_LEVEL_PROPERTY.replace(PROCESS_NAME_PLACEHOLDER, processId.getKey());
- Level newLevel = resolveLevel(props, SONAR_LOG_LEVEL_PROPERTY, processProperty, processProperty + "." + domain.key);
- loggerNames.forEach(loggerName -> {
- Logger logger = getRootContext().getLogger(loggerName);
- logger.setLevel(newLevel);
- });
- return newLevel;
- }
-
- /**
- * Configure the log level of the specified logger to specified level.
- * <p>
- * Any level is allowed.
- * </p>
- */
- public Logger configureLogger(String loggerName, Level level) {
- Logger logger = getRootContext().getLogger(loggerName);
- logger.setLevel(level);
- return logger;
- }
-
public Level getLoggerLevel(String loggerName) {
return getRootContext().getLogger(loggerName).getLevel();
}
@@ -414,15 +388,4 @@ public class LogbackHelper {
}
}
- public enum LogDomain {
- SQL("sql"),
- ES_CLIENT("es"),
- JMX("jmx");
-
- private final String key;
-
- LogDomain(String key) {
- this.key = key;
- }
- }
}
diff --git a/server/sonar-process/src/test/java/org/sonar/process/logging/LogLevelConfigTest.java b/server/sonar-process/src/test/java/org/sonar/process/logging/LogLevelConfigTest.java
new file mode 100644
index 00000000000..88025203b88
--- /dev/null
+++ b/server/sonar-process/src/test/java/org/sonar/process/logging/LogLevelConfigTest.java
@@ -0,0 +1,191 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.process.logging;
+
+import ch.qos.logback.classic.Level;
+import java.util.Collections;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.sonar.process.ProcessId;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Fail.fail;
+import static org.slf4j.Logger.ROOT_LOGGER_NAME;
+import static org.sonar.process.logging.LogLevelConfig.newBuilder;
+
+public class LogLevelConfigTest {
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
+
+ private LogLevelConfig.Builder underTest = newBuilder();
+
+ @Test
+ public void build_can_create_empty_config_and_returned_maps_are_unmodifiable() {
+ LogLevelConfig underTest = newBuilder().build();
+
+ expectUnsupportedOperationException(() -> underTest.getConfiguredByProperties().put("1", Collections.emptyList()));
+ expectUnsupportedOperationException(() -> underTest.getConfiguredByHardcodedLevel().put("1", Level.ERROR));
+ }
+
+ @Test
+ public void builder_rootLevelFor_add_global_and_process_property_in_order_for_root_logger() {
+ LogLevelConfig underTest = newBuilder().rootLevelFor(ProcessId.ELASTICSEARCH).build();
+
+ assertThat(underTest.getConfiguredByProperties()).hasSize(1);
+ assertThat(underTest.getConfiguredByProperties().get(ROOT_LOGGER_NAME))
+ .containsExactly("sonar.log.level", "sonar.log.level.es");
+ assertThat(underTest.getConfiguredByHardcodedLevel()).hasSize(0);
+ }
+
+ @Test
+ public void builder_rootLevelFor_fails_with_ProcessId_if_loggerName_is_null() {
+ expectedException.expect(NullPointerException.class);
+ expectedException.expectMessage("ProcessId can't be null");
+
+ underTest.rootLevelFor(null);
+ }
+
+ @Test
+ public void builder_rootLevelFor_fails_with_ISE_if_called_twice() {
+ underTest.rootLevelFor(ProcessId.ELASTICSEARCH);
+
+ expectedException.expect(IllegalStateException.class);
+ expectedException.expectMessage("Configuration by property already registered for " + ROOT_LOGGER_NAME);
+
+ underTest.rootLevelFor(ProcessId.WEB_SERVER);
+ }
+
+ @Test
+ public void builder_levelByDomain_fails_with_NPE_if_loggerName_is_null() {
+ expectedException.expect(NullPointerException.class);
+ expectedException.expectMessage("loggerName can't be null");
+
+ underTest.levelByDomain(null, ProcessId.WEB_SERVER, LogDomain.JMX);
+ }
+
+ @Test
+ public void builder_levelByDomain_fails_with_IAE_if_loggerName_is_empty() {
+ expectedException.expect(IllegalArgumentException.class);
+ expectedException.expectMessage("loggerName can't be empty");
+
+ underTest.levelByDomain("", ProcessId.WEB_SERVER, LogDomain.JMX);
+ }
+
+ @Test
+ public void builder_levelByDomain_fails_with_NPE_if_ProcessId_is_null() {
+ expectedException.expect(NullPointerException.class);
+ expectedException.expectMessage("ProcessId can't be null");
+
+ underTest.levelByDomain("bar", null, LogDomain.JMX);
+ }
+
+ @Test
+ public void builder_levelByDomain_fails_with_NPE_if_LogDomain_is_null() {
+ expectedException.expect(NullPointerException.class);
+ expectedException.expectMessage("LogDomain can't be null");
+
+ underTest.levelByDomain("bar", ProcessId.WEB_SERVER, null);
+ }
+
+ @Test
+ public void builder_levelByDomain_adds_global_process_and_domain_properties_in_order_for_specified_logger() {
+ LogLevelConfig underTest = newBuilder()
+ .levelByDomain("foo", ProcessId.WEB_SERVER, LogDomain.SQL)
+ .build();
+
+ assertThat(underTest.getConfiguredByProperties()).hasSize(1);
+ assertThat(underTest.getConfiguredByProperties().get("foo"))
+ .containsExactly("sonar.log.level", "sonar.log.level.web", "sonar.log.level.web.sql");
+ assertThat(underTest.getConfiguredByHardcodedLevel()).hasSize(0);
+ }
+
+ @Test
+ public void builder_levelByDomain_fails_with_ISE_if_loggerName_has_immutableLevel() {
+ underTest.immutableLevel("bar", Level.INFO);
+
+ expectedException.expect(IllegalStateException.class);
+ expectedException.expectMessage("Configuration hardcoded level already registered for bar");
+
+ underTest.levelByDomain("bar", ProcessId.WEB_SERVER, LogDomain.JMX);
+ }
+
+ @Test
+ public void builder_immutableLevel_fails_with_NPE_if_logger_name_is_null() {
+ expectedException.expect(NullPointerException.class);
+ expectedException.expectMessage("loggerName can't be null");
+
+ underTest.immutableLevel(null, Level.ERROR);
+ }
+
+ @Test
+ public void builder_immutableLevel_fails_with_IAE_if_logger_name_is_empty() {
+ expectedException.expect(IllegalArgumentException.class);
+ expectedException.expectMessage("loggerName can't be empty");
+
+ underTest.immutableLevel("", Level.ERROR);
+ }
+
+ @Test
+ public void builder_immutableLevel_fails_with_NPE_if_level_is_null() {
+ expectedException.expect(NullPointerException.class);
+ expectedException.expectMessage("level can't be null");
+
+ underTest.immutableLevel("foo", null);
+ }
+
+ @Test
+ public void builder_immutableLevel_set_specified_level_for_specified_logger() {
+ LogLevelConfig config = underTest.immutableLevel("bar", Level.INFO).build();
+
+ assertThat(config.getConfiguredByProperties()).isEmpty();
+ assertThat(config.getConfiguredByHardcodedLevel()).hasSize(1);
+ assertThat(config.getConfiguredByHardcodedLevel().get("bar")).isEqualTo(Level.INFO);
+ }
+
+ @Test
+ public void builder_fails_with_ISE_if_immutableLevel_called_twice_for_same_logger() {
+ underTest.immutableLevel("foo", Level.INFO);
+
+ expectedException.expect(IllegalStateException.class);
+ expectedException.expectMessage("Configuration hardcoded level already registered for foo");
+
+ underTest.immutableLevel("foo", Level.DEBUG);
+ }
+
+ @Test
+ public void builder_fails_with_ISE_if_logger_has_domain_config() {
+ underTest.levelByDomain("pop", ProcessId.WEB_SERVER, LogDomain.JMX);
+
+ expectedException.expect(IllegalStateException.class);
+ expectedException.expectMessage("Configuration by property already registered for pop");
+
+ underTest.immutableLevel("pop", Level.DEBUG);
+ }
+
+ private static void expectUnsupportedOperationException(Runnable runnable) {
+ try {
+ runnable.run();
+ fail("a UnsupportedOperationException should have been raised");
+ } catch (Exception e) {
+ assertThat(e).isInstanceOf(UnsupportedOperationException.class);
+ }
+ }
+}
diff --git a/server/sonar-process/src/test/java/org/sonar/process/logging/LogbackHelperTest.java b/server/sonar-process/src/test/java/org/sonar/process/logging/LogbackHelperTest.java
index 1037de3f863..023d8494875 100644
--- a/server/sonar-process/src/test/java/org/sonar/process/logging/LogbackHelperTest.java
+++ b/server/sonar-process/src/test/java/org/sonar/process/logging/LogbackHelperTest.java
@@ -20,7 +20,6 @@
package org.sonar.process.logging;
import ch.qos.logback.classic.Level;
-import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.encoder.PatternLayoutEncoder;
import ch.qos.logback.classic.spi.LoggerContextListener;
@@ -31,24 +30,34 @@ import ch.qos.logback.core.rolling.FixedWindowRollingPolicy;
import ch.qos.logback.core.rolling.RollingFileAppender;
import ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy;
import ch.qos.logback.core.rolling.TimeBasedRollingPolicy;
+import com.tngtech.java.junit.dataprovider.DataProvider;
+import com.tngtech.java.junit.dataprovider.DataProviderRunner;
+import com.tngtech.java.junit.dataprovider.UseDataProvider;
import java.io.File;
import java.util.Properties;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
+import org.junit.rules.ExpectedException;
import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
import org.sonar.process.MessageException;
+import org.sonar.process.ProcessId;
import org.sonar.process.ProcessProperties;
import org.sonar.process.Props;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.fail;
+import static org.slf4j.Logger.ROOT_LOGGER_NAME;
+@RunWith(DataProviderRunner.class)
public class LogbackHelperTest {
@Rule
public TemporaryFolder temp = new TemporaryFolder();
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
private Props props = new Props(new Properties());
private LogbackHelper underTest = new LogbackHelper();
@@ -93,14 +102,6 @@ public class LogbackHelperTest {
}
@Test
- public void configureLogger() {
- Logger logger = underTest.configureLogger("my_logger", Level.WARN);
-
- assertThat(logger.getLevel()).isEqualTo(Level.WARN);
- assertThat(logger.getName()).isEqualTo("my_logger");
- }
-
- @Test
public void createRollingPolicy_defaults() {
LoggerContext ctx = underTest.getRootContext();
LogbackHelper.RollingPolicy policy = underTest.createRollingPolicy(ctx, props, "sonar");
@@ -170,4 +171,164 @@ public class LogbackHelperTest {
assertThat(e).hasMessage("Unsupported value for property sonar.log.rollingPolicy: unknown:foo");
}
}
+
+ @Test
+ public void apply_fails_with_IAE_if_global_property_has_unsupported_level() {
+ LogLevelConfig config = LogLevelConfig.newBuilder().rootLevelFor(ProcessId.WEB_SERVER).build();
+
+ props.set("sonar.log.level", "ERROR");
+
+ expectedException.expect(IllegalArgumentException.class);
+ expectedException.expectMessage("log level ERROR in property sonar.log.level is not a supported value (allowed levels are [TRACE, DEBUG, INFO])");
+
+ underTest.apply(config, props);
+ }
+
+ @Test
+ public void apply_fails_with_IAE_if_process_property_has_unsupported_level() {
+ LogLevelConfig config = LogLevelConfig.newBuilder().rootLevelFor(ProcessId.WEB_SERVER).build();
+
+ props.set("sonar.log.level.web", "ERROR");
+
+ expectedException.expect(IllegalArgumentException.class);
+ expectedException.expectMessage("log level ERROR in property sonar.log.level.web is not a supported value (allowed levels are [TRACE, DEBUG, INFO])");
+
+ underTest.apply(config, props);
+ }
+
+ @Test
+ public void apply_sets_logger_to_INFO_if_no_property_is_set() {
+ LogLevelConfig config = LogLevelConfig.newBuilder().rootLevelFor(ProcessId.WEB_SERVER).build();
+
+ LoggerContext context = underTest.apply(config, props);
+
+ assertThat(context.getLogger(ROOT_LOGGER_NAME).getLevel()).isEqualTo(Level.INFO);
+ }
+
+ @Test
+ public void apply_sets_logger_to_globlal_property_if_set() {
+ LogLevelConfig config = LogLevelConfig.newBuilder().rootLevelFor(ProcessId.WEB_SERVER).build();
+
+ props.set("sonar.log.level", "TRACE");
+
+ LoggerContext context = underTest.apply(config, props);
+
+ assertThat(context.getLogger(ROOT_LOGGER_NAME).getLevel()).isEqualTo(Level.TRACE);
+ }
+
+ @Test
+ public void apply_sets_logger_to_process_property_if_set() {
+ LogLevelConfig config = LogLevelConfig.newBuilder().rootLevelFor(ProcessId.WEB_SERVER).build();
+
+ props.set("sonar.log.level.web", "DEBUG");
+
+ LoggerContext context = underTest.apply(config, props);
+
+ assertThat(context.getLogger(ROOT_LOGGER_NAME).getLevel()).isEqualTo(Level.DEBUG);
+ }
+
+ @Test
+ public void apply_sets_logger_to_process_property_over_global_property_if_both_set() {
+ LogLevelConfig config = LogLevelConfig.newBuilder().rootLevelFor(ProcessId.WEB_SERVER).build();
+ props.set("sonar.log.level", "DEBUG");
+ props.set("sonar.log.level.web", "TRACE");
+
+ LoggerContext context = underTest.apply(config, props);
+
+ assertThat(context.getLogger(ROOT_LOGGER_NAME).getLevel()).isEqualTo(Level.TRACE);
+ }
+
+ @Test
+ public void apply_sets_domain_property_over_process_and_global_property_if_all_set() {
+ LogLevelConfig config = LogLevelConfig.newBuilder().levelByDomain("foo", ProcessId.WEB_SERVER, LogDomain.ES_CLIENT).build();
+ props.set("sonar.log.level", "DEBUG");
+ props.set("sonar.log.level.web", "DEBUG");
+ props.set("sonar.log.level.web.es", "TRACE");
+
+ LoggerContext context = underTest.apply(config, props);
+
+ assertThat(context.getLogger("foo").getLevel()).isEqualTo(Level.TRACE);
+ }
+
+ @Test
+ public void apply_sets_domain_property_over_process_property_if_both_set() {
+ LogLevelConfig config = LogLevelConfig.newBuilder().levelByDomain("foo", ProcessId.WEB_SERVER, LogDomain.ES_CLIENT).build();
+ props.set("sonar.log.level.web", "DEBUG");
+ props.set("sonar.log.level.web.es", "TRACE");
+
+ LoggerContext context = underTest.apply(config, props);
+
+ assertThat(context.getLogger("foo").getLevel()).isEqualTo(Level.TRACE);
+ }
+
+ @Test
+ public void apply_sets_domain_property_over_global_property_if_both_set() {
+ LogLevelConfig config = LogLevelConfig.newBuilder().levelByDomain("foo", ProcessId.WEB_SERVER, LogDomain.ES_CLIENT).build();
+ props.set("sonar.log.level", "DEBUG");
+ props.set("sonar.log.level.web.es", "TRACE");
+
+ LoggerContext context = underTest.apply(config, props);
+
+ assertThat(context.getLogger("foo").getLevel()).isEqualTo(Level.TRACE);
+ }
+
+ @Test
+ public void apply_fails_with_IAE_if_domain_property_has_unsupported_level() {
+ LogLevelConfig config = LogLevelConfig.newBuilder().levelByDomain("foo", ProcessId.WEB_SERVER, LogDomain.JMX).build();
+
+ props.set("sonar.log.level.web.jmx", "ERROR");
+
+ expectedException.expect(IllegalArgumentException.class);
+ expectedException.expectMessage("log level ERROR in property sonar.log.level.web.jmx is not a supported value (allowed levels are [TRACE, DEBUG, INFO])");
+
+ underTest.apply(config, props);
+ }
+
+ @Test
+ @UseDataProvider("logbackLevels")
+ public void apply_accepts_any_level_as_hardcoded_level(Level level) {
+ LogLevelConfig config = LogLevelConfig.newBuilder().immutableLevel("bar", level).build();
+
+ LoggerContext context = underTest.apply(config, props);
+
+ assertThat(context.getLogger("bar").getLevel()).isEqualTo(level);
+ }
+
+ @Test
+ public void changeRoot_sets_level_of_ROOT_and_all_loggers_with_a_config_but_the_hardcoded_one() {
+ LogLevelConfig config = LogLevelConfig.newBuilder()
+ .rootLevelFor(ProcessId.WEB_SERVER)
+ .levelByDomain("foo", ProcessId.WEB_SERVER, LogDomain.JMX)
+ .levelByDomain("bar", ProcessId.COMPUTE_ENGINE, LogDomain.ES_CLIENT)
+ .immutableLevel("doh", Level.ERROR)
+ .immutableLevel("pif", Level.TRACE)
+ .build();
+ LoggerContext context = underTest.apply(config, props);
+ assertThat(context.getLogger(ROOT_LOGGER_NAME).getLevel()).isEqualTo(Level.INFO);
+ assertThat(context.getLogger("foo").getLevel()).isEqualTo(Level.INFO);
+ assertThat(context.getLogger("bar").getLevel()).isEqualTo(Level.INFO);
+ assertThat(context.getLogger("doh").getLevel()).isEqualTo(Level.ERROR);
+ assertThat(context.getLogger("pif").getLevel()).isEqualTo(Level.TRACE);
+
+ underTest.changeRoot(config, Level.DEBUG);
+
+ assertThat(context.getLogger(ROOT_LOGGER_NAME).getLevel()).isEqualTo(Level.DEBUG);
+ assertThat(context.getLogger("foo").getLevel()).isEqualTo(Level.DEBUG);
+ assertThat(context.getLogger("bar").getLevel()).isEqualTo(Level.DEBUG);
+ assertThat(context.getLogger("doh").getLevel()).isEqualTo(Level.ERROR);
+ assertThat(context.getLogger("pif").getLevel()).isEqualTo(Level.TRACE);
+ }
+
+ @DataProvider
+ public static Object[][] logbackLevels() {
+ return new Object[][] {
+ {Level.OFF},
+ {Level.ERROR},
+ {Level.WARN},
+ {Level.INFO},
+ {Level.DEBUG},
+ {Level.TRACE},
+ {Level.ALL}
+ };
+ }
}