]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-8335 make changing root level change level of all loggers
authorSébastien Lesaint <sebastien.lesaint@sonarsource.com>
Tue, 29 Nov 2016 12:48:00 +0000 (13:48 +0100)
committerSébastien Lesaint <sebastien.lesaint@sonarsource.com>
Thu, 1 Dec 2016 11:29:40 +0000 (12:29 +0100)
21 files changed:
server/sonar-ce/src/main/java/org/sonar/ce/container/ComputeEngineContainerImpl.java
server/sonar-ce/src/main/java/org/sonar/ce/logging/ChangeLogLevelHttpAction.java
server/sonar-ce/src/test/java/org/sonar/ce/container/ComputeEngineContainerImplTest.java
server/sonar-ce/src/test/java/org/sonar/ce/logging/ChangeLogLevelHttpActionTest.java
server/sonar-process/src/main/java/org/sonar/process/logging/LogDomain.java [new file with mode: 0644]
server/sonar-process/src/main/java/org/sonar/process/logging/LogLevelConfig.java [new file with mode: 0644]
server/sonar-process/src/main/java/org/sonar/process/logging/LogbackHelper.java
server/sonar-process/src/test/java/org/sonar/process/logging/LogLevelConfigTest.java [new file with mode: 0644]
server/sonar-process/src/test/java/org/sonar/process/logging/LogbackHelperTest.java
server/sonar-search/src/main/java/org/sonar/search/SearchLogging.java
server/sonar-server/src/main/java/org/sonar/ce/log/CeProcessLogging.java
server/sonar-server/src/main/java/org/sonar/server/app/ServerProcessLogging.java
server/sonar-server/src/main/java/org/sonar/server/app/WebServerProcessLogging.java
server/sonar-server/src/main/java/org/sonar/server/platform/ServerLogging.java
server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel1.java
server/sonar-server/src/main/java/org/sonar/server/platform/ws/ChangeLogLevelAction.java
server/sonar-server/src/test/java/org/sonar/ce/log/CeProcessLoggingTest.java
server/sonar-server/src/test/java/org/sonar/server/app/WebServerProcessLoggingTest.java
server/sonar-server/src/test/java/org/sonar/server/platform/ServerLoggingTest.java
server/sonar-server/src/test/java/org/sonar/server/platform/ws/ChangeLogLevelActionTest.java
sonar-application/src/main/java/org/sonar/application/AppLogging.java

index c9711c9de2ec74e70d8a5c7c9f5d407caf947d2d..c671a6a2f879aba88571a9c491bdef7e0b06960c 100644 (file)
@@ -44,6 +44,7 @@ import org.sonar.ce.CeHttpModule;
 import org.sonar.ce.CeQueueModule;
 import org.sonar.ce.CeTaskCommonsModule;
 import org.sonar.ce.db.ReadOnlyPropertiesDao;
+import org.sonar.ce.log.CeProcessLogging;
 import org.sonar.ce.platform.ComputeEngineExtensionInstaller;
 import org.sonar.ce.settings.ProjectSettingsFactory;
 import org.sonar.ce.user.CeUserSession;
