From 72e202b73cffcedc1ca3cc7311008f2a39006d21 Mon Sep 17 00:00:00 2001 From: =?utf8?q?S=C3=A9bastien=20Lesaint?= Date: Thu, 17 Aug 2017 17:26:14 +0200 Subject: [PATCH] SONAR-9239 configure log4j to log as before in es.log with support for loggers configuration through LogLevelConfig, all three rolling strategies (time, size and none) and almost the same log pattern (shortening of logger names is a big different in log4j) --- .../org/sonar/application/AppLogging.java | 2 +- .../process/CommandFactoryImpl.java | 24 +- .../sonar/application/process/EsLogging.java | 50 ++ .../application/process/EsLoggingTest.java | 131 +++++ .../process/logging/AbstractLogHelper.java | 98 ++++ .../logging/Log4JPropertiesBuilder.java | 279 ++++++++++ .../sonar/process/logging/LogLevelConfig.java | 20 +- .../sonar/process/logging/LogbackHelper.java | 77 +-- .../logging/Log4JPropertiesBuilderTest.java | 517 ++++++++++++++++++ .../process/logging/LogLevelConfigTest.java | 32 +- .../process/logging/LogbackHelperTest.java | 109 +++- .../java/org/sonar/search/SearchLogging.java | 2 +- .../server/app/ServerProcessLogging.java | 2 +- .../server/platform/ServerLoggingTest.java | 4 +- 14 files changed, 1221 insertions(+), 126 deletions(-) create mode 100644 server/sonar-process-monitor/src/main/java/org/sonar/application/process/EsLogging.java create mode 100644 server/sonar-process-monitor/src/test/java/org/sonar/application/process/EsLoggingTest.java create mode 100644 server/sonar-process/src/main/java/org/sonar/process/logging/AbstractLogHelper.java create mode 100644 server/sonar-process/src/main/java/org/sonar/process/logging/Log4JPropertiesBuilder.java create mode 100644 server/sonar-process/src/test/java/org/sonar/process/logging/Log4JPropertiesBuilderTest.java 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 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. + *

+ * To compute the applied log level the following rules will be followed: + *

+ * + *

+ * + * @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. + *

+ *

+ *

+ * + * @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 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 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> configuredByProperties; private final Map configuredByHardcodedLevel; private final Set 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> getConfiguredByProperties() { @@ -57,21 +58,26 @@ public final class LogLevelConfig { return configuredByHardcodedLevel; } - public Set getOffUnlessTrace() { + Set 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> configuredByProperties = new HashMap<>(); private final Map configuredByHardcodedLevel = new HashMap<>(); private final Set 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 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. - *

- * To compute the applied log level the following rules will be followed: - *

    the last property with a defined and valid value in the order of the {@code propertyKeys} argument will be applied
- *
    if there is none, {@link Level#INFO INFO} will be returned
- *

- * - * @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 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"); @@ -237,9 +284,19 @@ 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); -- 2.39.5