aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--it/it-tests/src/test/java/it/serverSystem/LogsTest.java53
-rw-r--r--server/sonar-ce/src/main/java/org/sonar/ce/container/ComputeEngineContainerImpl.java4
-rw-r--r--server/sonar-ce/src/main/java/org/sonar/ce/logging/ChangeLogLevelHttpAction.java7
-rw-r--r--server/sonar-ce/src/test/java/org/sonar/ce/container/ComputeEngineContainerImplTest.java2
-rw-r--r--server/sonar-ce/src/test/java/org/sonar/ce/logging/ChangeLogLevelHttpActionTest.java12
-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.java (renamed from server/sonar-process/src/main/java/org/sonar/process/LogbackHelper.java)186
-rw-r--r--server/sonar-process/src/main/java/org/sonar/process/logging/RootLoggerConfig.java71
-rw-r--r--server/sonar-process/src/test/java/org/sonar/process/LogbackHelperTest.java170
-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.java334
-rw-r--r--server/sonar-search/src/main/java/org/sonar/search/SearchLogging.java11
-rw-r--r--server/sonar-search/src/test/java/org/sonar/search/SearchLoggingTest.java2
-rw-r--r--server/sonar-server/src/main/java/org/sonar/ce/log/CeProcessLogging.java18
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/app/ServerProcessLogging.java66
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/app/TomcatAccessLog.java2
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/app/WebServerProcessLogging.java18
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/platform/ServerLogging.java31
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel1.java4
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/platform/ws/ChangeLogLevelAction.java9
-rw-r--r--server/sonar-server/src/test/java/org/sonar/ce/log/CeLoggingTest.java2
-rw-r--r--server/sonar-server/src/test/java/org/sonar/ce/log/CeProcessLoggingTest.java130
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/app/ProgrammaticLogbackValveTest.java2
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/app/WebServerProcessLoggingTest.java130
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/platform/ServerLoggingTest.java90
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/platform/ws/ChangeLogLevelActionTest.java8
-rw-r--r--sonar-application/src/main/java/org/sonar/application/AppLogging.java10
-rw-r--r--sonar-application/src/test/java/org/sonar/application/AppLoggingTest.java2
-rw-r--r--sonar-db/src/main/java/org/sonar/db/DefaultDatabase.java2
-rw-r--r--sonar-db/src/test/java/org/sonar/db/DefaultDatabaseTest.java2
-rw-r--r--sonar-db/src/test/java/org/sonar/db/TestDb.java2
32 files changed, 1283 insertions, 457 deletions
diff --git a/it/it-tests/src/test/java/it/serverSystem/LogsTest.java b/it/it-tests/src/test/java/it/serverSystem/LogsTest.java
index dc976fc78d5..4252df014ba 100644
--- a/it/it-tests/src/test/java/it/serverSystem/LogsTest.java
+++ b/it/it-tests/src/test/java/it/serverSystem/LogsTest.java
@@ -20,20 +20,28 @@
package it.serverSystem;
import com.sonar.orchestrator.Orchestrator;
+import com.sonar.orchestrator.build.SonarScanner;
import it.Category4Suite;
import java.io.File;
import java.io.IOException;
+import java.util.Collection;
+import java.util.Collections;
import java.util.List;
+import java.util.stream.Collectors;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.input.ReversedLinesFileReader;
+import org.assertj.core.util.Files;
+import org.junit.Before;
import org.junit.ClassRule;
import org.junit.Test;
import org.sonarqube.ws.client.GetRequest;
import org.sonarqube.ws.client.WsClient;
+import org.sonarqube.ws.client.issue.SearchWsRequest;
import util.ItUtils;
import static java.lang.String.format;
import static org.assertj.core.api.Assertions.assertThat;
+import static util.ItUtils.projectDir;
public class LogsTest {
@@ -43,6 +51,11 @@ public class LogsTest {
@ClassRule
public static final Orchestrator orchestrator = Category4Suite.ORCHESTRATOR;
+ @Before
+ public void cleanDatabase() {
+ orchestrator.resetData();
+ }
+
/**
* SONAR-7581
*/
@@ -75,6 +88,46 @@ public class LogsTest {
assertThat(logs.get(logs.size() - 1)).describedAs("message is the last line of logs").contains(sqIsUpMessage);
}
+ @Test
+ public void test_ws_change_log_level() throws IOException {
+ generateSqlAndEsLogsInWebAndCe();
+
+ assertThat(logLevelsOf(orchestrator.getServer().getWebLogs())).doesNotContain("DEBUG", "TRACE");
+ assertThat(logLevelsOf(orchestrator.getServer().getCeLogs())).doesNotContain("DEBUG", "TRACE");
+
+ orchestrator.getServer().adminWsClient().post("api/system/change_log_level", "level", "TRACE");
+
+ generateSqlAndEsLogsInWebAndCe();
+
+ // there is hardly DEBUG logs, but we are sure there must be TRACE logs for SQL and ES requests
+ assertThat(logLevelsOf(orchestrator.getServer().getWebLogs())).contains("TRACE");
+ assertThat(logLevelsOf(orchestrator.getServer().getCeLogs())).contains("TRACE");
+
+ // reset log files to empty and level to INFO
+ orchestrator.getServer().adminWsClient().post("api/system/change_log_level", "level", "INFO");
+ FileUtils.write(orchestrator.getServer().getWebLogs(), "");
+ FileUtils.write(orchestrator.getServer().getCeLogs(), "");
+
+ generateSqlAndEsLogsInWebAndCe();
+
+ assertThat(logLevelsOf(orchestrator.getServer().getWebLogs())).doesNotContain("DEBUG", "TRACE");
+ assertThat(logLevelsOf(orchestrator.getServer().getCeLogs())).doesNotContain("DEBUG", "TRACE");
+ }
+
+ private void generateSqlAndEsLogsInWebAndCe() {
+ orchestrator.executeBuild(SonarScanner.create(projectDir("shared/xoo-sample")));
+ ItUtils.newAdminWsClient(orchestrator).issues().search(new SearchWsRequest()
+ .setProjectKeys(Collections.singletonList("sample")));
+ }
+
+ private Collection<String> logLevelsOf(File webLogs) {
+ return Files.linesOf(webLogs, "UTF-8").stream()
+ .filter(str -> str.length() >= 25)
+ .map(str -> str.substring(20, 25))
+ .map(String::trim)
+ .collect(Collectors.toSet());
+ }
+
private void verifyLastAccessLogLine(String login, String path, int status) throws IOException {
assertThat(readLastAccessLog()).endsWith(format("\"%s\" \"GET %s HTTP/1.1\" %d", login, path, status));
}
diff --git a/server/sonar-ce/src/main/java/org/sonar/ce/container/ComputeEngineContainerImpl.java b/server/sonar-ce/src/main/java/org/sonar/ce/container/ComputeEngineContainerImpl.java
index fe082dbbc7a..c671a6a2f87 100644
--- a/server/sonar-ce/src/main/java/org/sonar/ce/container/ComputeEngineContainerImpl.java
+++ b/server/sonar-ce/src/main/java/org/sonar/ce/container/ComputeEngineContainerImpl.java
@@ -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;
@@ -63,7 +64,7 @@ import org.sonar.db.DbClient;
import org.sonar.db.DefaultDatabase;
import org.sonar.db.purge.PurgeProfiler;
import org.sonar.db.version.DatabaseVersion;
-import org.sonar.process.LogbackHelper;
+import org.sonar.process.logging.LogbackHelper;
import org.sonar.process.Props;
import org.sonar.server.component.ComponentCleanerService;
import org.sonar.server.component.ComponentFinder;
@@ -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,
diff --git a/server/sonar-ce/src/main/java/org/sonar/ce/logging/ChangeLogLevelHttpAction.java b/server/sonar-ce/src/main/java/org/sonar/ce/logging/ChangeLogLevelHttpAction.java
index 626eb20c4a6..a84ccc23b85 100644
--- a/server/sonar-ce/src/main/java/org/sonar/ce/logging/ChangeLogLevelHttpAction.java
+++ b/server/sonar-ce/src/main/java/org/sonar/ce/logging/ChangeLogLevelHttpAction.java
@@ -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);
diff --git a/server/sonar-ce/src/test/java/org/sonar/ce/container/ComputeEngineContainerImplTest.java b/server/sonar-ce/src/test/java/org/sonar/ce/container/ComputeEngineContainerImplTest.java
index a31db40f3ee..25c7fa6fe55 100644
--- a/server/sonar-ce/src/test/java/org/sonar/ce/container/ComputeEngineContainerImplTest.java
+++ b/server/sonar-ce/src/test/java/org/sonar/ce/container/ComputeEngineContainerImplTest.java
@@ -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
+ 63 // content of CorePropertyDefinitions
diff --git a/server/sonar-ce/src/test/java/org/sonar/ce/logging/ChangeLogLevelHttpActionTest.java b/server/sonar-ce/src/test/java/org/sonar/ce/logging/ChangeLogLevelHttpActionTest.java
index cce5313c2f5..dc7b79977e8 100644
--- a/server/sonar-ce/src/test/java/org/sonar/ce/logging/ChangeLogLevelHttpActionTest.java
+++ b/server/sonar-ce/src/test/java/org/sonar/ce/logging/ChangeLogLevelHttpActionTest.java
@@ -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
index 00000000000..5a4198363b8
--- /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("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/LogbackHelper.java b/server/sonar-process/src/main/java/org/sonar/process/logging/LogbackHelper.java
index ac9351d8ad2..b9342e4f70f 100644
--- a/server/sonar-process/src/main/java/org/sonar/process/LogbackHelper.java
+++ b/server/sonar-process/src/main/java/org/sonar/process/logging/LogbackHelper.java
@@ -17,7 +17,7 @@
* 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;
+package org.sonar.process.logging;
import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.Logger;
@@ -38,14 +38,15 @@ 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.ProcessProperties;
+import org.sonar.process.Props;
import static java.lang.String.format;
-import static java.util.Objects.requireNonNull;
import static org.slf4j.Logger.ROOT_LOGGER_NAME;
/**
@@ -56,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";
@@ -93,53 +92,61 @@ public class LogbackHelper {
return propagator;
}
- public static final class RootLoggerConfig {
- private final ProcessId processId;
- private final String threadIdFieldPattern;
-
- private RootLoggerConfig(Builder builder) {
- this.processId = requireNonNull(builder.processId);
- this.threadIdFieldPattern = builder.threadIdFieldPattern;
- }
-
- public static Builder newRootLoggerConfigBuilder() {
- return new Builder();
- }
-
- ProcessId getProcessId() {
- return processId;
- }
-
- String getThreadIdFieldPattern() {
- return threadIdFieldPattern;
- }
+ /**
+ * 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;
+ }
- public static final class Builder {
- @CheckForNull
- private ProcessId processId;
- private String threadIdFieldPattern = "";
+ private void applyLevelByProperty(Props props, Logger logger, List<String> properties) {
+ logger.setLevel(resolveLevel(props, properties.stream().toArray(String[]::new)));
+ }
- private Builder() {
- // prevents instantiation outside RootLoggerConfig, use static factory method
+ /**
+ * 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;
+ }
- public Builder setProcessId(ProcessId processId) {
- this.processId = processId;
- return this;
- }
+ private static void applyHardcodedLevel(LoggerContext rootContext, String loggerName, Level newLevel) {
+ rootContext.getLogger(loggerName).setLevel(newLevel);
+ }
- public Builder setThreadIdFieldPattern(String threadIdFieldPattern) {
- this.threadIdFieldPattern = requireNonNull(threadIdFieldPattern, "threadIdFieldPattern can't be null");
- return this;
- }
+ 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));
+ }
- public RootLoggerConfig build() {
- return new RootLoggerConfig(this);
- }
+ 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(LogbackHelper.RootLoggerConfig config) {
+ public String buildLogPattern(RootLoggerConfig config) {
return LOG_FORMAT
.replace(PROCESS_NAME_PLACEHOLDER, config.getProcessId().getKey())
.replace(THREAD_ID_PLACEHOLDER, config.getThreadIdFieldPattern());
@@ -185,7 +192,7 @@ public class LogbackHelper {
return fileAppender;
}
- public FileAppender<ILoggingEvent> newFileAppender(LoggerContext ctx, Props props, LogbackHelper.RootLoggerConfig config, String logPattern) {
+ public FileAppender<ILoggingEvent> newFileAppender(LoggerContext ctx, Props props, RootLoggerConfig config, String logPattern) {
RollingPolicy rollingPolicy = createRollingPolicy(ctx, props, config.getProcessId().getLogFilenamePrefix());
FileAppender<ILoggingEvent> fileAppender = rollingPolicy.createAppender("file_" + config.getProcessId().getLogFilenamePrefix());
fileAppender.setContext(ctx);
@@ -219,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);
@@ -268,57 +243,13 @@ public class LogbackHelper {
private static boolean isAllowed(Level level) {
for (Level allowedRootLogLevel : ALLOWED_ROOT_LOG_LEVELS) {
- if (level == allowedRootLogLevel) {
+ if (level.equals(allowedRootLogLevel)) {
return true;
}
}
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();
}
@@ -457,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/main/java/org/sonar/process/logging/RootLoggerConfig.java b/server/sonar-process/src/main/java/org/sonar/process/logging/RootLoggerConfig.java
new file mode 100644
index 00000000000..d1773e8ab78
--- /dev/null
+++ b/server/sonar-process/src/main/java/org/sonar/process/logging/RootLoggerConfig.java
@@ -0,0 +1,71 @@
+/*
+ * 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 javax.annotation.CheckForNull;
+import org.sonar.process.ProcessId;
+
+import static java.util.Objects.requireNonNull;
+
+public final class RootLoggerConfig {
+ private final ProcessId processId;
+ private final String threadIdFieldPattern;
+
+ private RootLoggerConfig(Builder builder) {
+ this.processId = requireNonNull(builder.processId);
+ this.threadIdFieldPattern = builder.threadIdFieldPattern;
+ }
+
+ public static Builder newRootLoggerConfigBuilder() {
+ return new Builder();
+ }
+
+ ProcessId getProcessId() {
+ return processId;
+ }
+
+ String getThreadIdFieldPattern() {
+ return threadIdFieldPattern;
+ }
+
+ public static final class Builder {
+ @CheckForNull
+ private ProcessId processId;
+ private String threadIdFieldPattern = "";
+
+ private Builder() {
+ // prevents instantiation outside RootLoggerConfig, use static factory method
+ }
+
+ public Builder setProcessId(ProcessId processId) {
+ this.processId = processId;
+ return this;
+ }
+
+ public Builder setThreadIdFieldPattern(String threadIdFieldPattern) {
+ this.threadIdFieldPattern = requireNonNull(threadIdFieldPattern, "threadIdFieldPattern can't be null");
+ return this;
+ }
+
+ public RootLoggerConfig build() {
+ return new RootLoggerConfig(this);
+ }
+ }
+}
diff --git a/server/sonar-process/src/test/java/org/sonar/process/LogbackHelperTest.java b/server/sonar-process/src/test/java/org/sonar/process/LogbackHelperTest.java
deleted file mode 100644
index 3ffc86c64e9..00000000000
--- a/server/sonar-process/src/test/java/org/sonar/process/LogbackHelperTest.java
+++ /dev/null
@@ -1,170 +0,0 @@
-/*
- * 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;
-
-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;
-import ch.qos.logback.core.Appender;
-import ch.qos.logback.core.ConsoleAppender;
-import ch.qos.logback.core.FileAppender;
-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 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.TemporaryFolder;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.junit.Assert.fail;
-
-public class LogbackHelperTest {
-
- @Rule
- public TemporaryFolder temp = new TemporaryFolder();
-
- private Props props = new Props(new Properties());
- private LogbackHelper underTest = new LogbackHelper();
-
- @Before
- public void setUp() throws Exception {
- File dir = temp.newFolder();
- props.set(ProcessProperties.PATH_LOGS, dir.getAbsolutePath());
- }
-
- @AfterClass
- public static void resetLogback() throws Exception {
- new LogbackHelper().resetFromXml("/logback-test.xml");
- }
-
- @Test
- public void getRootContext() {
- assertThat(underTest.getRootContext()).isNotNull();
- }
-
- @Test
- public void enableJulChangePropagation() {
- LoggerContext ctx = underTest.getRootContext();
- int countListeners = ctx.getCopyOfListenerList().size();
-
- LoggerContextListener propagator = underTest.enableJulChangePropagation(ctx);
- assertThat(ctx.getCopyOfListenerList().size()).isEqualTo(countListeners + 1);
-
- ctx.removeListener(propagator);
- }
-
- @Test
- public void newConsoleAppender() {
- LoggerContext ctx = underTest.getRootContext();
- ConsoleAppender<?> appender = underTest.newConsoleAppender(ctx, "MY_APPENDER", "%msg%n");
-
- assertThat(appender.getName()).isEqualTo("MY_APPENDER");
- assertThat(appender.getContext()).isSameAs(ctx);
- assertThat(appender.isStarted()).isTrue();
- assertThat(((PatternLayoutEncoder) appender.getEncoder()).getPattern()).isEqualTo("%msg%n");
- 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();
- LogbackHelper.RollingPolicy policy = underTest.createRollingPolicy(ctx, props, "sonar");
- FileAppender appender = policy.createAppender("SONAR_FILE");
- assertThat(appender).isInstanceOf(RollingFileAppender.class);
-
- // max 5 daily files
- RollingFileAppender fileAppender = (RollingFileAppender) appender;
- TimeBasedRollingPolicy triggeringPolicy = (TimeBasedRollingPolicy) fileAppender.getTriggeringPolicy();
- assertThat(triggeringPolicy.getMaxHistory()).isEqualTo(7);
- assertThat(triggeringPolicy.getFileNamePattern()).endsWith("sonar.%d{yyyy-MM-dd}.log");
- }
-
- @Test
- public void createRollingPolicy_none() {
- props.set("sonar.log.rollingPolicy", "none");
- LoggerContext ctx = underTest.getRootContext();
- LogbackHelper.RollingPolicy policy = underTest.createRollingPolicy(ctx, props, "sonar");
-
- Appender appender = policy.createAppender("SONAR_FILE");
- assertThat(appender).isNotInstanceOf(RollingFileAppender.class).isInstanceOf(FileAppender.class);
- }
-
- @Test
- public void createRollingPolicy_size() {
- props.set("sonar.log.rollingPolicy", "size:1MB");
- props.set("sonar.log.maxFiles", "20");
- LoggerContext ctx = underTest.getRootContext();
- LogbackHelper.RollingPolicy policy = underTest.createRollingPolicy(ctx, props, "sonar");
-
- Appender appender = policy.createAppender("SONAR_FILE");
- assertThat(appender).isInstanceOf(RollingFileAppender.class);
-
- // max 20 files of 1Mb
- RollingFileAppender fileAppender = (RollingFileAppender) appender;
- FixedWindowRollingPolicy rollingPolicy = (FixedWindowRollingPolicy) fileAppender.getRollingPolicy();
- assertThat(rollingPolicy.getMaxIndex()).isEqualTo(20);
- assertThat(rollingPolicy.getFileNamePattern()).endsWith("sonar.%i.log");
- SizeBasedTriggeringPolicy triggeringPolicy = (SizeBasedTriggeringPolicy) fileAppender.getTriggeringPolicy();
- assertThat(triggeringPolicy.getMaxFileSize()).isEqualTo("1MB");
- }
-
- @Test
- public void createRollingPolicy_time() {
- props.set("sonar.log.rollingPolicy", "time:yyyy-MM");
- props.set("sonar.log.maxFiles", "20");
-
- LoggerContext ctx = underTest.getRootContext();
- LogbackHelper.RollingPolicy policy = underTest.createRollingPolicy(ctx, props, "sonar");
-
- RollingFileAppender appender = (RollingFileAppender) policy.createAppender("SONAR_FILE");
-
- // max 5 monthly files
- TimeBasedRollingPolicy triggeringPolicy = (TimeBasedRollingPolicy) appender.getTriggeringPolicy();
- assertThat(triggeringPolicy.getMaxHistory()).isEqualTo(20);
- assertThat(triggeringPolicy.getFileNamePattern()).endsWith("sonar.%d{yyyy-MM}.log");
- }
-
- @Test
- public void createRollingPolicy_fail_if_unknown_policy() {
- props.set("sonar.log.rollingPolicy", "unknown:foo");
- try {
- LoggerContext ctx = underTest.getRootContext();
- underTest.createRollingPolicy(ctx, props, "sonar");
- fail();
- } catch (MessageException e) {
- assertThat(e).hasMessage("Unsupported value for property sonar.log.rollingPolicy: unknown:foo");
- }
- }
-}
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
new file mode 100644
index 00000000000..66d9912de1e
--- /dev/null
+++ b/server/sonar-process/src/test/java/org/sonar/process/logging/LogbackHelperTest.java
@@ -0,0 +1,334 @@
+/*
+ * 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 ch.qos.logback.classic.LoggerContext;
+import ch.qos.logback.classic.encoder.PatternLayoutEncoder;
+import ch.qos.logback.classic.spi.LoggerContextListener;
+import ch.qos.logback.core.Appender;
+import ch.qos.logback.core.ConsoleAppender;
+import ch.qos.logback.core.FileAppender;
+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();
+
+ @Before
+ public void setUp() throws Exception {
+ File dir = temp.newFolder();
+ props.set(ProcessProperties.PATH_LOGS, dir.getAbsolutePath());
+ }
+
+ @AfterClass
+ public static void resetLogback() throws Exception {
+ new LogbackHelper().resetFromXml("/logback-test.xml");
+ }
+
+ @Test
+ public void getRootContext() {
+ assertThat(underTest.getRootContext()).isNotNull();
+ }
+
+ @Test
+ public void enableJulChangePropagation() {
+ LoggerContext ctx = underTest.getRootContext();
+ int countListeners = ctx.getCopyOfListenerList().size();
+
+ LoggerContextListener propagator = underTest.enableJulChangePropagation(ctx);
+ assertThat(ctx.getCopyOfListenerList().size()).isEqualTo(countListeners + 1);
+
+ ctx.removeListener(propagator);
+ }
+
+ @Test
+ public void newConsoleAppender() {
+ LoggerContext ctx = underTest.getRootContext();
+ ConsoleAppender<?> appender = underTest.newConsoleAppender(ctx, "MY_APPENDER", "%msg%n");
+
+ assertThat(appender.getName()).isEqualTo("MY_APPENDER");
+ assertThat(appender.getContext()).isSameAs(ctx);
+ assertThat(appender.isStarted()).isTrue();
+ assertThat(((PatternLayoutEncoder) appender.getEncoder()).getPattern()).isEqualTo("%msg%n");
+ assertThat(appender.getCopyOfAttachedFiltersList()).isEmpty();
+ }
+
+ @Test
+ public void createRollingPolicy_defaults() {
+ LoggerContext ctx = underTest.getRootContext();
+ LogbackHelper.RollingPolicy policy = underTest.createRollingPolicy(ctx, props, "sonar");
+ FileAppender appender = policy.createAppender("SONAR_FILE");
+ assertThat(appender).isInstanceOf(RollingFileAppender.class);
+
+ // max 5 daily files
+ RollingFileAppender fileAppender = (RollingFileAppender) appender;
+ TimeBasedRollingPolicy triggeringPolicy = (TimeBasedRollingPolicy) fileAppender.getTriggeringPolicy();
+ assertThat(triggeringPolicy.getMaxHistory()).isEqualTo(7);
+ assertThat(triggeringPolicy.getFileNamePattern()).endsWith("sonar.%d{yyyy-MM-dd}.log");
+ }
+
+ @Test
+ public void createRollingPolicy_none() {
+ props.set("sonar.log.rollingPolicy", "none");
+ LoggerContext ctx = underTest.getRootContext();
+ LogbackHelper.RollingPolicy policy = underTest.createRollingPolicy(ctx, props, "sonar");
+
+ Appender appender = policy.createAppender("SONAR_FILE");
+ assertThat(appender).isNotInstanceOf(RollingFileAppender.class).isInstanceOf(FileAppender.class);
+ }
+
+ @Test
+ public void createRollingPolicy_size() {
+ props.set("sonar.log.rollingPolicy", "size:1MB");
+ props.set("sonar.log.maxFiles", "20");
+ LoggerContext ctx = underTest.getRootContext();
+ LogbackHelper.RollingPolicy policy = underTest.createRollingPolicy(ctx, props, "sonar");
+
+ Appender appender = policy.createAppender("SONAR_FILE");
+ assertThat(appender).isInstanceOf(RollingFileAppender.class);
+
+ // max 20 files of 1Mb
+ RollingFileAppender fileAppender = (RollingFileAppender) appender;
+ FixedWindowRollingPolicy rollingPolicy = (FixedWindowRollingPolicy) fileAppender.getRollingPolicy();
+ assertThat(rollingPolicy.getMaxIndex()).isEqualTo(20);
+ assertThat(rollingPolicy.getFileNamePattern()).endsWith("sonar.%i.log");
+ SizeBasedTriggeringPolicy triggeringPolicy = (SizeBasedTriggeringPolicy) fileAppender.getTriggeringPolicy();
+ assertThat(triggeringPolicy.getMaxFileSize()).isEqualTo("1MB");
+ }
+
+ @Test
+ public void createRollingPolicy_time() {
+ props.set("sonar.log.rollingPolicy", "time:yyyy-MM");
+ props.set("sonar.log.maxFiles", "20");
+
+ LoggerContext ctx = underTest.getRootContext();
+ LogbackHelper.RollingPolicy policy = underTest.createRollingPolicy(ctx, props, "sonar");
+
+ RollingFileAppender appender = (RollingFileAppender) policy.createAppender("SONAR_FILE");
+
+ // max 5 monthly files
+ TimeBasedRollingPolicy triggeringPolicy = (TimeBasedRollingPolicy) appender.getTriggeringPolicy();
+ assertThat(triggeringPolicy.getMaxHistory()).isEqualTo(20);
+ assertThat(triggeringPolicy.getFileNamePattern()).endsWith("sonar.%d{yyyy-MM}.log");
+ }
+
+ @Test
+ public void createRollingPolicy_fail_if_unknown_policy() {
+ props.set("sonar.log.rollingPolicy", "unknown:foo");
+ try {
+ LoggerContext ctx = underTest.getRootContext();
+ underTest.createRollingPolicy(ctx, props, "sonar");
+ fail();
+ } catch (MessageException e) {
+ 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).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).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).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)
+ .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}
+ };
+ }
+}
diff --git a/server/sonar-search/src/main/java/org/sonar/search/SearchLogging.java b/server/sonar-search/src/main/java/org/sonar/search/SearchLogging.java
index d0216cc89f6..d4ef440abd4 100644
--- a/server/sonar-search/src/main/java/org/sonar/search/SearchLogging.java
+++ b/server/sonar-search/src/main/java/org/sonar/search/SearchLogging.java
@@ -20,11 +20,13 @@
package org.sonar.search;
import ch.qos.logback.classic.LoggerContext;
-import org.sonar.process.LogbackHelper;
+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 static org.sonar.process.LogbackHelper.RootLoggerConfig.newRootLoggerConfigBuilder;
+import static org.sonar.process.logging.RootLoggerConfig.newRootLoggerConfigBuilder;
public class SearchLogging {
@@ -34,12 +36,13 @@ public class SearchLogging {
LoggerContext ctx = helper.getRootContext();
ctx.reset();
- LogbackHelper.RootLoggerConfig config = newRootLoggerConfigBuilder().setProcessId(ProcessId.ELASTICSEARCH).build();
+ RootLoggerConfig config = newRootLoggerConfigBuilder().setProcessId(ProcessId.ELASTICSEARCH).build();
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;
}
diff --git a/server/sonar-search/src/test/java/org/sonar/search/SearchLoggingTest.java b/server/sonar-search/src/test/java/org/sonar/search/SearchLoggingTest.java
index c1a18209704..2017d61a19d 100644
--- a/server/sonar-search/src/test/java/org/sonar/search/SearchLoggingTest.java
+++ b/server/sonar-search/src/test/java/org/sonar/search/SearchLoggingTest.java
@@ -35,7 +35,7 @@ import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.rules.TemporaryFolder;
-import org.sonar.process.LogbackHelper;
+import org.sonar.process.logging.LogbackHelper;
import org.sonar.process.ProcessProperties;
import org.sonar.process.Props;
diff --git a/server/sonar-server/src/main/java/org/sonar/ce/log/CeProcessLogging.java b/server/sonar-server/src/main/java/org/sonar/ce/log/CeProcessLogging.java
index 566aa1be7d0..5729b41fa5b 100644
--- a/server/sonar-server/src/main/java/org/sonar/ce/log/CeProcessLogging.java
+++ b/server/sonar-server/src/main/java/org/sonar/ce/log/CeProcessLogging.java
@@ -19,13 +19,12 @@
*/
package org.sonar.ce.log;
-import org.sonar.process.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.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);
+ JMX_RMI_LOGGER_NAMES.forEach(loggerName -> logLevelConfigBuilder.levelByDomain(loggerName, ProcessId.COMPUTE_ENGINE, LogDomain.JMX));
+ }
+
+ @Override
+ protected void extendConfigure() {
+ // nothing to do
}
}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/app/ServerProcessLogging.java b/server/sonar-server/src/main/java/org/sonar/server/app/ServerProcessLogging.java
index c8c0d9bc6b4..baeaa88d318 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/app/ServerProcessLogging.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/app/ServerProcessLogging.java
@@ -19,34 +19,60 @@
*/
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.LogbackHelper;
+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.server.platform.ServerLogging;
+import org.sonar.process.logging.RootLoggerConfig;
-import static org.sonar.process.LogbackHelper.RootLoggerConfig.newRootLoggerConfigBuilder;
+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) {
@@ -55,16 +81,23 @@ 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) {
- LogbackHelper.RootLoggerConfig config = newRootLoggerConfigBuilder()
+ RootLoggerConfig config = newRootLoggerConfigBuilder()
.setProcessId(processId)
.setThreadIdFieldPattern(threadIdFieldPattern)
.build();
@@ -72,9 +105,6 @@ public abstract class ServerProcessLogging {
helper.configureGlobalFileLog(props, config, logPattern);
helper.configureForSubprocessGobbler(props, logPattern);
-
- helper.configureRootLogLevel(props, processId);
- ServerLogging.configureHardcodedLevels(helper);
}
}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/app/TomcatAccessLog.java b/server/sonar-server/src/main/java/org/sonar/server/app/TomcatAccessLog.java
index 18792c1676f..cb938e59f0e 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/app/TomcatAccessLog.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/app/TomcatAccessLog.java
@@ -26,7 +26,7 @@ import org.apache.catalina.LifecycleListener;
import org.apache.catalina.startup.Tomcat;
import org.sonar.api.utils.log.Logger;
import org.sonar.api.utils.log.Loggers;
-import org.sonar.process.LogbackHelper;
+import org.sonar.process.logging.LogbackHelper;
import org.sonar.process.Props;
class TomcatAccessLog {
diff --git a/server/sonar-server/src/main/java/org/sonar/server/app/WebServerProcessLogging.java b/server/sonar-server/src/main/java/org/sonar/server/app/WebServerProcessLogging.java
index 6b101065be5..149d94be91a 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/app/WebServerProcessLogging.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/app/WebServerProcessLogging.java
@@ -21,11 +21,10 @@ package org.sonar.server.app;
import java.util.logging.LogManager;
import org.slf4j.bridge.SLF4JBridgeHandler;
-import org.sonar.process.LogbackHelper;
+import org.sonar.process.logging.LogLevelConfig;
import org.sonar.process.ProcessId;
-import org.sonar.process.Props;
-import static org.sonar.process.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);
+ 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);
}
}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/ServerLogging.java b/server/sonar-server/src/main/java/org/sonar/server/platform/ServerLogging.java
index 5fe0cbb7d3b..08feeea802c 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/platform/ServerLogging.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/platform/ServerLogging.java
@@ -29,8 +29,9 @@ import org.sonar.api.config.Settings;
import org.sonar.api.server.ServerSide;
import org.sonar.api.utils.log.LoggerLevel;
import org.sonar.api.utils.log.Loggers;
-import org.sonar.process.LogbackHelper;
+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.
*/
@@ -83,11 +67,4 @@ public class ServerLogging {
return new File(settings.getString(ProcessProperties.PATH_LOGS));
}
- /**
- * The file sonar.log, may not exist.
- */
- public File getCurrentLogFile() {
- return new File(getLogsDir(), "sonar.log");
- }
-
}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel1.java b/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel1.java
index d2543fb2b93..7bc4c362646 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel1.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel1.java
@@ -37,9 +37,10 @@ import org.sonar.db.DefaultDatabase;
import org.sonar.db.purge.PurgeProfiler;
import org.sonar.db.semaphore.SemaphoresImpl;
import org.sonar.db.version.DatabaseVersion;
-import org.sonar.process.LogbackHelper;
+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)
diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/ws/ChangeLogLevelAction.java b/server/sonar-server/src/main/java/org/sonar/server/platform/ws/ChangeLogLevelAction.java
index 74917769dab..1ccb94afe40 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/platform/ws/ChangeLogLevelAction.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/platform/ws/ChangeLogLevelAction.java
@@ -25,10 +25,11 @@ 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;
-import static org.sonar.process.LogbackHelper.allowedLogLevels;
+import static org.sonar.process.logging.LogbackHelper.allowedLogLevels;
public class ChangeLogLevelAction implements SystemWsAction {
@@ -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();
}
diff --git a/server/sonar-server/src/test/java/org/sonar/ce/log/CeLoggingTest.java b/server/sonar-server/src/test/java/org/sonar/ce/log/CeLoggingTest.java
index c251892a284..53c28708c2e 100644
--- a/server/sonar-server/src/test/java/org/sonar/ce/log/CeLoggingTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/ce/log/CeLoggingTest.java
@@ -27,7 +27,7 @@ import org.junit.rules.ExpectedException;
import org.mockito.Mockito;
import org.slf4j.MDC;
import org.sonar.ce.queue.CeTask;
-import org.sonar.process.LogbackHelper;
+import org.sonar.process.logging.LogbackHelper;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.when;
diff --git a/server/sonar-server/src/test/java/org/sonar/ce/log/CeProcessLoggingTest.java b/server/sonar-server/src/test/java/org/sonar/ce/log/CeProcessLoggingTest.java
index 41474bed5a9..0058688f056 100644
--- a/server/sonar-server/src/test/java/org/sonar/ce/log/CeProcessLoggingTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/ce/log/CeProcessLoggingTest.java
@@ -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.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;
@@ -247,6 +247,63 @@ public class CeProcessLoggingTest {
}
@Test
+ public void jmx_logger_level_changes_with_global_property_and_is_case_insensitive() {
+ props.set("sonar.log.level", "InFO");
+
+ LoggerContext ctx = underTest.configure(props);
+
+ verifyJmxLogLevel(ctx, Level.INFO);
+ }
+
+ @Test
+ public void jmx_logger_level_changes_with_jmx_property_and_is_case_insensitive() {
+ props.set("sonar.log.level.ce", "TrACe");
+
+ LoggerContext ctx = underTest.configure(props);
+
+ verifyJmxLogLevel(ctx, Level.TRACE);
+ }
+
+ @Test
+ public void jmx_logger_level_changes_with_ce_jmx_property_and_is_case_insensitive() {
+ props.set("sonar.log.level.ce.jmx", "debug");
+
+ LoggerContext ctx = underTest.configure(props);
+
+ verifyJmxLogLevel(ctx, Level.DEBUG);
+ }
+
+ @Test
+ public void jmx_logger_level_is_configured_from_ce_jmx_property_over_ce_property() {
+ props.set("sonar.log.level.ce.jmx", "debug");
+ props.set("sonar.log.level.ce", "TRACE");
+
+ LoggerContext ctx = underTest.configure(props);
+
+ verifyJmxLogLevel(ctx, Level.DEBUG);
+ }
+
+ @Test
+ public void jmx_logger_level_is_configured_from_ce_jmx_property_over_global_property() {
+ props.set("sonar.log.level.ce.jmx", "debug");
+ props.set("sonar.log.level", "TRACE");
+
+ LoggerContext ctx = underTest.configure(props);
+
+ verifyJmxLogLevel(ctx, Level.DEBUG);
+ }
+
+ @Test
+ public void jmx_logger_level_is_configured_from_ce_property_over_global_property() {
+ props.set("sonar.log.level.ce", "debug");
+ props.set("sonar.log.level", "TRACE");
+
+ LoggerContext ctx = underTest.configure(props);
+
+ verifyJmxLogLevel(ctx, Level.DEBUG);
+ }
+
+ @Test
public void root_logger_level_defaults_to_INFO_if_ce_property_has_invalid_value() {
props.set("sonar.log.level.ce", "DodoDouh!");
@@ -271,6 +328,14 @@ public class CeProcessLoggingTest {
}
@Test
+ public void jmx_loggers_level_defaults_to_INFO_if_ce_jmx_property_has_invalid_value() {
+ props.set("sonar.log.level.ce.jmx", "DodoDouh!");
+
+ LoggerContext ctx = underTest.configure(props);
+ verifyJmxLogLevel(ctx, Level.INFO);
+ }
+
+ @Test
public void fail_with_IAE_if_global_property_unsupported_level() {
props.set("sonar.log.level", "ERROR");
@@ -310,6 +375,41 @@ public class CeProcessLoggingTest {
underTest.configure(props);
}
+ @Test
+ public void fail_with_IAE_if_ce_jmx_property_unsupported_level() {
+ props.set("sonar.log.level.ce.jmx", "ERROR");
+
+ expectedException.expect(IllegalArgumentException.class);
+ expectedException.expectMessage("log level ERROR in property sonar.log.level.ce.jmx is not a supported value (allowed levels are [TRACE, DEBUG, INFO])");
+
+ 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);
}
@@ -321,4 +421,32 @@ public class CeProcessLoggingTest {
private void verifyEsLogLevel(LoggerContext ctx, Level expected) {
assertThat(ctx.getLogger("es").getLevel()).isEqualTo(expected);
}
+
+ private void verifyJmxLogLevel(LoggerContext ctx, Level expected) {
+ assertThat(ctx.getLogger("javax.management.remote.timeout").getLevel()).isEqualTo(expected);
+ assertThat(ctx.getLogger("javax.management.remote.misc").getLevel()).isEqualTo(expected);
+ assertThat(ctx.getLogger("javax.management.remote.rmi").getLevel()).isEqualTo(expected);
+ assertThat(ctx.getLogger("javax.management.mbeanserver").getLevel()).isEqualTo(expected);
+ assertThat(ctx.getLogger("sun.rmi.loader").getLevel()).isEqualTo(expected);
+ assertThat(ctx.getLogger("sun.rmi.transport.tcp").getLevel()).isEqualTo(expected);
+ assertThat(ctx.getLogger("sun.rmi.transport.misc").getLevel()).isEqualTo(expected);
+ 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);
+ }
}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/app/ProgrammaticLogbackValveTest.java b/server/sonar-server/src/test/java/org/sonar/server/app/ProgrammaticLogbackValveTest.java
index 3099b2bc34e..01f919098d3 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/app/ProgrammaticLogbackValveTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/app/ProgrammaticLogbackValveTest.java
@@ -22,7 +22,7 @@ package org.sonar.server.app;
import org.apache.catalina.Container;
import org.junit.AfterClass;
import org.junit.Test;
-import org.sonar.process.LogbackHelper;
+import org.sonar.process.logging.LogbackHelper;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
diff --git a/server/sonar-server/src/test/java/org/sonar/server/app/WebServerProcessLoggingTest.java b/server/sonar-server/src/test/java/org/sonar/server/app/WebServerProcessLoggingTest.java
index a81077b6793..02dba16937a 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/app/WebServerProcessLoggingTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/app/WebServerProcessLoggingTest.java
@@ -36,7 +36,7 @@ import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.rules.TemporaryFolder;
-import org.sonar.process.LogbackHelper;
+import org.sonar.process.logging.LogbackHelper;
import org.sonar.process.ProcessProperties;
import org.sonar.process.Props;
@@ -247,6 +247,63 @@ public class WebServerProcessLoggingTest {
}
@Test
+ public void jmx_logger_level_changes_with_global_property_and_is_case_insensitive() {
+ props.set("sonar.log.level", "InFO");
+
+ LoggerContext ctx = underTest.configure(props);
+
+ verifyJmxLogLevel(ctx, Level.INFO);
+ }
+
+ @Test
+ public void jmx_logger_level_changes_with_jmx_property_and_is_case_insensitive() {
+ props.set("sonar.log.level.web", "TrACe");
+
+ LoggerContext ctx = underTest.configure(props);
+
+ verifyJmxLogLevel(ctx, Level.TRACE);
+ }
+
+ @Test
+ public void jmx_logger_level_changes_with_web_jmx_property_and_is_case_insensitive() {
+ props.set("sonar.log.level.web.jmx", "debug");
+
+ LoggerContext ctx = underTest.configure(props);
+
+ verifyJmxLogLevel(ctx, Level.DEBUG);
+ }
+
+ @Test
+ public void jmx_logger_level_is_configured_from_web_jmx_property_over_web_property() {
+ props.set("sonar.log.level.web.jmx", "debug");
+ props.set("sonar.log.level.web", "TRACE");
+
+ LoggerContext ctx = underTest.configure(props);
+
+ verifyJmxLogLevel(ctx, Level.DEBUG);
+ }
+
+ @Test
+ public void jmx_logger_level_is_configured_from_web_jmx_property_over_global_property() {
+ props.set("sonar.log.level.web.jmx", "debug");
+ props.set("sonar.log.level", "TRACE");
+
+ LoggerContext ctx = underTest.configure(props);
+
+ verifyJmxLogLevel(ctx, Level.DEBUG);
+ }
+
+ @Test
+ public void jmx_logger_level_is_configured_from_web_property_over_global_property() {
+ props.set("sonar.log.level.web", "debug");
+ props.set("sonar.log.level", "TRACE");
+
+ LoggerContext ctx = underTest.configure(props);
+
+ verifyJmxLogLevel(ctx, Level.DEBUG);
+ }
+
+ @Test
public void root_logger_level_defaults_to_INFO_if_web_property_has_invalid_value() {
props.set("sonar.log.level.web", "DodoDouh!");
@@ -271,6 +328,14 @@ public class WebServerProcessLoggingTest {
}
@Test
+ public void jmx_loggers_level_defaults_to_INFO_if_wedb_jmx_property_has_invalid_value() {
+ props.set("sonar.log.level.web.jmx", "DodoDouh!");
+
+ LoggerContext ctx = underTest.configure(props);
+ verifyJmxLogLevel(ctx, Level.INFO);
+ }
+
+ @Test
public void fail_with_IAE_if_global_property_unsupported_level() {
props.set("sonar.log.level", "ERROR");
@@ -310,6 +375,41 @@ public class WebServerProcessLoggingTest {
underTest.configure(props);
}
+ @Test
+ public void fail_with_IAE_if_web_jmx_property_unsupported_level() {
+ 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.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);
@@ -323,4 +423,32 @@ public class WebServerProcessLoggingTest {
assertThat(ctx.getLogger("es").getLevel()).isEqualTo(expected);
}
+ private void verifyJmxLogLevel(LoggerContext ctx, Level expected) {
+ assertThat(ctx.getLogger("javax.management.remote.timeout").getLevel()).isEqualTo(expected);
+ assertThat(ctx.getLogger("javax.management.remote.misc").getLevel()).isEqualTo(expected);
+ assertThat(ctx.getLogger("javax.management.remote.rmi").getLevel()).isEqualTo(expected);
+ assertThat(ctx.getLogger("javax.management.mbeanserver").getLevel()).isEqualTo(expected);
+ assertThat(ctx.getLogger("sun.rmi.loader").getLevel()).isEqualTo(expected);
+ assertThat(ctx.getLogger("sun.rmi.transport.tcp").getLevel()).isEqualTo(expected);
+ assertThat(ctx.getLogger("sun.rmi.transport.misc").getLevel()).isEqualTo(expected);
+ 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);
+ }
+
}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/platform/ServerLoggingTest.java b/server/sonar-server/src/test/java/org/sonar/server/platform/ServerLoggingTest.java
index c3b6f9b8d68..9dc813ddaf5 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/platform/ServerLoggingTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/platform/ServerLoggingTest.java
@@ -20,25 +20,32 @@
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.apache.commons.io.FileUtils;
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.LogbackHelper;
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
@@ -68,76 +75,39 @@ public class ServerLoggingTest {
}
@Test
- public void getCurrentLogFile() throws IOException {
- File dir = temp.newFolder();
- File logFile = new File(dir, "sonar.log");
- FileUtils.touch(logFile);
- settings.setProperty(ProcessProperties.PATH_LOGS, dir.getAbsolutePath());
+ @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);
- assertThat(underTest.getCurrentLogFile()).isEqualTo(logFile);
- }
-
- @Test
- public void configureHardcodedLevels() {
- LogbackHelper logbackHelper = mock(LogbackHelper.class);
- ServerLogging.configureHardcodedLevels(logbackHelper);
+ underTest.changeLevel(serverProcessLogging, level);
- verifyHardcodedLevels(logbackHelper);
+ verify(logbackHelper).changeRoot(logLevelConfig, Level.valueOf(level.name()));
}
- @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(LoggerLevel.WARN);
+ @DataProvider
+ public static Object[][] supportedSonarApiLevels() {
+ return new Object[][] {
+ {LoggerLevel.INFO},
+ {LoggerLevel.DEBUG},
+ {LoggerLevel.TRACE}
+ };
}
@Test
- public void changeLevel_throws_IAE_if_level_is_ERROR() {
+ 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 [");
+ expectedException.expectMessage("ERROR log level is not supported (allowed levels are [TRACE, DEBUG, INFO])");
- underTest.changeLevel(LoggerLevel.ERROR);
+ underTest.changeLevel(mock(ServerProcessLogging.class), LoggerLevel.ERROR);
}
@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);
-
- verify(logbackHelper).configureRootLogLevel(Level.DEBUG);
- verifyHardcodedLevels(logbackHelper);
- }
-
- @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);
}
}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/platform/ws/ChangeLogLevelActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/platform/ws/ChangeLogLevelActionTest.java
index bdf1f6abf0f..ca30fa204e7 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/platform/ws/ChangeLogLevelActionTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/platform/ws/ChangeLogLevelActionTest.java
@@ -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);
}
diff --git a/sonar-application/src/main/java/org/sonar/application/AppLogging.java b/sonar-application/src/main/java/org/sonar/application/AppLogging.java
index 69ba954e4f1..284fef1466d 100644
--- a/sonar-application/src/main/java/org/sonar/application/AppLogging.java
+++ b/sonar-application/src/main/java/org/sonar/application/AppLogging.java
@@ -24,12 +24,14 @@ 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.LogbackHelper;
+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 static org.slf4j.Logger.ROOT_LOGGER_NAME;
-import static org.sonar.process.LogbackHelper.RootLoggerConfig.newRootLoggerConfigBuilder;
+import static org.sonar.process.logging.RootLoggerConfig.newRootLoggerConfigBuilder;
import static org.sonar.process.monitor.StreamGobbler.LOGGER_GOBBLER;
/**
@@ -108,7 +110,7 @@ class AppLogging {
private static final String CONSOLE_PLAIN_APPENDER = "CONSOLE";
private static final String APP_CONSOLE_APPENDER = "APP_CONSOLE";
private static final String GOBBLER_PLAIN_CONSOLE = "GOBBLER_CONSOLE";
- private static final LogbackHelper.RootLoggerConfig APP_ROOT_LOGGER_CONFIG = newRootLoggerConfigBuilder()
+ private static final RootLoggerConfig APP_ROOT_LOGGER_CONFIG = newRootLoggerConfigBuilder()
.setProcessId(ProcessId.APP)
.build();
@@ -126,7 +128,7 @@ class AppLogging {
} else {
configureWithWrapperWritingToFile(ctx);
}
- helper.configureRootLogLevel(props, ProcessId.APP);
+ helper.apply(LogLevelConfig.newBuilder().rootLevelFor(ProcessId.APP).build(), props);
return ctx;
}
diff --git a/sonar-application/src/test/java/org/sonar/application/AppLoggingTest.java b/sonar-application/src/test/java/org/sonar/application/AppLoggingTest.java
index c58854b8032..c382d61058f 100644
--- a/sonar-application/src/test/java/org/sonar/application/AppLoggingTest.java
+++ b/sonar-application/src/test/java/org/sonar/application/AppLoggingTest.java
@@ -39,7 +39,7 @@ import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.rules.TemporaryFolder;
-import org.sonar.process.LogbackHelper;
+import org.sonar.process.logging.LogbackHelper;
import org.sonar.process.ProcessProperties;
import org.sonar.process.Props;
diff --git a/sonar-db/src/main/java/org/sonar/db/DefaultDatabase.java b/sonar-db/src/main/java/org/sonar/db/DefaultDatabase.java
index 5a1995161d9..8520edd25a8 100644
--- a/sonar-db/src/main/java/org/sonar/db/DefaultDatabase.java
+++ b/sonar-db/src/main/java/org/sonar/db/DefaultDatabase.java
@@ -40,7 +40,7 @@ import org.sonar.db.dialect.DialectUtils;
import org.sonar.db.profiling.NullConnectionInterceptor;
import org.sonar.db.profiling.ProfiledConnectionInterceptor;
import org.sonar.db.profiling.ProfiledDataSource;
-import org.sonar.process.LogbackHelper;
+import org.sonar.process.logging.LogbackHelper;
import static java.lang.String.format;
diff --git a/sonar-db/src/test/java/org/sonar/db/DefaultDatabaseTest.java b/sonar-db/src/test/java/org/sonar/db/DefaultDatabaseTest.java
index 454dcbac3ac..c130cfa5799 100644
--- a/sonar-db/src/test/java/org/sonar/db/DefaultDatabaseTest.java
+++ b/sonar-db/src/test/java/org/sonar/db/DefaultDatabaseTest.java
@@ -25,7 +25,7 @@ import org.junit.Test;
import org.sonar.api.config.Settings;
import org.sonar.api.config.MapSettings;
import org.sonar.db.dialect.PostgreSql;
-import org.sonar.process.LogbackHelper;
+import org.sonar.process.logging.LogbackHelper;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
diff --git a/sonar-db/src/test/java/org/sonar/db/TestDb.java b/sonar-db/src/test/java/org/sonar/db/TestDb.java
index 4f6d0c0c0b0..c1a99c0e896 100644
--- a/sonar-db/src/test/java/org/sonar/db/TestDb.java
+++ b/sonar-db/src/test/java/org/sonar/db/TestDb.java
@@ -41,7 +41,7 @@ import org.sonar.api.config.MapSettings;
import org.sonar.api.utils.log.Logger;
import org.sonar.api.utils.log.Loggers;
import org.sonar.db.dialect.H2;
-import org.sonar.process.LogbackHelper;
+import org.sonar.process.logging.LogbackHelper;
/**
* This class should be call using @ClassRule in order to create the schema once (ft @Rule is used