@@ -212,6 +213,7 @@ public class ComputeEngineContainerImpl implements ComputeEngineContainer {
       ThreadLocalSettings.class,
       new SonarQubeVersion(apiVersion),
       SonarRuntimeImpl.forSonarQube(ApiVersion.load(System2.INSTANCE), SonarQubeSide.COMPUTE_ENGINE),
+      CeProcessLogging.class,
       UuidFactoryImpl.INSTANCE,
       ClusterImpl.class,
       LogbackHelper.class,
index 626eb20c4a66a502374990609c314212fc941186..a84ccc23b85f53555dd47f08352cdff46a412475 100644 (file)
@@ -23,6 +23,7 @@ import fi.iki.elonen.NanoHTTPD;
 import org.sonar.api.utils.log.LoggerLevel;
 import org.sonar.api.utils.log.Loggers;
 import org.sonar.ce.httpd.HttpAction;
+import org.sonar.ce.log.CeProcessLogging;
 import org.sonar.db.Database;
 import org.sonar.server.platform.ServerLogging;
 
@@ -40,10 +41,12 @@ public class ChangeLogLevelHttpAction implements HttpAction {
 
   private final ServerLogging logging;
   private final Database db;
+  private final CeProcessLogging ceProcessLogging;
 
-  public ChangeLogLevelHttpAction(ServerLogging logging, Database db) {
+  public ChangeLogLevelHttpAction(ServerLogging logging, Database db, CeProcessLogging ceProcessLogging) {
     this.logging = logging;
     this.db = db;
+    this.ceProcessLogging = ceProcessLogging;
   }
 
   @Override
@@ -64,7 +67,7 @@ public class ChangeLogLevelHttpAction implements HttpAction {
     try {
       LoggerLevel level = LoggerLevel.valueOf(levelStr);
       db.enableSqlLogging(level.equals(LoggerLevel.TRACE));
-      logging.changeLevel(level);
+      logging.changeLevel(ceProcessLogging, level);
       return newFixedLengthResponse(OK, MIME_PLAINTEXT, null);
     } catch (IllegalArgumentException e) {
       Loggers.get(ChangeLogLevelHttpAction.class).debug("Value '{}' for parameter '{}' is invalid", levelStr, PARAM_LEVEL, e);
index 35fbd48101b59c8292f59993268de3cc659af0df..65f5a0826db16f0e9a5e73c9d76860198300ce2a 100644 (file)
@@ -105,7 +105,7 @@ public class ComputeEngineContainerImplTest {
     );
     assertThat(picoContainer.getParent().getParent().getParent().getComponentAdapters()).hasSize(
       COMPONENTS_IN_LEVEL_1_AT_CONSTRUCTION
-        + 26 // level 1
+        + 27 // level 1
         + 47 // content of DaoModule
         + 2 // content of EsSearchModule
         + 62 // content of CorePropertyDefinitions
index cce5313c2f5a76fd7f81b005a73d8762608d9c04..dc7b79977e8ecb2a726306e5b6f2ab004b3c9544 100644 (file)
@@ -26,6 +26,7 @@ import org.apache.commons.io.IOUtils;
 import org.junit.Test;
 import org.sonar.api.utils.log.LoggerLevel;
 import org.sonar.ce.httpd.HttpAction;
+import org.sonar.ce.log.CeProcessLogging;
 import org.sonar.db.Database;
 import org.sonar.server.platform.ServerLogging;
 
@@ -42,7 +43,8 @@ import static org.sonar.ce.httpd.CeHttpUtils.createHttpSession;
 public class ChangeLogLevelHttpActionTest {
   private ServerLogging serverLogging = mock(ServerLogging.class);
   private Database database = mock(Database.class);
-  private ChangeLogLevelHttpAction underTest = new ChangeLogLevelHttpAction(serverLogging, database);
+  private CeProcessLogging ceProcessLogging = new CeProcessLogging();
+  private ChangeLogLevelHttpAction underTest = new ChangeLogLevelHttpAction(serverLogging, database, ceProcessLogging);
 
   @Test
   public void register_to_path_systemInfo() {
@@ -81,7 +83,7 @@ public class ChangeLogLevelHttpActionTest {
 
     assertThat(response.getStatus()).isEqualTo(OK);
 
-    verify(serverLogging).changeLevel(LoggerLevel.ERROR);
+    verify(serverLogging).changeLevel(ceProcessLogging, LoggerLevel.ERROR);
     verify(database).enableSqlLogging(false);
   }
 
@@ -91,7 +93,7 @@ public class ChangeLogLevelHttpActionTest {
 
     assertThat(response.getStatus()).isEqualTo(OK);
 
-    verify(serverLogging).changeLevel(LoggerLevel.INFO);
+    verify(serverLogging).changeLevel(ceProcessLogging, LoggerLevel.INFO);
     verify(database).enableSqlLogging(false);
   }
 
@@ -101,7 +103,7 @@ public class ChangeLogLevelHttpActionTest {
 
     assertThat(response.getStatus()).isEqualTo(OK);
 
-    verify(serverLogging).changeLevel(LoggerLevel.DEBUG);
+    verify(serverLogging).changeLevel(ceProcessLogging, LoggerLevel.DEBUG);
     verify(database).enableSqlLogging(false);
   }
 
@@ -111,7 +113,7 @@ public class ChangeLogLevelHttpActionTest {
 
     assertThat(response.getStatus()).isEqualTo(OK);
 
-    verify(serverLogging).changeLevel(LoggerLevel.TRACE);
+    verify(serverLogging).changeLevel(ceProcessLogging, LoggerLevel.TRACE);
     verify(database).enableSqlLogging(true);
   }
 }
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 (file)
index 0000000..a4a19a6
--- /dev/null
@@ -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 (file)
index 0000000..cbef4a4
--- /dev/null
@@ -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);
+    }
+  }
+}
index 6d7589a618a0da063c8eb01349ae8104c39fcb80..5a55bade6d3fda625ef439a6340f89d1828f3d86 100644 (file)
@@ -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 (file)
index 0000000..8802520
--- /dev/null
@@ -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);
+    }
+  }
+}
index 1037de3f863630e8bc8a7b32ad8ee42292fa04f0..023d8494875ca55d4b708fde753c2ce4767b5fb1 100644 (file)
@@ -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();
@@ -92,14 +101,6 @@ public class LogbackHelperTest {
     assertThat(appender.getCopyOfAttachedFiltersList()).isEmpty();
   }
 
-  @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();
@@ -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}
+    };
+  }
 }
index 38e26b7621af4c2fb19c6b6942752b0545f990a8..d4ef440abd44d4b18abea653b4570e943ab1e8c4 100644 (file)
@@ -20,6 +20,7 @@
 package org.sonar.search;
 
 import ch.qos.logback.classic.LoggerContext;
