diff options
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); |