aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--server/sonar-process-monitor/src/main/java/org/sonar/application/AppLogging.java2
-rw-r--r--server/sonar-process-monitor/src/main/java/org/sonar/application/process/CommandFactoryImpl.java24
-rw-r--r--server/sonar-process-monitor/src/main/java/org/sonar/application/process/EsLogging.java50
-rw-r--r--server/sonar-process-monitor/src/test/java/org/sonar/application/process/EsLoggingTest.java131
-rw-r--r--server/sonar-process/src/main/java/org/sonar/process/logging/AbstractLogHelper.java98
-rw-r--r--server/sonar-process/src/main/java/org/sonar/process/logging/Log4JPropertiesBuilder.java279
-rw-r--r--server/sonar-process/src/main/java/org/sonar/process/logging/LogLevelConfig.java20
-rw-r--r--server/sonar-process/src/main/java/org/sonar/process/logging/LogbackHelper.java77
-rw-r--r--server/sonar-process/src/test/java/org/sonar/process/logging/Log4JPropertiesBuilderTest.java517
-rw-r--r--server/sonar-process/src/test/java/org/sonar/process/logging/LogLevelConfigTest.java32
-rw-r--r--server/sonar-process/src/test/java/org/sonar/process/logging/LogbackHelperTest.java109
-rw-r--r--server/sonar-search/src/main/java/org/sonar/search/SearchLogging.java2
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/app/ServerProcessLogging.java2
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/platform/ServerLoggingTest.java4
14 files changed, 1221 insertions, 126 deletions
diff --git a/server/sonar-process-monitor/src/main/java/org/sonar/application/AppLogging.java b/server/sonar-process-monitor/src/main/java/org/sonar/application/AppLogging.java
index 2755f893c55..5e19aab5945 100644
--- a/server/sonar-process-monitor/src/main/java/org/sonar/application/AppLogging.java
+++ b/server/sonar-process-monitor/src/main/java/org/sonar/application/AppLogging.java
@@ -137,7 +137,7 @@ public class AppLogging {
configureWithWrapperWritingToFile(ctx);
}
helper.apply(
- LogLevelConfig.newBuilder()
+ LogLevelConfig.newBuilder(helper.getRootLoggerName())
.rootLevelFor(ProcessId.APP)
.immutableLevel("com.hazelcast",
Level.toLevel(
diff --git a/server/sonar-process-monitor/src/main/java/org/sonar/application/process/CommandFactoryImpl.java b/server/sonar-process-monitor/src/main/java/org/sonar/application/process/CommandFactoryImpl.java
index 2a2bffde8a9..35d018126c0 100644
--- a/server/sonar-process-monitor/src/main/java/org/sonar/application/process/CommandFactoryImpl.java
+++ b/server/sonar-process-monitor/src/main/java/org/sonar/application/process/CommandFactoryImpl.java
@@ -23,11 +23,9 @@ import java.io.File;
import java.nio.file.Path;
import java.util.Map;
import java.util.Optional;
-import java.util.Properties;
import org.sonar.application.config.AppSettings;
import org.sonar.process.ProcessId;
import org.sonar.process.ProcessProperties;
-import org.sonar.process.logging.LogbackHelper;
import static org.sonar.process.ProcessProperties.HTTPS_PROXY_HOST;
import static org.sonar.process.ProcessProperties.HTTPS_PROXY_PORT;
@@ -71,7 +69,7 @@ public class CommandFactoryImpl implements CommandFactory {
.setWorkDir(executable.getParentFile().getParentFile())
.setExecutable(executable)
.setConfDir(confDir)
- .setLog4j2Properties(buildLog4j2Properties(logDir))
+ .setLog4j2Properties(new EsLogging().createProperties(settings.getProps(), logDir))
.setArguments(this.settings.getProps().rawProperties())
.setClusterName(settingsMap.get("cluster.name"))
.setHost(settingsMap.get("network.host"))
@@ -87,26 +85,6 @@ public class CommandFactoryImpl implements CommandFactory {
return res;
}
- private Properties buildLog4j2Properties(File logDir) {
- // FIXME create a Log4jHelper which shares code with LogbackHelper to build this Properties object + not make LogbackHelper.resolveLevel public + provide correct log format, rolling policy, ...
- String logLevel = LogbackHelper.resolveLevel(settings.getProps(), "sonar.log.level", "sonar.log.level.es").toString();
- Properties log4j2Properties = new Properties();
- log4j2Properties.put("status", "error");
- log4j2Properties.put("appender.rolling.type", "RollingFile");
- log4j2Properties.put("appender.rolling.name", "rolling");
- log4j2Properties.put("appender.rolling.fileName", new File(logDir, "es.log").getAbsolutePath());
- log4j2Properties.put("appender.rolling.layout.type", "PatternLayout");
- log4j2Properties.put("appender.rolling.layout.pattern", "[%d{ISO8601}][%-5p][%-25c{1.}] %marker%.-10000m%n");
- log4j2Properties.put("appender.rolling.filePattern", "${sys:es.logs}-%d{yyyy-MM-dd}.log");
- log4j2Properties.put("appender.rolling.policies.type", "Policies");
- log4j2Properties.put("appender.rolling.policies.time.type", "TimeBasedTriggeringPolicy");
- log4j2Properties.put("appender.rolling.policies.time.interval", "1");
- log4j2Properties.put("appender.rolling.policies.time.modulate", "true");
- log4j2Properties.put("rootLogger.level", logLevel);
- log4j2Properties.put("rootLogger.appenderRef.rolling.ref", "rolling");
- return log4j2Properties;
- }
-
private static String getExecutable() {
if (System.getProperty("os.name").startsWith("Windows")) {
return "elasticsearch/bin/elasticsearch.bat";
diff --git a/server/sonar-process-monitor/src/main/java/org/sonar/application/process/EsLogging.java b/server/sonar-process-monitor/src/main/java/org/sonar/application/process/EsLogging.java
new file mode 100644
index 00000000000..4dadb95c9fa
--- /dev/null
+++ b/server/sonar-process-monitor/src/main/java/org/sonar/application/process/EsLogging.java
@@ -0,0 +1,50 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info 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.application.process;
+
+import ch.qos.logback.classic.Level;
+import java.io.File;
+import java.util.Properties;
+import org.sonar.process.ProcessId;
+import org.sonar.process.Props;
+import org.sonar.process.logging.Log4JPropertiesBuilder;
+import org.sonar.process.logging.LogLevelConfig;
+import org.sonar.process.logging.RootLoggerConfig;
+
+import static org.sonar.process.logging.RootLoggerConfig.newRootLoggerConfigBuilder;
+
+public class EsLogging {
+
+ public Properties createProperties(Props props, File logDir) {
+ Log4JPropertiesBuilder log4JPropertiesBuilder = new Log4JPropertiesBuilder(props);
+ RootLoggerConfig config = newRootLoggerConfigBuilder().setProcessId(ProcessId.ELASTICSEARCH).build();
+ String logPattern = log4JPropertiesBuilder.buildLogPattern(config);
+
+ log4JPropertiesBuilder.internalLogLevel(Level.ERROR);
+ log4JPropertiesBuilder.configureGlobalFileLog(config, logDir, logPattern);
+ log4JPropertiesBuilder.apply(
+ LogLevelConfig.newBuilder(log4JPropertiesBuilder.getRootLoggerName())
+ .rootLevelFor(ProcessId.ELASTICSEARCH)
+ .build());
+
+ return log4JPropertiesBuilder.get();
+ }
+
+}
diff --git a/server/sonar-process-monitor/src/test/java/org/sonar/application/process/EsLoggingTest.java b/server/sonar-process-monitor/src/test/java/org/sonar/application/process/EsLoggingTest.java
new file mode 100644
index 00000000000..a5f55d14886
--- /dev/null
+++ b/server/sonar-process-monitor/src/test/java/org/sonar/application/process/EsLoggingTest.java
@@ -0,0 +1,131 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info 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.application.process;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.HashSet;
+import java.util.Properties;
+import java.util.Set;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.sonar.process.Props;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class EsLoggingTest {
+ @Rule
+ public TemporaryFolder temporaryFolder = new TemporaryFolder();
+
+ private EsLogging underTest = new EsLogging();
+
+ @Test
+ public void createProperties_with_empty_props() throws IOException {
+ File logDir = temporaryFolder.newFolder();
+ Properties properties = underTest.createProperties(newProps(), logDir);
+
+ verifyProperties(properties,
+ "status", "ERROR",
+ "appender.file_es.type", "RollingFile",
+ "appender.file_es.name", "file_es",
+ "appender.file_es.filePattern", new File(logDir, "es.%d{yyyy-MM-dd}.log").getAbsolutePath(),
+ "appender.file_es.fileName", new File(logDir, "es.log").getAbsolutePath(),
+ "appender.file_es.layout.type", "PatternLayout",
+ "appender.file_es.layout.pattern", "%d{yyyy.MM.dd HH:mm:ss} %-5level es[][%logger{1.}] %msg%n",
+ "appender.file_es.policies.type", "Policies",
+ "appender.file_es.policies.time.type", "TimeBasedTriggeringPolicy",
+ "appender.file_es.policies.time.interval", "1",
+ "appender.file_es.policies.time.modulate", "true",
+ "appender.file_es.strategy.type", "DefaultRolloverStrategy",
+ "appender.file_es.strategy.fileIndex", "nomax",
+ "appender.file_es.strategy.action.type", "Delete",
+ "appender.file_es.strategy.action.basepath", logDir.getAbsolutePath(),
+ "appender.file_es.strategy.action.maxDepth", "1",
+ "appender.file_es.strategy.action.condition.type", "IfFileName",
+ "appender.file_es.strategy.action.condition.glob", "es*",
+ "appender.file_es.strategy.action.condition.nested_condition.type", "IfAccumulatedFileCount",
+ "appender.file_es.strategy.action.condition.nested_condition.exceeds", "7",
+ "rootLogger.level", "INFO",
+ "rootLogger.appenderRef.file_es.ref", "file_es");
+ }
+
+ @Test
+ public void createProperties_sets_root_logger_to_INFO_if_no_property_is_set() throws IOException {
+ File logDir = temporaryFolder.newFolder();
+ Properties properties = underTest.createProperties(newProps(), logDir);
+
+ assertThat(properties.getProperty("rootLogger.level")).isEqualTo("INFO");
+ }
+
+ @Test
+ public void createProperties_sets_root_logger_to_global_property_if_set() throws IOException {
+ File logDir = temporaryFolder.newFolder();
+ Properties properties = underTest.createProperties(newProps("sonar.log.level", "TRACE"), logDir);
+
+ assertThat(properties.getProperty("rootLogger.level")).isEqualTo("TRACE");
+ }
+
+ @Test
+ public void createProperties_sets_root_logger_to_process_property_if_set() throws IOException {
+ File logDir = temporaryFolder.newFolder();
+ Properties properties = underTest.createProperties(newProps("sonar.log.level.es", "DEBUG"), logDir);
+
+ assertThat(properties.getProperty("rootLogger.level")).isEqualTo("DEBUG");
+ }
+
+ @Test
+ public void createProperties_sets_root_logger_to_process_property_over_global_property_if_both_set() throws IOException {
+ File logDir = temporaryFolder.newFolder();
+ Properties properties = underTest.createProperties(
+ newProps(
+ "sonar.log.level", "DEBUG",
+ "sonar.log.level.es", "TRACE"),
+ logDir);
+
+ assertThat(properties.getProperty("rootLogger.level")).isEqualTo("TRACE");
+ }
+
+ private static Props newProps(String... propertyKeysAndValues) {
+ assertThat(propertyKeysAndValues.length % 2).describedAs("Number of parameters must be even").isEqualTo(0);
+ Properties properties = new Properties();
+ for (int i = 0; i < propertyKeysAndValues.length; i++) {
+ properties.put(propertyKeysAndValues[i++], propertyKeysAndValues[i]);
+ }
+ return new Props(properties);
+ }
+
+ private void verifyProperties(Properties properties, String... expectedPropertyKeysAndValuesOrdered) {
+ if (expectedPropertyKeysAndValuesOrdered.length == 0) {
+ assertThat(properties.size()).isEqualTo(0);
+ } else {
+ assertThat(expectedPropertyKeysAndValuesOrdered.length % 2).describedAs("Number of parameters must be even").isEqualTo(0);
+ Set<String> keys = new HashSet<>(expectedPropertyKeysAndValuesOrdered.length / 2 + 1);
+ keys.add("status");
+ for (int i = 0; i < expectedPropertyKeysAndValuesOrdered.length; i++) {
+ String key = expectedPropertyKeysAndValuesOrdered[i++];
+ String value = expectedPropertyKeysAndValuesOrdered[i];
+ assertThat(properties.get(key)).describedAs("Unexpected value for property " + key).isEqualTo(value);
+ keys.add(key);
+ }
+ assertThat(properties.keySet()).containsOnly(keys.toArray());
+ }
+ }
+}
diff --git a/server/sonar-process/src/main/java/org/sonar/process/logging/AbstractLogHelper.java b/server/sonar-process/src/main/java/org/sonar/process/logging/AbstractLogHelper.java
new file mode 100644
index 00000000000..a46ca82b9ce
--- /dev/null
+++ b/server/sonar-process/src/main/java/org/sonar/process/logging/AbstractLogHelper.java
@@ -0,0 +1,98 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info 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 javax.annotation.CheckForNull;
+import org.sonar.process.Props;
+
+import static java.lang.String.format;
+
+public abstract class AbstractLogHelper {
+ static final Level[] ALLOWED_ROOT_LOG_LEVELS = new Level[] {Level.TRACE, Level.DEBUG, Level.INFO};
+ static final String SONAR_LOG_LEVEL_PROPERTY = "sonar.log.level";
+ static final String ROLLING_POLICY_PROPERTY = "sonar.log.rollingPolicy";
+ static final String MAX_FILES_PROPERTY = "sonar.log.maxFiles";
+
+ private static final String PROCESS_NAME_PLACEHOLDER = "XXXX";
+ private static final String THREAD_ID_PLACEHOLDER = "ZZZZ";
+ private static final String LOGGER_NAME_PLACEHOLDER = "YYYY";
+ private static final String LOG_FORMAT = "%d{yyyy.MM.dd HH:mm:ss} %-5level " + PROCESS_NAME_PLACEHOLDER + "[" + THREAD_ID_PLACEHOLDER + "][YYYY] %msg%n";
+ private final String loggerNamePattern;
+
+ protected AbstractLogHelper(String loggerNamePattern) {
+ this.loggerNamePattern = loggerNamePattern;
+ }
+
+ public abstract String getRootLoggerName();
+
+ public String buildLogPattern(RootLoggerConfig config) {
+ return LOG_FORMAT
+ .replace(PROCESS_NAME_PLACEHOLDER, config.getProcessId().getKey())
+ .replace(THREAD_ID_PLACEHOLDER, config.getThreadIdFieldPattern())
+ .replace(LOGGER_NAME_PLACEHOLDER, loggerNamePattern);
+ }
+
+ /**
+ * 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}
+ */
+ 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
+ static Level getPropertyValueAsLevel(Props props, String propertyKey) {
+ String value = props.value(propertyKey);
+ if (value == null) {
+ return null;
+ }
+
+ Level level = Level.toLevel(value, Level.INFO);
+ if (!isAllowed(level)) {
+ throw new IllegalArgumentException(format("log level %s in property %s is not a supported value (allowed levels are %s)",
+ level, propertyKey, Arrays.toString(ALLOWED_ROOT_LOG_LEVELS)));
+ }
+ return level;
+ }
+
+ static boolean isAllowed(Level level) {
+ for (Level allowedRootLogLevel : ALLOWED_ROOT_LOG_LEVELS) {
+ if (level.equals(allowedRootLogLevel)) {
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/server/sonar-process/src/main/java/org/sonar/process/logging/Log4JPropertiesBuilder.java b/server/sonar-process/src/main/java/org/sonar/process/logging/Log4JPropertiesBuilder.java
new file mode 100644
index 00000000000..8e83756002c
--- /dev/null
+++ b/server/sonar-process/src/main/java/org/sonar/process/logging/Log4JPropertiesBuilder.java
@@ -0,0 +1,279 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info 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.io.File;
+import java.util.List;
+import java.util.Objects;
+import java.util.Properties;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import org.apache.commons.lang.StringUtils;
+import org.sonar.process.MessageException;
+import org.sonar.process.Props;
+
+import static java.lang.String.format;
+import static java.lang.String.valueOf;
+
+public class Log4JPropertiesBuilder extends AbstractLogHelper {
+ private static final String ROOT_LOGGER_NAME = "rootLogger";
+
+ private final Properties log4j2Properties = new Properties();
+ private final Props props;
+
+ public Log4JPropertiesBuilder(Props props) {
+ super("%logger{1.}");
+ this.props = Objects.requireNonNull(props, "Props can't be null");
+ internalLogLevel(Level.ERROR);
+ }
+
+ @Override
+ public String getRootLoggerName() {
+ return ROOT_LOGGER_NAME;
+ }
+
+ public Properties get() {
+ Properties res = new Properties();
+ res.putAll(log4j2Properties);
+ return res;
+ }
+
+ public void internalLogLevel(Level level) {
+ putProperty("status", level.toString());
+ }
+
+ private void putProperty(String key, String value) {
+ log4j2Properties.put(key, value);
+ }
+
+ /**
+ * Make log4j2 configuration for a process to push all its logs to a log file.
+ * <p>
+ * <ul>
+ * <li>the file's name will use the prefix defined in {@link RootLoggerConfig#getProcessId()#getLogFilenamePrefix()}.</li>
+ * <li>the file will follow the rotation policy defined in property {@link #ROLLING_POLICY_PROPERTY} and
+ * the max number of files defined in property {@link #MAX_FILES_PROPERTY}</li>
+ * <li>the logs will follow the specified log pattern</li>
+ * </ul>
+ * </p>
+ *
+ * @see #buildLogPattern(RootLoggerConfig)
+ */
+ public void configureGlobalFileLog(RootLoggerConfig config, File logDir, String logPattern) {
+ String appenderRef = writeFileAppender(config, logDir, logPattern);
+
+ putProperty(ROOT_LOGGER_NAME + ".appenderRef." + appenderRef + ".ref", appenderRef);
+ }
+
+ private String writeFileAppender(RootLoggerConfig config, File logDir, String logPattern) {
+ String appenderName = "file_" + config.getProcessId().getLogFilenamePrefix();
+ RollingPolicy rollingPolicy = createRollingPolicy(logDir, config.getProcessId().getLogFilenamePrefix());
+ Appender appender = new Appender(appenderName, rollingPolicy, logPattern);
+ appender.writeAppenderProperties();
+ return appender.getAppenderRef();
+ }
+
+ private RollingPolicy createRollingPolicy(File logDir, String filenamePrefix) {
+ String rollingPolicy = props.value(ROLLING_POLICY_PROPERTY, "time:yyyy-MM-dd");
+ int maxFiles = props.valueAsInt(MAX_FILES_PROPERTY, 7);
+
+ if (rollingPolicy.startsWith("time:")) {
+ return new TimeRollingPolicy(filenamePrefix, logDir, maxFiles, StringUtils.substringAfter(rollingPolicy, "time:"));
+ } else if (rollingPolicy.startsWith("size:")) {
+ return new SizeRollingPolicy(filenamePrefix, logDir, maxFiles, StringUtils.substringAfter(rollingPolicy, "size:"));
+ } else if ("none".equals(rollingPolicy)) {
+ return new NoRollingPolicy(filenamePrefix, logDir);
+ } else {
+ throw new MessageException(format("Unsupported value for property %s: %s", ROLLING_POLICY_PROPERTY, rollingPolicy));
+ }
+ }
+
+ public void apply(LogLevelConfig logLevelConfig) {
+ if (!ROOT_LOGGER_NAME.equals(logLevelConfig.getRootLoggerName())) {
+ throw new IllegalArgumentException("Value of LogLevelConfig#rootLoggerName must be \"" + ROOT_LOGGER_NAME + "\"");
+ }
+
+ Level propertyValueAsLevel = getPropertyValueAsLevel(props, SONAR_LOG_LEVEL_PROPERTY);
+ boolean traceGloballyEnabled = propertyValueAsLevel == Level.TRACE;
+
+ List<String> loggerNames = Stream.of(
+ logLevelConfig.getConfiguredByProperties().keySet().stream(),
+ logLevelConfig.getConfiguredByHardcodedLevel().keySet().stream(),
+ logLevelConfig.getOffUnlessTrace().stream().filter(k -> !traceGloballyEnabled))
+ .flatMap(s -> s)
+ .filter(loggerName -> !ROOT_LOGGER_NAME.equals(loggerName))
+ .distinct()
+ .sorted()
+ .collect(Collectors.toList());
+ if (!loggerNames.isEmpty()) {
+ putProperty("loggers", loggerNames.stream().collect(Collectors.joining(",")));
+ }
+
+ logLevelConfig.getConfiguredByProperties().forEach((loggerName, value) -> applyLevelByProperty(props, loggerName, value));
+ logLevelConfig.getConfiguredByHardcodedLevel().forEach(this::applyHardcodedLevel);
+ logLevelConfig.getOffUnlessTrace().stream().filter(k -> !traceGloballyEnabled).forEach(logger -> applyHardcodedLevel(logger, Level.OFF));
+ }
+
+ private void applyLevelByProperty(Props props, String loggerKey, List<String> properties) {
+ putLevel(loggerKey, resolveLevel(props, properties.stream().toArray(String[]::new)));
+ }
+
+ private void applyHardcodedLevel(String loggerName, Level newLevel) {
+ putLevel(loggerName, newLevel);
+ }
+
+ private void putLevel(String loggerName, Level level) {
+ if (loggerName.equals(ROOT_LOGGER_NAME)) {
+ putProperty(loggerName + ".level", level.toString());
+ } else {
+ putProperty("logger." + loggerName + ".name", loggerName);
+ putProperty("logger." + loggerName + ".level", level.toString());
+ }
+ }
+
+ private class Appender {
+ private final String prefix;
+ private final String appenderName;
+ private final RollingPolicy rollingPolicy;
+ private final String logPattern;
+
+ private Appender(String appenderName, RollingPolicy rollingPolicy, String logPattern) {
+ this.prefix = "appender." + appenderName + ".";
+ this.appenderName = appenderName;
+ this.rollingPolicy = rollingPolicy;
+ this.logPattern = logPattern;
+ }
+
+ void writeAppenderProperties() {
+ put("name", appenderName);
+ put("layout.type", "PatternLayout");
+ put("layout.pattern", logPattern);
+
+ rollingPolicy.writePolicy(this.prefix);
+ }
+
+ void put(String key, String value) {
+ Log4JPropertiesBuilder.this.putProperty(this.prefix + key, value);
+ }
+
+ String getAppenderRef() {
+ return appenderName;
+ }
+ }
+
+ private abstract class RollingPolicy {
+ final String filenamePrefix;
+ final File logsDir;
+
+ RollingPolicy(String filenamePrefix, File logsDir) {
+ this.filenamePrefix = filenamePrefix;
+ this.logsDir = logsDir;
+ }
+
+ abstract void writePolicy(String propertyPrefix);
+
+ void writeTypeProperty(String propertyPrefix, String type) {
+ putProperty(propertyPrefix + "type", type);
+ }
+
+ void writeFileNameProperty(String propertyPrefix) {
+ putProperty(propertyPrefix + "fileName", new File(logsDir, filenamePrefix + ".log").getAbsolutePath());
+ }
+
+ void writeFilePatternProperty(String propertyPrefix, String pattern) {
+ putProperty(propertyPrefix + "filePattern", new File(logsDir, filenamePrefix + "." + pattern + ".log").getAbsolutePath());
+ }
+
+ }
+
+ /**
+ * Log files are not rotated, for example when unix command logrotate is in place.
+ */
+ private class NoRollingPolicy extends RollingPolicy {
+ private NoRollingPolicy(String filenamePrefix, File logsDir) {
+ super(filenamePrefix, logsDir);
+ }
+
+ @Override
+ public void writePolicy(String propertyPrefix) {
+ writeTypeProperty(propertyPrefix, "File");
+ writeFileNameProperty(propertyPrefix);
+ }
+ }
+
+ private class TimeRollingPolicy extends RollingPolicy {
+ private final String datePattern;
+ private final int maxFiles;
+
+ private TimeRollingPolicy(String filenamePrefix, File logsDir, int maxFiles, String datePattern) {
+ super(filenamePrefix, logsDir);
+ this.datePattern = datePattern;
+ this.maxFiles = maxFiles;
+ }
+
+ @Override
+ public void writePolicy(String propertyPrefix) {
+ writeTypeProperty(propertyPrefix, "RollingFile");
+ writeFileNameProperty(propertyPrefix);
+ writeFilePatternProperty(propertyPrefix, "%d{" + datePattern + "}");
+
+ putProperty(propertyPrefix + "policies.type", "Policies");
+ putProperty(propertyPrefix + "policies.time.type", "TimeBasedTriggeringPolicy");
+ putProperty(propertyPrefix + "policies.time.interval", "1");
+ putProperty(propertyPrefix + "policies.time.modulate", "true");
+
+ putProperty(propertyPrefix + "strategy.type", "DefaultRolloverStrategy");
+ putProperty(propertyPrefix + "strategy.fileIndex", "nomax");
+ putProperty(propertyPrefix + "strategy.action.type", "Delete");
+ putProperty(propertyPrefix + "strategy.action.basepath", logsDir.getAbsolutePath());
+ putProperty(propertyPrefix + "strategy.action.maxDepth", valueOf(1));
+ putProperty(propertyPrefix + "strategy.action.condition.type", "IfFileName");
+ putProperty(propertyPrefix + "strategy.action.condition.glob", filenamePrefix + "*");
+ putProperty(propertyPrefix + "strategy.action.condition.nested_condition.type", "IfAccumulatedFileCount");
+ putProperty(propertyPrefix + "strategy.action.condition.nested_condition.exceeds", valueOf(maxFiles));
+ }
+ }
+
+ private class SizeRollingPolicy extends RollingPolicy {
+ private final String size;
+ private final int maxFiles;
+
+ private SizeRollingPolicy(String filenamePrefix, File logsDir, int maxFiles, String size) {
+ super(filenamePrefix, logsDir);
+ this.size = size;
+ this.maxFiles = maxFiles;
+ }
+
+ @Override
+ public void writePolicy(String propertyPrefix) {
+ writeTypeProperty(propertyPrefix, "RollingFile");
+ writeFileNameProperty(propertyPrefix);
+ writeFilePatternProperty(propertyPrefix, "%i");
+
+ putProperty(propertyPrefix + "policies.type", "Policies");
+ putProperty(propertyPrefix + "policies.size.type", "SizeBasedTriggeringPolicy");
+ putProperty(propertyPrefix + "policies.size.size", size);
+
+ putProperty(propertyPrefix + "strategy.type", "DefaultRolloverStrategy");
+ putProperty(propertyPrefix + "strategy.max", valueOf(maxFiles));
+ }
+ }
+}
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
index 8e4c813b24f..6203d98b665 100644
--- 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
@@ -32,7 +32,6 @@ 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";
@@ -42,11 +41,13 @@ public final class LogLevelConfig {
private final Map<String, List<String>> configuredByProperties;
private final Map<String, Level> configuredByHardcodedLevel;
private final Set<String> offUnlessTrace;
+ private final String rootLoggerName;
private LogLevelConfig(Builder builder) {
this.configuredByProperties = Collections.unmodifiableMap(builder.configuredByProperties);
this.configuredByHardcodedLevel = Collections.unmodifiableMap(builder.configuredByHardcodedLevel);
this.offUnlessTrace = Collections.unmodifiableSet(builder.offUnlessTrace);
+ this.rootLoggerName = builder.rootLoggerName;
}
Map<String, List<String>> getConfiguredByProperties() {
@@ -57,21 +58,26 @@ public final class LogLevelConfig {
return configuredByHardcodedLevel;
}
- public Set<String> getOffUnlessTrace() {
+ Set<String> getOffUnlessTrace() {
return offUnlessTrace;
}
- public static Builder newBuilder() {
- return new Builder();
+ String getRootLoggerName() {
+ return rootLoggerName;
+ }
+
+ public static Builder newBuilder(String rootLoggerName) {
+ return new Builder(rootLoggerName);
}
public static final class Builder {
private final Map<String, List<String>> configuredByProperties = new HashMap<>();
private final Map<String, Level> configuredByHardcodedLevel = new HashMap<>();
private final Set<String> offUnlessTrace = new HashSet<>();
+ private final String rootLoggerName;
- private Builder() {
- // use static factory method
+ private Builder(String rootLoggerName) {
+ this.rootLoggerName = requireNonNull(rootLoggerName, "rootLoggerName can't be null");
}
/**
@@ -81,7 +87,7 @@ public final class LogLevelConfig {
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()));
+ levelByProperty(rootLoggerName, SONAR_LOG_LEVEL_PROPERTY, SONAR_PROCESS_LOG_LEVEL_PROPERTY.replace(PROCESS_NAME_PLACEHOLDER, processId.getKey()));
return this;
}
diff --git a/server/sonar-process/src/main/java/org/sonar/process/logging/LogbackHelper.java b/server/sonar-process/src/main/java/org/sonar/process/logging/LogbackHelper.java
index a044a931112..5cee5492ddb 100644
--- a/server/sonar-process/src/main/java/org/sonar/process/logging/LogbackHelper.java
+++ b/server/sonar-process/src/main/java/org/sonar/process/logging/LogbackHelper.java
@@ -40,7 +40,6 @@ import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.logging.LogManager;
-import javax.annotation.CheckForNull;
import org.apache.commons.lang.StringUtils;
import org.slf4j.LoggerFactory;
import org.slf4j.bridge.SLF4JBridgeHandler;
@@ -54,21 +53,24 @@ import static org.slf4j.Logger.ROOT_LOGGER_NAME;
/**
* Helps to configure Logback in a programmatic way, without using XML.
*/
-public class LogbackHelper {
+public class LogbackHelper extends AbstractLogHelper {
- private static final String SONAR_LOG_LEVEL_PROPERTY = "sonar.log.level";
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 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";
- private static final Level[] ALLOWED_ROOT_LOG_LEVELS = new Level[] {Level.TRACE, Level.DEBUG, Level.INFO};
+ private static final String LOGBACK_LOGGER_NAME_PATTERN = "%logger{20}";
+
+ public LogbackHelper() {
+ super(LOGBACK_LOGGER_NAME_PATTERN);
+ }
public static Collection<Level> allowedLogLevels() {
return Arrays.asList(ALLOWED_ROOT_LOG_LEVELS);
}
+ @Override
+ public String getRootLoggerName() {
+ return ROOT_LOGGER_NAME;
+ }
+
public LoggerContext getRootContext() {
org.slf4j.Logger logger;
while (!((logger = LoggerFactory.getLogger(ROOT_LOGGER_NAME)) instanceof Logger)) {
@@ -106,6 +108,10 @@ public class LogbackHelper {
* @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) {
+ if (!ROOT_LOGGER_NAME.equals(logLevelConfig.getRootLoggerName())) {
+ throw new IllegalArgumentException("Value of LogLevelConfig#rootLoggerName must be \"" + ROOT_LOGGER_NAME + "\"");
+ }
+
LoggerContext rootContext = getRootContext();
logLevelConfig.getConfiguredByProperties().forEach((key, value) -> applyLevelByProperty(props, rootContext.getLogger(key), value));
logLevelConfig.getConfiguredByHardcodedLevel().forEach((key, value) -> applyHardcodedLevel(rootContext, key, value));
@@ -119,27 +125,6 @@ public class LogbackHelper {
logger.setLevel(resolveLevel(props, properties.stream().toArray(String[]::new)));
}
- /**
- * Resolve a log level reading the value of specified properties.
- * <p>
- * To compute the applied log level the following rules will be followed:
- * <ul>the last property with a defined and valid value in the order of the {@code propertyKeys} argument will be applied</ul>
- * <ul>if there is none, {@link Level#INFO INFO} will be returned</ul>
- * </p>
- *
- * @throws IllegalArgumentException if the value of the specified property is not one of {@link #ALLOWED_ROOT_LOG_LEVELS}
- */
- public static Level resolveLevel(Props props, String... propertyKeys) {
- Level newLevel = Level.INFO;
- for (String propertyKey : propertyKeys) {
- Level level = getPropertyValueAsLevel(props, propertyKey);
- if (level != null) {
- newLevel = level;
- }
- }
- return newLevel;
- }
-
private static void applyHardcodedLevel(LoggerContext rootContext, String loggerName, Level newLevel) {
rootContext.getLogger(loggerName).setLevel(newLevel);
}
@@ -163,12 +148,6 @@ public class LogbackHelper {
}
}
- public String buildLogPattern(RootLoggerConfig config) {
- return LOG_FORMAT
- .replace(PROCESS_NAME_PLACEHOLDER, config.getProcessId().getKey())
- .replace(THREAD_ID_PLACEHOLDER, config.getThreadIdFieldPattern());
- }
-
/**
* Creates a new {@link ConsoleAppender} to {@code System.out} with the specified name and log pattern.
*
@@ -243,30 +222,6 @@ public class LogbackHelper {
return props.valueAsBoolean(ALL_LOGS_TO_CONSOLE_PROPERTY, false);
}
- @CheckForNull
- private static Level getPropertyValueAsLevel(Props props, String propertyKey) {
- String value = props.value(propertyKey);
- if (value == null) {
- return null;
- }
-
- Level level = Level.toLevel(value, Level.INFO);
- if (!isAllowed(level)) {
- throw new IllegalArgumentException(format("log level %s in property %s is not a supported value (allowed levels are %s)",
- level, propertyKey, Arrays.toString(ALLOWED_ROOT_LOG_LEVELS)));
- }
- return level;
- }
-
- private static boolean isAllowed(Level level) {
- for (Level allowedRootLogLevel : ALLOWED_ROOT_LOG_LEVELS) {
- if (level.equals(allowedRootLogLevel)) {
- return true;
- }
- }
- return false;
- }
-
public Level getLoggerLevel(String loggerName) {
return getRootContext().getLogger(loggerName).getLevel();
}
@@ -318,7 +273,7 @@ public class LogbackHelper {
}
/**
- * Log files are not rotated, for example for unix command logrotate is in place.
+ * Log files are not rotated, for example when unix command logrotate is in place.
*/
private static class NoRollingPolicy extends RollingPolicy {
private NoRollingPolicy(Context context, String filenamePrefix, File logsDir, int maxFiles) {
diff --git a/server/sonar-process/src/test/java/org/sonar/process/logging/Log4JPropertiesBuilderTest.java b/server/sonar-process/src/test/java/org/sonar/process/logging/Log4JPropertiesBuilderTest.java
new file mode 100644
index 00000000000..8df33a39273
--- /dev/null
+++ b/server/sonar-process/src/test/java/org/sonar/process/logging/Log4JPropertiesBuilderTest.java
@@ -0,0 +1,517 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info 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 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 java.util.HashSet;
+import java.util.Properties;
+import java.util.Random;
+import java.util.Set;
+import org.apache.commons.lang.RandomStringUtils;
+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.Props;
+
+import static java.lang.String.valueOf;
+import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.sonar.process.logging.RootLoggerConfig.newRootLoggerConfigBuilder;
+
+@RunWith(DataProviderRunner.class)
+public class Log4JPropertiesBuilderTest {
+ private static final String ROLLING_POLICY_PROPERTY = "sonar.log.rollingPolicy";
+ private static final String PROPERTY_MAX_FILES = "sonar.log.maxFiles";
+
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
+ @Rule
+ public TemporaryFolder temporaryFolder = new TemporaryFolder();
+
+ private final RootLoggerConfig esRootLoggerConfig = newRootLoggerConfigBuilder().setProcessId(ProcessId.ELASTICSEARCH).build();
+ private final Log4JPropertiesBuilder underTest = newLog4JPropertiesBuilder();
+
+ @Test
+ public void constructor_fails_with_NPE_if_Props_is_null() {
+ expectedException.expect(NullPointerException.class);
+ expectedException.expectMessage("Props can't be null");
+
+ new Log4JPropertiesBuilder(null);
+ }
+
+ @Test
+ public void constructor_sets_status_to_ERROR() {
+ Properties properties = underTest.get();
+
+ assertThat(properties.getProperty("status")).isEqualTo("ERROR");
+ }
+
+ @Test
+ public void getRootLoggerName_returns_rootLogger() {
+ assertThat(underTest.getRootLoggerName()).isEqualTo("rootLogger");
+ }
+
+ @Test
+ public void get_always_returns_a_new_object() {
+ Properties previous = underTest.get();
+ for (int i = 0; i < 2 + new Random().nextInt(5); i++) {
+ Properties properties = underTest.get();
+ assertThat(properties).isNotSameAs(previous);
+ previous = properties;
+ }
+ }
+
+ @Test
+ public void buildLogPattern_puts_process_key_as_process_id() {
+ String pattern = underTest.buildLogPattern(newRootLoggerConfigBuilder()
+ .setProcessId(ProcessId.ELASTICSEARCH)
+ .build());
+
+ assertThat(pattern).isEqualTo("%d{yyyy.MM.dd HH:mm:ss} %-5level es[][%logger{1.}] %msg%n");
+ }
+
+ @Test
+ public void buildLogPattern_puts_threadIdFieldPattern_from_RootLoggerConfig_non_null() {
+ String threadIdFieldPattern = RandomStringUtils.randomAlphabetic(5);
+
+ String pattern = underTest.buildLogPattern(
+ newRootLoggerConfigBuilder()
+ .setProcessId(ProcessId.APP)
+ .setThreadIdFieldPattern(threadIdFieldPattern)
+ .build());
+
+ assertThat(pattern).isEqualTo("%d{yyyy.MM.dd HH:mm:ss} %-5level app[" + threadIdFieldPattern + "][%logger{1.}] %msg%n");
+ }
+
+ @Test
+ public void buildLogPattern_does_not_put_threadIdFieldPattern_from_RootLoggerConfig_is_null() {
+ String pattern = underTest.buildLogPattern(
+ newRootLoggerConfigBuilder()
+ .setProcessId(ProcessId.COMPUTE_ENGINE)
+ .build());
+
+ assertThat(pattern).isEqualTo("%d{yyyy.MM.dd HH:mm:ss} %-5level ce[][%logger{1.}] %msg%n");
+ }
+
+ @Test
+ public void buildLogPattern_does_not_put_threadIdFieldPattern_from_RootLoggerConfig_is_empty() {
+ String pattern = underTest.buildLogPattern(
+ newRootLoggerConfigBuilder()
+ .setProcessId(ProcessId.WEB_SERVER)
+ .setThreadIdFieldPattern("")
+ .build());
+
+ assertThat(pattern).isEqualTo("%d{yyyy.MM.dd HH:mm:ss} %-5level web[][%logger{1.}] %msg%n");
+ }
+
+ @Test
+ public void configureGlobalFileLog_sets_properties_for_daily_time_rolling_policy_with_max_7_files_for_empty_props() throws IOException {
+ File logDir = temporaryFolder.newFolder();
+ String logPattern = randomAlphanumeric(15);
+ underTest.configureGlobalFileLog(esRootLoggerConfig, logDir, logPattern);
+
+ verifyTimeRollingPolicy(underTest, logDir, logPattern, "yyyy-MM-dd", 7);
+ }
+
+ @Test
+ public void configureGlobalFileLog_throws_MessageException_when_property_is_not_supported() throws IOException {
+ File logDir = temporaryFolder.newFolder();
+ String logPattern = randomAlphanumeric(15);
+ String invalidPropertyValue = randomAlphanumeric(3);
+ Log4JPropertiesBuilder underTest = newLog4JPropertiesBuilder(
+ ROLLING_POLICY_PROPERTY, invalidPropertyValue);
+
+ expectedException.expect(MessageException.class);
+ expectedException.expectMessage("Unsupported value for property " + ROLLING_POLICY_PROPERTY + ": " + invalidPropertyValue);
+
+ underTest.configureGlobalFileLog(esRootLoggerConfig, logDir, logPattern);
+ }
+
+ @Test
+ public void configureGlobalFileLog_sets_properties_for_time_rolling_policy_with_max_7_files_when_property_starts_with_time_colon() throws IOException {
+ File logDir = temporaryFolder.newFolder();
+ String logPattern = randomAlphanumeric(15);
+ String timePattern = randomAlphanumeric(6);
+ Log4JPropertiesBuilder underTest = newLog4JPropertiesBuilder(
+ ROLLING_POLICY_PROPERTY, "time:" + timePattern);
+ underTest.configureGlobalFileLog(esRootLoggerConfig, logDir, logPattern);
+
+ verifyTimeRollingPolicy(underTest, logDir, logPattern, timePattern, 7);
+ }
+
+ @Test
+ public void configureGlobalFileLog_sets_properties_for_time_rolling_policy_when_property_starts_with_time_colon_and_specified_max_number_of_files() throws IOException {
+ File logDir = temporaryFolder.newFolder();
+ String logPattern = randomAlphanumeric(15);
+ String timePattern = randomAlphanumeric(6);
+ int maxFile = 1 + new Random().nextInt(10);
+ Log4JPropertiesBuilder underTest = newLog4JPropertiesBuilder(
+ ROLLING_POLICY_PROPERTY, "time:" + timePattern,
+ PROPERTY_MAX_FILES, valueOf(maxFile));
+ underTest.configureGlobalFileLog(esRootLoggerConfig, logDir, logPattern);
+
+ verifyTimeRollingPolicy(underTest, logDir, logPattern, timePattern, maxFile);
+ }
+
+ @Test
+ public void configureGlobalFileLog_sets_properties_for_size_rolling_policy_with_max_7_files_when_property_starts_with_size_colon() throws IOException {
+ File logDir = temporaryFolder.newFolder();
+ String logPattern = randomAlphanumeric(15);
+ String sizePattern = randomAlphanumeric(6);
+ Log4JPropertiesBuilder underTest = newLog4JPropertiesBuilder(
+ ROLLING_POLICY_PROPERTY, "size:" + sizePattern);
+ underTest.configureGlobalFileLog(esRootLoggerConfig, logDir, logPattern);
+
+ verifySizeRollingPolicy(underTest, logDir, logPattern, sizePattern, 7);
+ }
+
+ @Test
+ public void configureGlobalFileLog_sets_properties_for_size_rolling_policy_when_property_starts_with_size_colon_and_specified_max_number_of_files() throws IOException {
+ File logDir = temporaryFolder.newFolder();
+ String logPattern = randomAlphanumeric(15);
+ String sizePattern = randomAlphanumeric(6);
+ int maxFile = 1 + new Random().nextInt(10);
+ Log4JPropertiesBuilder underTest = newLog4JPropertiesBuilder(
+ ROLLING_POLICY_PROPERTY, "size:" + sizePattern,
+ PROPERTY_MAX_FILES, valueOf(maxFile));
+ underTest.configureGlobalFileLog(esRootLoggerConfig, logDir, logPattern);
+
+ verifySizeRollingPolicy(underTest, logDir, logPattern, sizePattern, maxFile);
+ }
+
+ @Test
+ public void configureGlobalFileLog_sets_properties_for_no_rolling_policy_when_property_is_none() throws IOException {
+ File logDir = temporaryFolder.newFolder();
+ String logPattern = randomAlphanumeric(15);
+ Log4JPropertiesBuilder underTest = newLog4JPropertiesBuilder(
+ ROLLING_POLICY_PROPERTY, "none");
+ underTest.configureGlobalFileLog(esRootLoggerConfig, logDir, logPattern);
+
+ verifyPropertiesForConfigureGlobalFileLog(underTest.get(),
+ "appender.file_es.type", "File",
+ "appender.file_es.name", "file_es",
+ "appender.file_es.fileName", new File(logDir, "es.log").getAbsolutePath(),
+ "appender.file_es.layout.type", "PatternLayout",
+ "appender.file_es.layout.pattern", logPattern,
+ "rootLogger.appenderRef.file_es.ref", "file_es");
+ }
+
+ @Test
+ public void apply_fails_with_IAE_if_LogLevelConfig_does_not_have_rootLoggerName_of_Log4J() {
+ Log4JPropertiesBuilder underTest = newLog4JPropertiesBuilder();
+ LogLevelConfig logLevelConfig = LogLevelConfig.newBuilder(randomAlphanumeric(2)).build();
+
+ expectedException.expect(IllegalArgumentException.class);
+ expectedException.expectMessage("Value of LogLevelConfig#rootLoggerName must be \"rootLogger\"");
+
+ underTest.apply(logLevelConfig);
+ }
+
+ @Test
+ public void apply_fails_with_IAE_if_global_property_has_unsupported_level() {
+ LogLevelConfig config = newLogLevelConfig().rootLevelFor(ProcessId.WEB_SERVER).build();
+
+ Log4JPropertiesBuilder underTest = newLog4JPropertiesBuilder("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);
+ }
+
+ @Test
+ public void apply_fails_with_IAE_if_process_property_has_unsupported_level() {
+ LogLevelConfig config = newLogLevelConfig().rootLevelFor(ProcessId.WEB_SERVER).build();
+
+ Log4JPropertiesBuilder underTest = newLog4JPropertiesBuilder("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);
+ }
+
+ @Test
+ public void apply_sets_root_logger_to_INFO_if_no_property_is_set() {
+ LogLevelConfig config = newLogLevelConfig().rootLevelFor(ProcessId.WEB_SERVER).build();
+
+ underTest.apply(config);
+
+ verifyRootLoggerLevel(underTest, Level.INFO);
+ }
+
+ @Test
+ public void apply_sets_root_logger_to_global_property_if_set() {
+ LogLevelConfig config = newLogLevelConfig().rootLevelFor(ProcessId.WEB_SERVER).build();
+
+ Log4JPropertiesBuilder underTest = newLog4JPropertiesBuilder("sonar.log.level", "TRACE");
+
+ underTest.apply(config);
+
+ verifyRootLoggerLevel(underTest, Level.TRACE);
+ }
+
+ @Test
+ public void apply_sets_root_logger_to_process_property_if_set() {
+ LogLevelConfig config = newLogLevelConfig().rootLevelFor(ProcessId.WEB_SERVER).build();
+
+ Log4JPropertiesBuilder underTest = newLog4JPropertiesBuilder("sonar.log.level.web", "DEBUG");
+
+ underTest.apply(config);
+
+ verifyRootLoggerLevel(underTest, Level.DEBUG);
+ }
+
+ @Test
+ public void apply_sets_root_logger_to_process_property_over_global_property_if_both_set() {
+ LogLevelConfig config = newLogLevelConfig().rootLevelFor(ProcessId.WEB_SERVER).build();
+ Log4JPropertiesBuilder underTest = newLog4JPropertiesBuilder("sonar.log.level", "DEBUG",
+ "sonar.log.level.web", "TRACE");
+
+ underTest.apply(config);
+
+ verifyRootLoggerLevel(underTest, Level.TRACE);
+ }
+
+ @Test
+ public void apply_sets_domain_property_over_process_and_global_property_if_all_set() {
+ LogLevelConfig config = newLogLevelConfig().levelByDomain("foo", ProcessId.WEB_SERVER, LogDomain.ES).build();
+ Log4JPropertiesBuilder underTest = newLog4JPropertiesBuilder(
+ "sonar.log.level", "DEBUG",
+ "sonar.log.level.web", "DEBUG",
+ "sonar.log.level.web.es", "TRACE");
+
+ underTest.apply(config);
+
+ verifyLoggerProperties(underTest.get(), "foo", Level.TRACE);
+ }
+
+ @Test
+ public void apply_sets_domain_property_over_process_property_if_both_set() {
+ LogLevelConfig config = newLogLevelConfig().levelByDomain("foo", ProcessId.WEB_SERVER, LogDomain.ES).build();
+ Log4JPropertiesBuilder underTest = newLog4JPropertiesBuilder(
+ "sonar.log.level.web", "DEBUG",
+ "sonar.log.level.web.es", "TRACE");
+
+ underTest.apply(config);
+
+ verifyLoggerProperties(underTest.get(), "foo", Level.TRACE);
+ }
+
+ @Test
+ public void apply_sets_domain_property_over_global_property_if_both_set() {
+ LogLevelConfig config = newLogLevelConfig().levelByDomain("foo", ProcessId.WEB_SERVER, LogDomain.ES).build();
+ Log4JPropertiesBuilder underTest = newLog4JPropertiesBuilder(
+ "sonar.log.level", "DEBUG",
+ "sonar.log.level.web.es", "TRACE");
+
+ underTest.apply(config);
+
+ verifyLoggerProperties(underTest.get(), "foo", Level.TRACE);
+ }
+
+ @Test
+ public void apply_fails_with_IAE_if_domain_property_has_unsupported_level() {
+ LogLevelConfig config = newLogLevelConfig().levelByDomain("foo", ProcessId.WEB_SERVER, LogDomain.JMX).build();
+
+ Log4JPropertiesBuilder underTest = newLog4JPropertiesBuilder("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);
+ }
+
+ @Test
+ @UseDataProvider("logbackLevels")
+ public void apply_accepts_any_level_as_hardcoded_level(Level level) {
+ LogLevelConfig config = newLogLevelConfig().immutableLevel("bar", level).build();
+
+ underTest.apply(config);
+
+ verifyLoggerProperties(underTest.get(), "bar", level);
+ }
+
+ @Test
+ public void apply_set_level_to_OFF_if_sonar_global_level_is_not_set() {
+ underTest.apply(newLogLevelConfig().offUnlessTrace("fii").build());
+
+ verifyLoggerProperties(underTest.get(), "fii", Level.OFF);
+ }
+
+ @Test
+ public void apply_set_level_to_OFF_if_sonar_global_level_is_INFO() {
+ setLevelToOff(Level.INFO);
+ }
+
+ @Test
+ public void apply_set_level_to_OFF_if_sonar_global_level_is_DEBUG() {
+ setLevelToOff(Level.DEBUG);
+ }
+
+ @Test
+ public void apply_does_not_create_loggers_property_if_only_root_level_is_defined() {
+ LogLevelConfig logLevelConfig = newLogLevelConfig().rootLevelFor(ProcessId.APP).build();
+
+ underTest.apply(logLevelConfig);
+
+ assertThat(underTest.get().getProperty("loggers")).isNull();
+ }
+
+ @Test
+ public void apply_creates_loggers_property_with_logger_names_ordered_but_root() {
+ LogLevelConfig config = newLogLevelConfig()
+ .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)
+ .offUnlessTrace("fii")
+ .build();
+
+ underTest.apply(config);
+
+ assertThat(underTest.get().getProperty("loggers")).isEqualTo("bar,doh,fii,foo,pif");
+ }
+
+ private void setLevelToOff(Level globalLogLevel) {
+ Log4JPropertiesBuilder underTest = newLog4JPropertiesBuilder("sonar.log.level", globalLogLevel.toString());
+
+ underTest.apply(newLogLevelConfig().offUnlessTrace("fii").build());
+
+ verifyLoggerProperties(underTest.get(), "fii", Level.OFF);
+ }
+
+ @Test
+ public void apply_does_not_set_level_if_sonar_global_level_is_TRACE() {
+ Log4JPropertiesBuilder underTest = newLog4JPropertiesBuilder("sonar.log.level", Level.TRACE.toString());
+
+ underTest.apply(newLogLevelConfig().offUnlessTrace("fii").build());
+
+ verifyNoLoggerProperties(underTest.get(), "fii");
+ }
+
+ private static Log4JPropertiesBuilder newLog4JPropertiesBuilder(String... propertyKeysAndValues) {
+ Properties properties = new Properties();
+ assertThat(propertyKeysAndValues.length % 2).describedAs("propertyKeysAndValues must have even length").isEqualTo(0);
+ for (int i = 0; i < propertyKeysAndValues.length; i++) {
+ properties.put(propertyKeysAndValues[i++], propertyKeysAndValues[i]);
+ }
+ return new Log4JPropertiesBuilder(new Props(properties));
+ }
+
+ private void verifyTimeRollingPolicy(Log4JPropertiesBuilder builder, File logDir, String logPattern, String datePattern, int maxFiles) {
+ verifyPropertiesForConfigureGlobalFileLog(builder.get(),
+ "appender.file_es.type", "RollingFile",
+ "appender.file_es.name", "file_es",
+ "appender.file_es.filePattern", new File(logDir, "es.%d{" + datePattern + "}.log").getAbsolutePath(),
+ "appender.file_es.fileName", new File(logDir, "es.log").getAbsolutePath(),
+ "appender.file_es.layout.type", "PatternLayout",
+ "appender.file_es.layout.pattern", logPattern,
+ "appender.file_es.policies.type", "Policies",
+ "appender.file_es.policies.time.type", "TimeBasedTriggeringPolicy",
+ "appender.file_es.policies.time.interval", "1",
+ "appender.file_es.policies.time.modulate", "true",
+ "appender.file_es.strategy.type", "DefaultRolloverStrategy",
+ "appender.file_es.strategy.fileIndex", "nomax",
+ "appender.file_es.strategy.action.type", "Delete",
+ "appender.file_es.strategy.action.basepath", logDir.getAbsolutePath(),
+ "appender.file_es.strategy.action.maxDepth", "1",
+ "appender.file_es.strategy.action.condition.type", "IfFileName",
+ "appender.file_es.strategy.action.condition.glob", "es*",
+ "appender.file_es.strategy.action.condition.nested_condition.type", "IfAccumulatedFileCount",
+ "appender.file_es.strategy.action.condition.nested_condition.exceeds", valueOf(maxFiles),
+ "rootLogger.appenderRef.file_es.ref", "file_es");
+ }
+
+ private void verifySizeRollingPolicy(Log4JPropertiesBuilder builder, File logDir, String logPattern, String sizePattern, int maxFiles) {
+ verifyPropertiesForConfigureGlobalFileLog(builder.get(),
+ "appender.file_es.type", "RollingFile",
+ "appender.file_es.name", "file_es",
+ "appender.file_es.filePattern", new File(logDir, "es.%i.log").getAbsolutePath(),
+ "appender.file_es.fileName", new File(logDir, "es.log").getAbsolutePath(),
+ "appender.file_es.layout.type", "PatternLayout",
+ "appender.file_es.layout.pattern", logPattern,
+ "appender.file_es.policies.type", "Policies",
+ "appender.file_es.policies.size.type", "SizeBasedTriggeringPolicy",
+ "appender.file_es.policies.size.size", sizePattern,
+ "appender.file_es.strategy.type", "DefaultRolloverStrategy",
+ "appender.file_es.strategy.max", valueOf(maxFiles),
+ "rootLogger.appenderRef.file_es.ref", "file_es");
+ }
+
+ private void verifyPropertiesForConfigureGlobalFileLog(Properties properties, String... expectedPropertyKeysAndValuesOrdered) {
+ assertThat(properties.get("status")).isEqualTo("ERROR");
+ if (expectedPropertyKeysAndValuesOrdered.length == 0) {
+ assertThat(properties.size()).isEqualTo(1);
+ } else {
+ assertThat(expectedPropertyKeysAndValuesOrdered.length % 2).describedAs("Number of parameters must be even").isEqualTo(0);
+ Set<String> keys = new HashSet<>(expectedPropertyKeysAndValuesOrdered.length / 2 + 1);
+ keys.add("status");
+ for (int i = 0; i < expectedPropertyKeysAndValuesOrdered.length; i++) {
+ String key = expectedPropertyKeysAndValuesOrdered[i++];
+ String value = expectedPropertyKeysAndValuesOrdered[i];
+ assertThat(properties.get(key)).describedAs("Unexpected value for property " + key).isEqualTo(value);
+ keys.add(key);
+ }
+ assertThat(properties.keySet()).containsOnly(keys.toArray());
+ }
+ }
+
+ private LogLevelConfig.Builder newLogLevelConfig() {
+ return LogLevelConfig.newBuilder("rootLogger");
+ }
+
+ private void verifyLoggerProperties(Properties properties, String loggerName, Level expectedLevel) {
+ assertThat(properties.get("logger." + loggerName + ".name")).isEqualTo(loggerName);
+ assertThat(properties.get("logger." + loggerName + ".level")).isEqualTo(expectedLevel.toString());
+ }
+
+ private void verifyNoLoggerProperties(Properties properties, String loggerName) {
+ assertThat(properties.get("logger." + loggerName + ".name")).isNull();
+ assertThat(properties.get("logger." + loggerName + ".level")).isNull();
+ }
+
+ private void verifyRootLoggerLevel(Log4JPropertiesBuilder underTest, Level expectedLevel) {
+ assertThat(underTest.get().get("rootLogger.level")).isEqualTo(expectedLevel.toString());
+ }
+
+ @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-process/src/test/java/org/sonar/process/logging/LogLevelConfigTest.java b/server/sonar-process/src/test/java/org/sonar/process/logging/LogLevelConfigTest.java
index b307f887b76..78a814e551a 100644
--- 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
@@ -21,6 +21,7 @@ package org.sonar.process.logging;
import ch.qos.logback.classic.Level;
import java.util.Collections;
+import org.apache.commons.lang.RandomStringUtils;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
@@ -28,18 +29,35 @@ 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();
+ private final String rootLoggerName = RandomStringUtils.randomAlphabetic(20);
+ private LogLevelConfig.Builder underTest = newBuilder(rootLoggerName);
+
+ @Test
+ public void newBuilder_throws_NPE_if_rootLoggerName_is_null() {
+ expectedException.expect(NullPointerException.class);
+ expectedException.expectMessage("rootLoggerName can't be null");
+
+ newBuilder(null);
+ }
+
+ @Test
+ public void getLoggerName_returns_name_passed_to_builder() {
+ String rootLoggerName = RandomStringUtils.randomAlphabetic(32);
+
+ LogLevelConfig logLevelConfig = newBuilder(rootLoggerName).build();
+
+ assertThat(logLevelConfig.getRootLoggerName()).isEqualTo(rootLoggerName);
+ }
@Test
public void build_can_create_empty_config_and_returned_maps_are_unmodifiable() {
- LogLevelConfig underTest = newBuilder().build();
+ LogLevelConfig underTest = newBuilder(rootLoggerName).build();
expectUnsupportedOperationException(() -> underTest.getConfiguredByProperties().put("1", Collections.emptyList()));
expectUnsupportedOperationException(() -> underTest.getConfiguredByHardcodedLevel().put("1", Level.ERROR));
@@ -47,10 +65,10 @@ public class LogLevelConfigTest {
@Test
public void builder_rootLevelFor_add_global_and_process_property_in_order_for_root_logger() {
- LogLevelConfig underTest = newBuilder().rootLevelFor(ProcessId.ELASTICSEARCH).build();
+ LogLevelConfig underTest = newBuilder(rootLoggerName).rootLevelFor(ProcessId.ELASTICSEARCH).build();
assertThat(underTest.getConfiguredByProperties()).hasSize(1);
- assertThat(underTest.getConfiguredByProperties().get(ROOT_LOGGER_NAME))
+ assertThat(underTest.getConfiguredByProperties().get(rootLoggerName))
.containsExactly("sonar.log.level", "sonar.log.level.es");
assertThat(underTest.getConfiguredByHardcodedLevel()).hasSize(0);
}
@@ -68,7 +86,7 @@ public class LogLevelConfigTest {
underTest.rootLevelFor(ProcessId.ELASTICSEARCH);
expectedException.expect(IllegalStateException.class);
- expectedException.expectMessage("Configuration by property already registered for " + ROOT_LOGGER_NAME);
+ expectedException.expectMessage("Configuration by property already registered for " + rootLoggerName);
underTest.rootLevelFor(ProcessId.WEB_SERVER);
}
@@ -107,7 +125,7 @@ public class LogLevelConfigTest {
@Test
public void builder_levelByDomain_adds_global_process_and_domain_properties_in_order_for_specified_logger() {
- LogLevelConfig underTest = newBuilder()
+ LogLevelConfig underTest = newBuilder(rootLoggerName)
.levelByDomain("foo", ProcessId.WEB_SERVER, LogDomain.SQL)
.build();
diff --git a/server/sonar-process/src/test/java/org/sonar/process/logging/LogbackHelperTest.java b/server/sonar-process/src/test/java/org/sonar/process/logging/LogbackHelperTest.java
index 60ffdcd7328..2f6643bdb29 100644
--- a/server/sonar-process/src/test/java/org/sonar/process/logging/LogbackHelperTest.java
+++ b/server/sonar-process/src/test/java/org/sonar/process/logging/LogbackHelperTest.java
@@ -40,6 +40,7 @@ import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
+import org.apache.commons.lang.RandomStringUtils;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
@@ -52,9 +53,11 @@ import org.sonar.process.ProcessId;
import org.sonar.process.ProcessProperties;
import org.sonar.process.Props;
+import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.fail;
import static org.slf4j.Logger.ROOT_LOGGER_NAME;
+import static org.sonar.process.logging.RootLoggerConfig.newRootLoggerConfigBuilder;
@RunWith(DataProviderRunner.class)
public class LogbackHelperTest {
@@ -83,6 +86,48 @@ public class LogbackHelperTest {
assertThat(underTest.getRootContext()).isNotNull();
}
+
+ @Test
+ public void buildLogPattern_puts_process_key_as_process_id() {
+ String pattern = underTest.buildLogPattern(newRootLoggerConfigBuilder()
+ .setProcessId(ProcessId.ELASTICSEARCH)
+ .build());
+
+ assertThat(pattern).isEqualTo("%d{yyyy.MM.dd HH:mm:ss} %-5level es[][%logger{20}] %msg%n");
+ }
+
+ @Test
+ public void buildLogPattern_puts_threadIdFieldPattern_from_RootLoggerConfig_non_null() {
+ String threadIdFieldPattern = RandomStringUtils.randomAlphabetic(5);
+ String pattern = underTest.buildLogPattern(
+ newRootLoggerConfigBuilder()
+ .setProcessId(ProcessId.APP)
+ .setThreadIdFieldPattern(threadIdFieldPattern)
+ .build());
+
+ assertThat(pattern).isEqualTo("%d{yyyy.MM.dd HH:mm:ss} %-5level app[" + threadIdFieldPattern + "][%logger{20}] %msg%n");
+ }
+
+ @Test
+ public void buildLogPattern_does_not_put_threadIdFieldPattern_from_RootLoggerConfig_is_null() {
+ String pattern = underTest.buildLogPattern(
+ newRootLoggerConfigBuilder()
+ .setProcessId(ProcessId.COMPUTE_ENGINE)
+ .build());
+
+ assertThat(pattern).isEqualTo("%d{yyyy.MM.dd HH:mm:ss} %-5level ce[][%logger{20}] %msg%n");
+ }
+
+ @Test
+ public void buildLogPattern_does_not_put_threadIdFieldPattern_from_RootLoggerConfig_is_empty() {
+ String pattern = underTest.buildLogPattern(
+ newRootLoggerConfigBuilder()
+ .setProcessId(ProcessId.WEB_SERVER)
+ .setThreadIdFieldPattern("")
+ .build());
+
+ assertThat(pattern).isEqualTo("%d{yyyy.MM.dd HH:mm:ss} %-5level web[][%logger{20}] %msg%n");
+ }
@Test
public void enableJulChangePropagation() {
LoggerContext ctx = underTest.getRootContext();
@@ -97,13 +142,15 @@ public class LogbackHelperTest {
@Test
public void verify_jul_initialization() {
LoggerContext ctx = underTest.getRootContext();
- LogLevelConfig config = LogLevelConfig.newBuilder().levelByDomain(ROOT_LOGGER_NAME, ProcessId.WEB_SERVER, LogDomain.JMX).build();
+ String logbackRootLoggerName = underTest.getRootLoggerName();
+ LogLevelConfig config = LogLevelConfig.newBuilder(logbackRootLoggerName)
+ .levelByDomain(logbackRootLoggerName, ProcessId.WEB_SERVER, LogDomain.JMX).build();
props.set("sonar.log.level.web", "TRACE");
underTest.apply(config, props);
MemoryAppender memoryAppender = new MemoryAppender();
memoryAppender.start();
- underTest.getRootContext().getLogger(ROOT_LOGGER_NAME).addAppender(memoryAppender);
+ underTest.getRootContext().getLogger(logbackRootLoggerName).addAppender(memoryAppender);
java.util.logging.Logger julLogger = java.util.logging.Logger.getLogger("com.ms.sqlserver.jdbc.DTV");
julLogger.finest("Message1");
@@ -137,7 +184,7 @@ public class LogbackHelperTest {
assertThat(memoryAppender.getLogs()).hasSize(6);
memoryAppender.clear();
- ctx.getLogger(ROOT_LOGGER_NAME).setLevel(Level.INFO);
+ ctx.getLogger(logbackRootLoggerName).setLevel(Level.INFO);
julLogger.finest("Message3");
julLogger.finer("Message3");
@@ -238,8 +285,18 @@ public class LogbackHelperTest {
}
@Test
+ public void apply_fails_with_IAE_if_LogLevelConfig_does_not_have_ROOT_LOGGER_NAME_of_LogBack() {
+ LogLevelConfig logLevelConfig = LogLevelConfig.newBuilder(randomAlphanumeric(2)).build();
+
+ expectedException.expect(IllegalArgumentException.class);
+ expectedException.expectMessage("Value of LogLevelConfig#rootLoggerName must be \"ROOT\"");
+
+ underTest.apply(logLevelConfig, props);
+ }
+
+ @Test
public void apply_fails_with_IAE_if_global_property_has_unsupported_level() {
- LogLevelConfig config = LogLevelConfig.newBuilder().rootLevelFor(ProcessId.WEB_SERVER).build();
+ LogLevelConfig config = newLogLevelConfig().rootLevelFor(ProcessId.WEB_SERVER).build();
props.set("sonar.log.level", "ERROR");
@@ -251,7 +308,7 @@ public class LogbackHelperTest {
@Test
public void apply_fails_with_IAE_if_process_property_has_unsupported_level() {
- LogLevelConfig config = LogLevelConfig.newBuilder().rootLevelFor(ProcessId.WEB_SERVER).build();
+ LogLevelConfig config = newLogLevelConfig().rootLevelFor(ProcessId.WEB_SERVER).build();
props.set("sonar.log.level.web", "ERROR");
@@ -263,7 +320,7 @@ public class LogbackHelperTest {
@Test
public void apply_sets_logger_to_INFO_if_no_property_is_set() {
- LogLevelConfig config = LogLevelConfig.newBuilder().rootLevelFor(ProcessId.WEB_SERVER).build();
+ LogLevelConfig config = newLogLevelConfig().rootLevelFor(ProcessId.WEB_SERVER).build();
LoggerContext context = underTest.apply(config, props);
@@ -272,7 +329,7 @@ public class LogbackHelperTest {
@Test
public void apply_sets_logger_to_globlal_property_if_set() {
- LogLevelConfig config = LogLevelConfig.newBuilder().rootLevelFor(ProcessId.WEB_SERVER).build();
+ LogLevelConfig config = newLogLevelConfig().rootLevelFor(ProcessId.WEB_SERVER).build();
props.set("sonar.log.level", "TRACE");
@@ -283,7 +340,7 @@ public class LogbackHelperTest {
@Test
public void apply_sets_logger_to_process_property_if_set() {
- LogLevelConfig config = LogLevelConfig.newBuilder().rootLevelFor(ProcessId.WEB_SERVER).build();
+ LogLevelConfig config = newLogLevelConfig().rootLevelFor(ProcessId.WEB_SERVER).build();
props.set("sonar.log.level.web", "DEBUG");
@@ -294,7 +351,7 @@ public class LogbackHelperTest {
@Test
public void apply_sets_logger_to_process_property_over_global_property_if_both_set() {
- LogLevelConfig config = LogLevelConfig.newBuilder().rootLevelFor(ProcessId.WEB_SERVER).build();
+ LogLevelConfig config = newLogLevelConfig().rootLevelFor(ProcessId.WEB_SERVER).build();
props.set("sonar.log.level", "DEBUG");
props.set("sonar.log.level.web", "TRACE");
@@ -305,7 +362,7 @@ public class LogbackHelperTest {
@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();
+ LogLevelConfig config = newLogLevelConfig().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");
@@ -317,7 +374,7 @@ public class LogbackHelperTest {
@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();
+ LogLevelConfig config = newLogLevelConfig().levelByDomain("foo", ProcessId.WEB_SERVER, LogDomain.ES).build();
props.set("sonar.log.level.web", "DEBUG");
props.set("sonar.log.level.web.es", "TRACE");
@@ -328,7 +385,7 @@ public class LogbackHelperTest {
@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();
+ LogLevelConfig config = newLogLevelConfig().levelByDomain("foo", ProcessId.WEB_SERVER, LogDomain.ES).build();
props.set("sonar.log.level", "DEBUG");
props.set("sonar.log.level.web.es", "TRACE");
@@ -339,7 +396,7 @@ public class LogbackHelperTest {
@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();
+ LogLevelConfig config = newLogLevelConfig().levelByDomain("foo", ProcessId.WEB_SERVER, LogDomain.JMX).build();
props.set("sonar.log.level.web.jmx", "ERROR");
@@ -352,7 +409,7 @@ public class LogbackHelperTest {
@Test
@UseDataProvider("logbackLevels")
public void apply_accepts_any_level_as_hardcoded_level(Level level) {
- LogLevelConfig config = LogLevelConfig.newBuilder().immutableLevel("bar", level).build();
+ LogLevelConfig config = newLogLevelConfig().immutableLevel("bar", level).build();
LoggerContext context = underTest.apply(config, props);
@@ -361,13 +418,13 @@ public class LogbackHelperTest {
@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();
+ LogLevelConfig config = newLogLevelConfig()
+ .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);
@@ -386,7 +443,7 @@ public class LogbackHelperTest {
@Test
public void apply_set_level_to_OFF_if_sonar_global_level_is_not_set() {
- LoggerContext context = underTest.apply(LogLevelConfig.newBuilder().offUnlessTrace("fii").build(), new Props(new Properties()));
+ LoggerContext context = underTest.apply(newLogLevelConfig().offUnlessTrace("fii").build(), new Props(new Properties()));
assertThat(context.getLogger("fii").getLevel()).isEqualTo(Level.OFF);
}
@@ -407,16 +464,20 @@ public class LogbackHelperTest {
properties.setProperty("sonar.log.level", Level.TRACE.toString());
assertThat(underTest.getRootContext().getLogger("fii").getLevel()).isNull();
- LoggerContext context = underTest.apply(LogLevelConfig.newBuilder().offUnlessTrace("fii").build(), new Props(properties));
+ LoggerContext context = underTest.apply(newLogLevelConfig().offUnlessTrace("fii").build(), new Props(properties));
assertThat(context.getLogger("fii").getLevel()).isNull();
}
+ private LogLevelConfig.Builder newLogLevelConfig() {
+ return LogLevelConfig.newBuilder(ROOT_LOGGER_NAME);
+ }
+
private void setLevelToOff(Level globalLogLevel) {
Properties properties = new Properties();
properties.setProperty("sonar.log.level", globalLogLevel.toString());
- LoggerContext context = underTest.apply(LogLevelConfig.newBuilder().offUnlessTrace("fii").build(), new Props(properties));
+ LoggerContext context = underTest.apply(newLogLevelConfig().offUnlessTrace("fii").build(), new Props(properties));
assertThat(context.getLogger("fii").getLevel()).isEqualTo(Level.OFF);
}
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 e87b06307b4..adeab47a2d2 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
@@ -42,7 +42,7 @@ public class SearchLogging {
helper.configureGlobalFileLog(props, config, logPattern);
helper.configureForSubprocessGobbler(props, logPattern);
- helper.apply(LogLevelConfig.newBuilder().rootLevelFor(ProcessId.ELASTICSEARCH).build(), props);
+ helper.apply(LogLevelConfig.newBuilder(helper.getRootLoggerName()).rootLevelFor(ProcessId.ELASTICSEARCH).build(), props);
return ctx;
}
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 ec64e4026f0..caccee2097a 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
@@ -64,7 +64,7 @@ public abstract class ServerProcessLogging {
}
private LogLevelConfig createLogLevelConfiguration(ProcessId processId) {
- LogLevelConfig.Builder builder = LogLevelConfig.newBuilder();
+ LogLevelConfig.Builder builder = LogLevelConfig.newBuilder(helper.getRootLoggerName());
builder.rootLevelFor(processId);
builder.immutableLevel("org.apache.ibatis", Level.WARN);
builder.immutableLevel("java.sql", Level.WARN);
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 92aa85e3fa1..05fec294c8b 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
@@ -25,6 +25,7 @@ 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.lang.RandomStringUtils;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
@@ -52,6 +53,7 @@ public class ServerLoggingTest {
@Rule
public ExpectedException expectedException = ExpectedException.none();
+ private final String rootLoggerName = RandomStringUtils.randomAlphabetic(20);
private LogbackHelper logbackHelper = spy(new LogbackHelper());
private MapSettings settings = new MapSettings();
private ServerLogging underTest = new ServerLogging(logbackHelper, settings.asConfig());
@@ -77,7 +79,7 @@ public class ServerLoggingTest {
@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();
+ LogLevelConfig logLevelConfig = LogLevelConfig.newBuilder(rootLoggerName).build();
when(serverProcessLogging.getLogLevelConfig()).thenReturn(logLevelConfig);
underTest.changeLevel(serverProcessLogging, level);