+import org.sonar.process.logging.LogLevelConfig;
 import org.sonar.process.logging.LogbackHelper;
 import org.sonar.process.ProcessId;
 import org.sonar.process.Props;
@@ -40,7 +41,8 @@ public class SearchLogging {
     String logPattern = helper.buildLogPattern(config);
     helper.configureGlobalFileLog(props, config, logPattern);
     helper.configureForSubprocessGobbler(props, logPattern);
-    helper.configureRootLogLevel(props, ProcessId.ELASTICSEARCH);
+
+    helper.apply(LogLevelConfig.newBuilder().rootLevelFor(ProcessId.ELASTICSEARCH).build(), props);
 
     return ctx;
   }
index 76fdafff71410d83c5c079e411ab3d916d7b41e8..52a385de58f20dd54fee84f67776d72b0e29dacc 100644 (file)
  */
 package org.sonar.ce.log;
 
-import org.sonar.process.logging.LogbackHelper;
 import org.sonar.process.ProcessId;
-import org.sonar.process.Props;
+import org.sonar.process.logging.LogDomain;
+import org.sonar.process.logging.LogLevelConfig;
 import org.sonar.server.app.ServerProcessLogging;
 
 import static org.sonar.ce.log.CeLogging.MDC_CE_TASK_UUID;
-import static org.sonar.process.logging.LogbackHelper.LogDomain;
 
 /**
  * Configure logback for the Compute Engine process. Logs are written to file "ce.log" in SQ's log directory.
@@ -37,9 +36,14 @@ public class CeProcessLogging extends ServerProcessLogging {
   }
 
   @Override
-  protected void extendConfiguration(LogbackHelper helper, Props props) {
-    helper.configureLoggerLogLevelFromDomain("sql", props, ProcessId.COMPUTE_ENGINE, LogDomain.SQL);
-    helper.configureLoggerLogLevelFromDomain("es", props, ProcessId.COMPUTE_ENGINE, LogDomain.ES_CLIENT);
-    helper.configureLoggersLogLevelFromDomain(JMX_RMI_LOGGER_NAMES, props, ProcessId.COMPUTE_ENGINE, LogDomain.JMX);
+  protected void extendLogLevelConfiguration(LogLevelConfig.Builder logLevelConfigBuilder) {
+    logLevelConfigBuilder.levelByDomain("sql", ProcessId.COMPUTE_ENGINE, LogDomain.SQL);
+    logLevelConfigBuilder.levelByDomain("es", ProcessId.COMPUTE_ENGINE, LogDomain.ES_CLIENT);
+    JMX_RMI_LOGGER_NAMES.forEach(loggerName -> logLevelConfigBuilder.levelByDomain(loggerName, ProcessId.COMPUTE_ENGINE, LogDomain.JMX));
+  }
+
+  @Override
+  protected void extendConfigure() {
+    // nothing to do
   }
 }
index daadb4f1ddff419ae3abcb8dfec17b8ebe1034de..baeaa88d31878cc1cf52955b058a8304c27e3cd5 100644 (file)
  */
 package org.sonar.server.app;
 
+import ch.qos.logback.classic.Level;
 import ch.qos.logback.classic.LoggerContext;
 import com.google.common.collect.ImmutableSet;
 import java.util.Set;
+import org.sonar.process.logging.LogLevelConfig;
 import org.sonar.process.logging.LogbackHelper;
 import org.sonar.process.ProcessId;
 import org.sonar.process.Props;
 import org.sonar.process.logging.RootLoggerConfig;
-import org.sonar.server.platform.ServerLogging;
 
 import static org.sonar.process.logging.RootLoggerConfig.newRootLoggerConfigBuilder;
 
 public abstract class ServerProcessLogging {
   protected static final Set<String> JMX_RMI_LOGGER_NAMES = ImmutableSet.of(
-      "javax.management.remote.timeout",
-      "javax.management.remote.misc",
-      "javax.management.remote.rmi",
-      "javax.management.mbeanserver",
-      "sun.rmi.loader",
-      "sun.rmi.transport.tcp",
-      "sun.rmi.transport.misc",
-      "sun.rmi.server.call",
-      "sun.rmi.dgc");
+    "javax.management.remote.timeout",
+    "javax.management.remote.misc",
+    "javax.management.remote.rmi",
+    "javax.management.mbeanserver",
+    "sun.rmi.loader",
+    "sun.rmi.transport.tcp",
+    "sun.rmi.transport.misc",
+    "sun.rmi.server.call",
+    "sun.rmi.dgc");
   private final ProcessId processId;
   private final String threadIdFieldPattern;
   private final LogbackHelper helper = new LogbackHelper();
+  private final LogLevelConfig logLevelConfig;
 
   protected ServerProcessLogging(ProcessId processId, String threadIdFieldPattern) {
     this.processId = processId;
     this.threadIdFieldPattern = threadIdFieldPattern;
+    this.logLevelConfig = createLogLevelConfiguration(processId);
+  }
+
+  private LogLevelConfig createLogLevelConfiguration(ProcessId processId) {
+    LogLevelConfig.Builder builder = LogLevelConfig.newBuilder();
+    builder.rootLevelFor(processId);
+    builder.immutableLevel("rails", Level.WARN);
+    builder.immutableLevel("org.apache.ibatis", Level.WARN);
+    builder.immutableLevel("java.sql", Level.WARN);
+    builder.immutableLevel("java.sql.ResultSet", Level.WARN);
+    builder.immutableLevel("org.sonar.MEASURE_FILTER", Level.WARN);
+    builder.immutableLevel("org.elasticsearch", Level.INFO);
+    builder.immutableLevel("org.elasticsearch.node", Level.INFO);
+    builder.immutableLevel("org.elasticsearch.http", Level.INFO);
+    builder.immutableLevel("ch.qos.logback", Level.WARN);
+    builder.immutableLevel("org.apache.catalina", Level.INFO);
+    builder.immutableLevel("org.apache.coyote", Level.INFO);
+    builder.immutableLevel("org.apache.jasper", Level.INFO);
+    builder.immutableLevel("org.apache.tomcat", Level.INFO);
+
+    extendLogLevelConfiguration(builder);
+
+    return builder.build();
   }
 
   public LoggerContext configure(Props props) {
@@ -56,13 +81,20 @@ public abstract class ServerProcessLogging {
 
     helper.enableJulChangePropagation(ctx);
     configureRootLogger(props);
+    helper.apply(logLevelConfig, props);
 
-    extendConfiguration(helper, props);
+    extendConfigure();
 
     return ctx;
   }
 
-  protected abstract void extendConfiguration(LogbackHelper helper, Props props);
+  public LogLevelConfig getLogLevelConfig() {
+    return this.logLevelConfig;
+  }
+
+  protected abstract void extendLogLevelConfiguration(LogLevelConfig.Builder logLevelConfigBuilder);
+
+  protected abstract void extendConfigure();
 
   private void configureRootLogger(Props props) {
     RootLoggerConfig config = newRootLoggerConfigBuilder()
@@ -73,9 +105,6 @@ public abstract class ServerProcessLogging {
 
     helper.configureGlobalFileLog(props, config, logPattern);
     helper.configureForSubprocessGobbler(props, logPattern);
-
-    helper.configureRootLogLevel(props, processId);
-    ServerLogging.configureHardcodedLevels(helper);
   }
 
 }
index 202b9e1f5ce46a5aba6c2a6c837f15310fce7c22..da2a4f6ad4808f12a195cd27aadb803e11c41c05 100644 (file)
@@ -21,11 +21,10 @@ package org.sonar.server.app;
 
 import java.util.logging.LogManager;
 import org.slf4j.bridge.SLF4JBridgeHandler;
-import org.sonar.process.logging.LogbackHelper;
+import org.sonar.process.logging.LogLevelConfig;
 import org.sonar.process.ProcessId;
-import org.sonar.process.Props;
 
-import static org.sonar.process.logging.LogbackHelper.LogDomain;
+import org.sonar.process.logging.LogDomain;
 import static org.sonar.server.platform.web.requestid.RequestIdMDCStorage.HTTP_REQUEST_ID_MDC_KEY;
 
 /**
@@ -38,13 +37,16 @@ public class WebServerProcessLogging extends ServerProcessLogging {
   }
 
   @Override
-  protected void extendConfiguration(LogbackHelper helper, Props props) {
+  protected void extendLogLevelConfiguration(LogLevelConfig.Builder logLevelConfigBuilder) {
+    logLevelConfigBuilder.levelByDomain("sql", ProcessId.WEB_SERVER, LogDomain.SQL);
+    logLevelConfigBuilder.levelByDomain("es", ProcessId.WEB_SERVER, LogDomain.ES_CLIENT);
+    JMX_RMI_LOGGER_NAMES.forEach(loggerName -> logLevelConfigBuilder.levelByDomain(loggerName, ProcessId.WEB_SERVER, LogDomain.JMX));
+  }
+
+  @Override
+  protected void extendConfigure() {
     // Configure java.util.logging, used by Tomcat, in order to forward to slf4j
     LogManager.getLogManager().reset();
     SLF4JBridgeHandler.install();
-
-    helper.configureLoggerLogLevelFromDomain("sql", props, ProcessId.WEB_SERVER, LogDomain.SQL);
-    helper.configureLoggerLogLevelFromDomain("es", props, ProcessId.WEB_SERVER, LogDomain.ES_CLIENT);
-    helper.configureLoggersLogLevelFromDomain(JMX_RMI_LOGGER_NAMES, props, ProcessId.WEB_SERVER, LogDomain.JMX);
   }
 }
index 9dd8c447592a6306d842b0ca4890e83a9f55f794..08feeea802c9332b2b6c7d90143cad967ed6ae6f 100644 (file)
@@ -31,6 +31,7 @@ import org.sonar.api.utils.log.LoggerLevel;
 import org.sonar.api.utils.log.Loggers;
 import org.sonar.process.logging.LogbackHelper;
 import org.sonar.process.ProcessProperties;
+import org.sonar.server.app.ServerProcessLogging;
 
 @ServerSide
 @ComputeEngineSide
@@ -49,10 +50,9 @@ public class ServerLogging {
     this.settings = settings;
   }
 
-  public void changeLevel(LoggerLevel level) {
+  public void changeLevel(ServerProcessLogging serverProcessLogging, LoggerLevel level) {
     Level logbackLevel = Level.toLevel(level.name());
-    helper.configureRootLogLevel(logbackLevel);
-    configureHardcodedLevels(helper);
+    helper.changeRoot(serverProcessLogging.getLogLevelConfig(), logbackLevel);
     LoggerFactory.getLogger(ServerLogging.class).info("Level of logs changed to {}", level);
   }
 
@@ -60,22 +60,6 @@ public class ServerLogging {
     return Loggers.get(Logger.ROOT_LOGGER_NAME).getLevel();
   }
 
-  public static void configureHardcodedLevels(LogbackHelper helper) {
-    helper.configureLogger("rails", Level.WARN);
-    helper.configureLogger("org.apache.ibatis", Level.WARN);
-    helper.configureLogger("java.sql", Level.WARN);
-    helper.configureLogger("java.sql.ResultSet", Level.WARN);
-    helper.configureLogger("org.sonar.MEASURE_FILTER", Level.WARN);
-    helper.configureLogger("org.elasticsearch", Level.INFO);
-    helper.configureLogger("org.elasticsearch.node", Level.INFO);
-    helper.configureLogger("org.elasticsearch.http", Level.INFO);
-    helper.configureLogger("ch.qos.logback", Level.WARN);
-    helper.configureLogger("org.apache.catalina", Level.INFO);
-    helper.configureLogger("org.apache.coyote", Level.INFO);
-    helper.configureLogger("org.apache.jasper", Level.INFO);
-    helper.configureLogger("org.apache.tomcat", Level.INFO);
-  }
-
   /**
    * The directory that contains log files. May not exist.
    */
index 6fecd6991dc9a8848e6de2231566d47ee56c39ba..7bc4c362646605f90ec9613b58f2e4cbec394502 100644 (file)
@@ -40,6 +40,7 @@ import org.sonar.db.version.DatabaseVersion;
 import org.sonar.process.logging.LogbackHelper;
 import org.sonar.server.app.ProcessCommandWrapperImpl;
 import org.sonar.server.app.RestartFlagHolderImpl;
+import org.sonar.server.app.WebServerProcessLogging;
 import org.sonar.server.issue.index.IssueIndex;
 import org.sonar.server.platform.DatabaseServerCompatibility;
 import org.sonar.server.platform.LogServerVersion;
@@ -87,6 +88,7 @@ public class PlatformLevel1 extends PlatformLevel {
       UrlSettings.class,
       EmbeddedDatabaseFactory.class,
       LogbackHelper.class,
+      WebServerProcessLogging.class,
       DefaultDatabase.class,
       DatabaseChecker.class,
       // must instantiate deprecated class in 5.2 and only this one (and not its replacement)
index be2d3602580c408c42619ea4cf778b3dcaeff9cd..1ccb94afe4072560e3192b7150599e8096b46979 100644 (file)
@@ -25,6 +25,7 @@ import org.sonar.api.server.ws.WebService;
 import org.sonar.api.utils.log.LoggerLevel;
 import org.sonar.ce.http.CeHttpClient;
 import org.sonar.db.Database;
+import org.sonar.server.app.WebServerProcessLogging;
 import org.sonar.server.platform.ServerLogging;
 import org.sonar.server.user.UserSession;
 
@@ -38,12 +39,14 @@ public class ChangeLogLevelAction implements SystemWsAction {
   private final ServerLogging logging;
   private final Database db;
   private final CeHttpClient ceHttpClient;
+  private final WebServerProcessLogging webServerProcessLogging;
 
-  public ChangeLogLevelAction(UserSession userSession, ServerLogging logging, Database db, CeHttpClient ceHttpClient) {
+  public ChangeLogLevelAction(UserSession userSession, ServerLogging logging, Database db, CeHttpClient ceHttpClient, WebServerProcessLogging webServerProcessLogging) {
     this.userSession = userSession;
     this.logging = logging;
     this.db = db;
     this.ceHttpClient = ceHttpClient;
+    this.webServerProcessLogging = webServerProcessLogging;
   }
 
   @Override
@@ -67,7 +70,7 @@ public class ChangeLogLevelAction implements SystemWsAction {
 
     LoggerLevel level = LoggerLevel.valueOf(wsRequest.mandatoryParam(PARAM_LEVEL));
     db.enableSqlLogging(level.equals(LoggerLevel.TRACE));
-    logging.changeLevel(level);
+    logging.changeLevel(webServerProcessLogging, level);
     ceHttpClient.changeLogLevel(level);
     wsResponse.noContent();
   }
index f16620049efbab77233b2b56625245c5056d268f..0058688f0560a8ed3182436c2118e5e85976f6bb 100644 (file)
@@ -36,9 +36,9 @@ import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.ExpectedException;
 import org.junit.rules.TemporaryFolder;
-import org.sonar.process.logging.LogbackHelper;
 import org.sonar.process.ProcessProperties;
 import org.sonar.process.Props;
+import org.sonar.process.logging.LogbackHelper;
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.slf4j.Logger.ROOT_LOGGER_NAME;
@@ -385,6 +385,31 @@ public class CeProcessLoggingTest {
     underTest.configure(props);
   }
 
+  @Test
+  public void configure_defines_hardcoded_levels() {
+    LoggerContext context = underTest.configure(props);
+
+    verifyImmutableLogLevels(context);
+  }
+
+  @Test
+  public void configure_defines_hardcoded_levels_unchanged_by_global_property() {
+    props.set("sonar.log.level", "TRACE");
+
+    LoggerContext context = underTest.configure(props);
+
+    verifyImmutableLogLevels(context);
+  }
+
+  @Test
+  public void configure_defines_hardcoded_levels_unchanged_by_ce_property() {
+    props.set("sonar.log.level.ce", "TRACE");
+
+    LoggerContext context = underTest.configure(props);
+
+    verifyImmutableLogLevels(context);
+  }
+
   private void verifyRootLogLevel(LoggerContext ctx, Level expected) {
     assertThat(ctx.getLogger(ROOT_LOGGER_NAME).getLevel()).isEqualTo(expected);
   }
@@ -408,4 +433,20 @@ public class CeProcessLoggingTest {
     assertThat(ctx.getLogger("sun.rmi.server.call").getLevel()).isEqualTo(expected);
     assertThat(ctx.getLogger("sun.rmi.dgc").getLevel()).isEqualTo(expected);
   }
+
+  private void verifyImmutableLogLevels(LoggerContext ctx) {
+    assertThat(ctx.getLogger("rails").getLevel()).isEqualTo(Level.WARN);
+    assertThat(ctx.getLogger("org.apache.ibatis").getLevel()).isEqualTo(Level.WARN);
+    assertThat(ctx.getLogger("java.sql").getLevel()).isEqualTo(Level.WARN);
+    assertThat(ctx.getLogger("java.sql.ResultSet").getLevel()).isEqualTo(Level.WARN);
+    assertThat(ctx.getLogger("org.sonar.MEASURE_FILTER").getLevel()).isEqualTo(Level.WARN);
+    assertThat(ctx.getLogger("org.elasticsearch").getLevel()).isEqualTo(Level.INFO);
+    assertThat(ctx.getLogger("org.elasticsearch.node").getLevel()).isEqualTo(Level.INFO);
+    assertThat(ctx.getLogger("org.elasticsearch.http").getLevel()).isEqualTo(Level.INFO);
+    assertThat(ctx.getLogger("ch.qos.logback").getLevel()).isEqualTo(Level.WARN);
+    assertThat(ctx.getLogger("org.apache.catalina").getLevel()).isEqualTo(Level.INFO);
+    assertThat(ctx.getLogger("org.apache.coyote").getLevel()).isEqualTo(Level.INFO);
+    assertThat(ctx.getLogger("org.apache.jasper").getLevel()).isEqualTo(Level.INFO);
+    assertThat(ctx.getLogger("org.apache.tomcat").getLevel()).isEqualTo(Level.INFO);
+  }
 }
index 05b05b77f304c2d45b295d3214974cf8f610f2cb..02dba16937ae284de072f7982580c90823703723 100644 (file)
@@ -385,6 +385,31 @@ public class WebServerProcessLoggingTest {
     underTest.configure(props);
   }
 
+  @Test
+  public void configure_defines_hardcoded_levels() {
+    LoggerContext context = underTest.configure(props);
+
+    verifyImmutableLogLevels(context);
+  }
+
+  @Test
+  public void configure_defines_hardcoded_levels_unchanged_by_global_property() {
+    props.set("sonar.log.level", "TRACE");
+
+    LoggerContext context = underTest.configure(props);
+
+    verifyImmutableLogLevels(context);
+  }
+
+  @Test
+  public void configure_defines_hardcoded_levels_unchanged_by_ce_property() {
+    props.set("sonar.log.level.ce", "TRACE");
+
+    LoggerContext context = underTest.configure(props);
+
+    verifyImmutableLogLevels(context);
+  }
+
   private void verifyRootLogLevel(LoggerContext ctx, Level expected) {
     Logger rootLogger = ctx.getLogger(ROOT_LOGGER_NAME);
     assertThat(rootLogger.getLevel()).isEqualTo(expected);
@@ -410,4 +435,20 @@ public class WebServerProcessLoggingTest {
     assertThat(ctx.getLogger("sun.rmi.dgc").getLevel()).isEqualTo(expected);
   }
 
+  private void verifyImmutableLogLevels(LoggerContext ctx) {
+    assertThat(ctx.getLogger("rails").getLevel()).isEqualTo(Level.WARN);
+    assertThat(ctx.getLogger("org.apache.ibatis").getLevel()).isEqualTo(Level.WARN);
+    assertThat(ctx.getLogger("java.sql").getLevel()).isEqualTo(Level.WARN);
+    assertThat(ctx.getLogger("java.sql.ResultSet").getLevel()).isEqualTo(Level.WARN);
+    assertThat(ctx.getLogger("org.sonar.MEASURE_FILTER").getLevel()).isEqualTo(Level.WARN);
+    assertThat(ctx.getLogger("org.elasticsearch").getLevel()).isEqualTo(Level.INFO);
+    assertThat(ctx.getLogger("org.elasticsearch.node").getLevel()).isEqualTo(Level.INFO);
+    assertThat(ctx.getLogger("org.elasticsearch.http").getLevel()).isEqualTo(Level.INFO);
+    assertThat(ctx.getLogger("ch.qos.logback").getLevel()).isEqualTo(Level.WARN);
+    assertThat(ctx.getLogger("org.apache.catalina").getLevel()).isEqualTo(Level.INFO);
+    assertThat(ctx.getLogger("org.apache.coyote").getLevel()).isEqualTo(Level.INFO);
+    assertThat(ctx.getLogger("org.apache.jasper").getLevel()).isEqualTo(Level.INFO);
+    assertThat(ctx.getLogger("org.apache.tomcat").getLevel()).isEqualTo(Level.INFO);
+  }
+
 }
index 4092a0cce72a7c6cc9b0ee3864b58dbf374000f2..9dc813ddaf5b2bfe733f6bc2ab233ab61c80f77f 100644 (file)
 package org.sonar.server.platform;
 
 import ch.qos.logback.classic.Level;
+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.io.IOException;
 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.api.config.MapSettings;
 import org.sonar.api.config.Settings;
 import org.sonar.api.utils.log.LogTester;
 import org.sonar.api.utils.log.LoggerLevel;
 import org.sonar.process.ProcessProperties;
+import org.sonar.process.logging.LogLevelConfig;
 import org.sonar.process.logging.LogbackHelper;
+import org.sonar.server.app.ServerProcessLogging;
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
+@RunWith(DataProviderRunner.class)
 public class ServerLoggingTest {
 
   @Rule
@@ -67,66 +75,39 @@ public class ServerLoggingTest {
   }
 
   @Test
-  public void configureHardcodedLevels() {
-    LogbackHelper logbackHelper = mock(LogbackHelper.class);
-    ServerLogging.configureHardcodedLevels(logbackHelper);
+  @UseDataProvider("supportedSonarApiLevels")
+  public void changeLevel_calls_changeRoot_with_LogLevelConfig_and_level_converted_to_logback_class_then_log_INFO_message(LoggerLevel level) {
+    ServerProcessLogging serverProcessLogging = mock(ServerProcessLogging.class);
+    LogLevelConfig logLevelConfig = LogLevelConfig.newBuilder().build();
+    when(serverProcessLogging.getLogLevelConfig()).thenReturn(logLevelConfig);
 
-    verifyHardcodedLevels(logbackHelper);
-  }
-
-  @Test
-  public void changeLevel_throws_IAE_if_level_is_WARN() {
-    expectedException.expect(IllegalArgumentException.class);
-    expectedException.expectMessage("WARN log level is not supported (allowed levels are [");
+    underTest.changeLevel(serverProcessLogging, level);
 
-    underTest.changeLevel(LoggerLevel.WARN);
+    verify(logbackHelper).changeRoot(logLevelConfig, Level.valueOf(level.name()));
   }
 
-  @Test
-  public void changeLevel_throws_IAE_if_level_is_ERROR() {
-    expectedException.expect(IllegalArgumentException.class);
-    expectedException.expectMessage("ERROR log level is not supported (allowed levels are [");
-
-    underTest.changeLevel(LoggerLevel.ERROR);
+  @DataProvider
+  public static Object[][] supportedSonarApiLevels() {
+    return new Object[][] {
+      {LoggerLevel.INFO},
+      {LoggerLevel.DEBUG},
+      {LoggerLevel.TRACE}
+    };
   }
 
   @Test
-  public void changeLevel_changes_root_logger_level_to_INFO() {
-    underTest.changeLevel(LoggerLevel.INFO);
-
-    verify(logbackHelper).configureRootLogLevel(Level.INFO);
-    verifyHardcodedLevels(logbackHelper);
-  }
-
-  @Test
-  public void changeLevel_changes_root_logger_level_to_DEBUG() {
-    underTest.changeLevel(LoggerLevel.DEBUG);
+  public void changeLevel_fails_with_IAE_when_level_is_ERROR() {
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage("ERROR log level is not supported (allowed levels are [TRACE, DEBUG, INFO])");
 
-    verify(logbackHelper).configureRootLogLevel(Level.DEBUG);
-    verifyHardcodedLevels(logbackHelper);
+    underTest.changeLevel(mock(ServerProcessLogging.class), LoggerLevel.ERROR);
   }
 
   @Test
-  public void changeLevel_changes_root_logger_level_to_TRACE() {
-    underTest.changeLevel(LoggerLevel.TRACE);
-
-    verify(logbackHelper).configureRootLogLevel(Level.TRACE);
-    verifyHardcodedLevels(logbackHelper);
-  }
+  public void changeLevel_fails_with_IAE_when_level_is_WARN() {
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage("WARN log level is not supported (allowed levels are [TRACE, DEBUG, INFO])");
 
-  private void verifyHardcodedLevels(LogbackHelper logbackHelper) {
-    verify(logbackHelper).configureLogger("rails", Level.WARN);
-    verify(logbackHelper).configureLogger("org.apache.ibatis", Level.WARN);
-    verify(logbackHelper).configureLogger("java.sql", Level.WARN);
-    verify(logbackHelper).configureLogger("java.sql.ResultSet", Level.WARN);
-    verify(logbackHelper).configureLogger("org.sonar.MEASURE_FILTER", Level.WARN);
-    verify(logbackHelper).configureLogger("org.elasticsearch", Level.INFO);
-    verify(logbackHelper).configureLogger("org.elasticsearch.node", Level.INFO);
-    verify(logbackHelper).configureLogger("org.elasticsearch.http", Level.INFO);
-    verify(logbackHelper).configureLogger("ch.qos.logback", Level.WARN);
-    verify(logbackHelper).configureLogger("org.apache.catalina", Level.INFO);
-    verify(logbackHelper).configureLogger("org.apache.coyote", Level.INFO);
-    verify(logbackHelper).configureLogger("org.apache.jasper", Level.INFO);
-    verify(logbackHelper).configureLogger("org.apache.tomcat", Level.INFO);
+    underTest.changeLevel(mock(ServerProcessLogging.class), LoggerLevel.WARN);
   }
 }
index bdf1f6abf0fc614531d60d5318ae9bf1a917bc5c..ca30fa204e7a72ba181da499fcf652538f3293ce 100644 (file)
@@ -25,6 +25,7 @@ import org.junit.rules.ExpectedException;
 import org.sonar.api.utils.log.LoggerLevel;
 import org.sonar.ce.http.CeHttpClient;
 import org.sonar.db.Database;
+import org.sonar.server.app.WebServerProcessLogging;
 import org.sonar.server.exceptions.ForbiddenException;
 import org.sonar.server.platform.ServerLogging;
 import org.sonar.server.tester.UserSessionRule;
@@ -43,7 +44,8 @@ public class ChangeLogLevelActionTest {
   private ServerLogging serverLogging = mock(ServerLogging.class);
   private Database db = mock(Database.class);
   private CeHttpClient ceHttpClient = mock(CeHttpClient.class);
-  private ChangeLogLevelAction underTest = new ChangeLogLevelAction(userSession, serverLogging, db, ceHttpClient);
+  private WebServerProcessLogging webServerProcessLogging = new WebServerProcessLogging();
+  private ChangeLogLevelAction underTest = new ChangeLogLevelAction(userSession, serverLogging, db, ceHttpClient, webServerProcessLogging);
   private WsActionTester actionTester = new WsActionTester(underTest);
 
   @Test
@@ -71,7 +73,7 @@ public class ChangeLogLevelActionTest {
       .setMethod("POST")
       .execute();
 
-    verify(serverLogging).changeLevel(LoggerLevel.DEBUG);
+    verify(serverLogging).changeLevel(webServerProcessLogging, LoggerLevel.DEBUG);
     verify(ceHttpClient).changeLogLevel(LoggerLevel.DEBUG);
     verify(db).enableSqlLogging(false);
   }
@@ -85,7 +87,7 @@ public class ChangeLogLevelActionTest {
       .setMethod("POST")
       .execute();
 
-    verify(serverLogging).changeLevel(LoggerLevel.TRACE);
+    verify(serverLogging).changeLevel(webServerProcessLogging, LoggerLevel.TRACE);
     verify(ceHttpClient).changeLogLevel(LoggerLevel.TRACE);
     verify(db).enableSqlLogging(true);
   }
index 84d0b074b0ff3781e85c7fa62b969ea8323385ca..284fef1466d248cda7e31ab039dace6e734b5ca6 100644 (file)
@@ -24,6 +24,7 @@ import ch.qos.logback.classic.LoggerContext;
 import ch.qos.logback.classic.spi.ILoggingEvent;
 import ch.qos.logback.core.ConsoleAppender;
 import ch.qos.logback.core.FileAppender;
+import org.sonar.process.logging.LogLevelConfig;
 import org.sonar.process.logging.LogbackHelper;
 import org.sonar.process.ProcessId;
 import org.sonar.process.Props;
@@ -127,7 +128,7 @@ class AppLogging {
     } else {
       configureWithWrapperWritingToFile(ctx);
     }
-    helper.configureRootLogLevel(props, ProcessId.APP);
+    helper.apply(LogLevelConfig.newBuilder().rootLevelFor(ProcessId.APP).build(), props);
 
     return ctx;
   }