From 1f9bb0b02f646739bfbdfe9dc9e8ed15c86e4e52 Mon Sep 17 00:00:00 2001 From: =?utf8?q?S=C3=A9bastien=20Lesaint?= Date: Tue, 29 Nov 2016 13:48:00 +0100 Subject: [PATCH] SONAR-8335 make changing root level change level of all loggers --- .../container/ComputeEngineContainerImpl.java | 2 + .../ce/logging/ChangeLogLevelHttpAction.java | 7 +- .../ComputeEngineContainerImplTest.java | 2 +- .../logging/ChangeLogLevelHttpActionTest.java | 12 +- .../org/sonar/process/logging/LogDomain.java | 36 ++++ .../sonar/process/logging/LogLevelConfig.java | 133 ++++++++++++ .../sonar/process/logging/LogbackHelper.java | 147 +++++--------- .../process/logging/LogLevelConfigTest.java | 191 ++++++++++++++++++ .../process/logging/LogbackHelperTest.java | 179 +++++++++++++++- .../java/org/sonar/search/SearchLogging.java | 4 +- .../org/sonar/ce/log/CeProcessLogging.java | 18 +- .../server/app/ServerProcessLogging.java | 59 ++++-- .../server/app/WebServerProcessLogging.java | 18 +- .../sonar/server/platform/ServerLogging.java | 22 +- .../platformlevel/PlatformLevel1.java | 2 + .../platform/ws/ChangeLogLevelAction.java | 7 +- .../sonar/ce/log/CeProcessLoggingTest.java | 43 +++- .../app/WebServerProcessLoggingTest.java | 41 ++++ .../server/platform/ServerLoggingTest.java | 79 +++----- .../platform/ws/ChangeLogLevelActionTest.java | 8 +- .../org/sonar/application/AppLogging.java | 3 +- 21 files changed, 798 insertions(+), 215 deletions(-) create mode 100644 server/sonar-process/src/main/java/org/sonar/process/logging/LogDomain.java create mode 100644 server/sonar-process/src/main/java/org/sonar/process/logging/LogLevelConfig.java create mode 100644 server/sonar-process/src/test/java/org/sonar/process/logging/LogLevelConfigTest.java diff --git a/server/sonar-ce/src/main/java/org/sonar/ce/container/ComputeEngineContainerImpl.java b/server/sonar-ce/src/main/java/org/sonar/ce/container/ComputeEngineContainerImpl.java index c9711c9de2e..c671a6a2f87 100644 --- a/server/sonar-ce/src/main/java/org/sonar/ce/container/ComputeEngineContainerImpl.java +++ b/server/sonar-ce/src/main/java/org/sonar/ce/container/ComputeEngineContainerImpl.java @@ -44,6 +44,7 @@ import org.sonar.ce.CeHttpModule; import org.sonar.ce.CeQueueModule; import org.sonar.ce.CeTaskCommonsModule; import org.sonar.ce.db.ReadOnlyPropertiesDao; +import org.sonar.ce.log.CeProcessLogging; import org.sonar.ce.platform.ComputeEngineExtensionInstaller; import org.sonar.ce.settings.ProjectSettingsFactory; import org.sonar.ce.user.CeUserSession; @@ -212,6 +213,7 @@ public class ComputeEngineContainerImpl implements ComputeEngineContainer { ThreadLocalSettings.class, new SonarQubeVersion(apiVersion), SonarRuntimeImpl.forSonarQube(ApiVersion.load(System2.INSTANCE), SonarQubeSide.COMPUTE_ENGINE), + CeProcessLogging.class, UuidFactoryImpl.INSTANCE, ClusterImpl.class, LogbackHelper.class, diff --git a/server/sonar-ce/src/main/java/org/sonar/ce/logging/ChangeLogLevelHttpAction.java b/server/sonar-ce/src/main/java/org/sonar/ce/logging/ChangeLogLevelHttpAction.java index 626eb20c4a6..a84ccc23b85 100644 --- a/server/sonar-ce/src/main/java/org/sonar/ce/logging/ChangeLogLevelHttpAction.java +++ b/server/sonar-ce/src/main/java/org/sonar/ce/logging/ChangeLogLevelHttpAction.java @@ -23,6 +23,7 @@ import fi.iki.elonen.NanoHTTPD; import org.sonar.api.utils.log.LoggerLevel; import org.sonar.api.utils.log.Loggers; import org.sonar.ce.httpd.HttpAction; +import org.sonar.ce.log.CeProcessLogging; import org.sonar.db.Database; import org.sonar.server.platform.ServerLogging; @@ -40,10 +41,12 @@ public class ChangeLogLevelHttpAction implements HttpAction { private final ServerLogging logging; private final Database db; + private final CeProcessLogging ceProcessLogging; - public ChangeLogLevelHttpAction(ServerLogging logging, Database db) { + public ChangeLogLevelHttpAction(ServerLogging logging, Database db, CeProcessLogging ceProcessLogging) { this.logging = logging; this.db = db; + this.ceProcessLogging = ceProcessLogging; } @Override @@ -64,7 +67,7 @@ public class ChangeLogLevelHttpAction implements HttpAction { try { LoggerLevel level = LoggerLevel.valueOf(levelStr); db.enableSqlLogging(level.equals(LoggerLevel.TRACE)); - logging.changeLevel(level); + logging.changeLevel(ceProcessLogging, level); return newFixedLengthResponse(OK, MIME_PLAINTEXT, null); } catch (IllegalArgumentException e) { Loggers.get(ChangeLogLevelHttpAction.class).debug("Value '{}' for parameter '{}' is invalid", levelStr, PARAM_LEVEL, e); diff --git a/server/sonar-ce/src/test/java/org/sonar/ce/container/ComputeEngineContainerImplTest.java b/server/sonar-ce/src/test/java/org/sonar/ce/container/ComputeEngineContainerImplTest.java index 35fbd48101b..65f5a0826db 100644 --- a/server/sonar-ce/src/test/java/org/sonar/ce/container/ComputeEngineContainerImplTest.java +++ b/server/sonar-ce/src/test/java/org/sonar/ce/container/ComputeEngineContainerImplTest.java @@ -105,7 +105,7 @@ public class ComputeEngineContainerImplTest { ); assertThat(picoContainer.getParent().getParent().getParent().getComponentAdapters()).hasSize( COMPONENTS_IN_LEVEL_1_AT_CONSTRUCTION - + 26 // level 1 + + 27 // level 1 + 47 // content of DaoModule + 2 // content of EsSearchModule + 62 // content of CorePropertyDefinitions diff --git a/server/sonar-ce/src/test/java/org/sonar/ce/logging/ChangeLogLevelHttpActionTest.java b/server/sonar-ce/src/test/java/org/sonar/ce/logging/ChangeLogLevelHttpActionTest.java index cce5313c2f5..dc7b79977e8 100644 --- a/server/sonar-ce/src/test/java/org/sonar/ce/logging/ChangeLogLevelHttpActionTest.java +++ b/server/sonar-ce/src/test/java/org/sonar/ce/logging/ChangeLogLevelHttpActionTest.java @@ -26,6 +26,7 @@ import org.apache.commons.io.IOUtils; import org.junit.Test; import org.sonar.api.utils.log.LoggerLevel; import org.sonar.ce.httpd.HttpAction; +import org.sonar.ce.log.CeProcessLogging; import org.sonar.db.Database; import org.sonar.server.platform.ServerLogging; @@ -42,7 +43,8 @@ import static org.sonar.ce.httpd.CeHttpUtils.createHttpSession; public class ChangeLogLevelHttpActionTest { private ServerLogging serverLogging = mock(ServerLogging.class); private Database database = mock(Database.class); - private ChangeLogLevelHttpAction underTest = new ChangeLogLevelHttpAction(serverLogging, database); + private CeProcessLogging ceProcessLogging = new CeProcessLogging(); + private ChangeLogLevelHttpAction underTest = new ChangeLogLevelHttpAction(serverLogging, database, ceProcessLogging); @Test public void register_to_path_systemInfo() { @@ -81,7 +83,7 @@ public class ChangeLogLevelHttpActionTest { assertThat(response.getStatus()).isEqualTo(OK); - verify(serverLogging).changeLevel(LoggerLevel.ERROR); + verify(serverLogging).changeLevel(ceProcessLogging, LoggerLevel.ERROR); verify(database).enableSqlLogging(false); } @@ -91,7 +93,7 @@ public class ChangeLogLevelHttpActionTest { assertThat(response.getStatus()).isEqualTo(OK); - verify(serverLogging).changeLevel(LoggerLevel.INFO); + verify(serverLogging).changeLevel(ceProcessLogging, LoggerLevel.INFO); verify(database).enableSqlLogging(false); } @@ -101,7 +103,7 @@ public class ChangeLogLevelHttpActionTest { assertThat(response.getStatus()).isEqualTo(OK); - verify(serverLogging).changeLevel(LoggerLevel.DEBUG); + verify(serverLogging).changeLevel(ceProcessLogging, LoggerLevel.DEBUG); verify(database).enableSqlLogging(false); } @@ -111,7 +113,7 @@ public class ChangeLogLevelHttpActionTest { assertThat(response.getStatus()).isEqualTo(OK); - verify(serverLogging).changeLevel(LoggerLevel.TRACE); + verify(serverLogging).changeLevel(ceProcessLogging, LoggerLevel.TRACE); verify(database).enableSqlLogging(true); } } diff --git a/server/sonar-process/src/main/java/org/sonar/process/logging/LogDomain.java b/server/sonar-process/src/main/java/org/sonar/process/logging/LogDomain.java new file mode 100644 index 00000000000..a4a19a64ca7 --- /dev/null +++ b/server/sonar-process/src/main/java/org/sonar/process/logging/LogDomain.java @@ -0,0 +1,36 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.process.logging; + +public enum LogDomain { + SQL("sql"), + ES_CLIENT("es"), + JMX("jmx"); + + private final String key; + + LogDomain(String key) { + this.key = key; + } + + public String getKey() { + return key; + } +} diff --git a/server/sonar-process/src/main/java/org/sonar/process/logging/LogLevelConfig.java b/server/sonar-process/src/main/java/org/sonar/process/logging/LogLevelConfig.java new file mode 100644 index 00000000000..cbef4a4449b --- /dev/null +++ b/server/sonar-process/src/main/java/org/sonar/process/logging/LogLevelConfig.java @@ -0,0 +1,133 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.process.logging; + +import ch.qos.logback.classic.Level; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import org.sonar.process.ProcessId; + +import static java.util.Objects.requireNonNull; +import static org.slf4j.Logger.ROOT_LOGGER_NAME; + +public final class LogLevelConfig { + private static final String SONAR_LOG_LEVEL_PROPERTY = "sonar.log.level"; + private static final String PROCESS_NAME_PLACEHOLDER = "XXXX"; + private static final String SONAR_PROCESS_LOG_LEVEL_PROPERTY = "sonar.log.level." + PROCESS_NAME_PLACEHOLDER; + + private final Map> configuredByProperties; + private final Map configuredByHardcodedLevel; + + private LogLevelConfig(Builder builder) { + this.configuredByProperties = Collections.unmodifiableMap(builder.configuredByProperties); + this.configuredByHardcodedLevel = Collections.unmodifiableMap(builder.configuredByHardcodedLevel); + } + + Map> getConfiguredByProperties() { + return configuredByProperties; + } + + Map getConfiguredByHardcodedLevel() { + return configuredByHardcodedLevel; + } + + public static Builder newBuilder() { + return new Builder(); + } + + public static final class Builder { + private final Map> configuredByProperties = new HashMap<>(); + private final Map configuredByHardcodedLevel = new HashMap<>(); + + private Builder() { + // use static factory method + } + + /** + * Configure the log level of the root logger to be read from the value of properties {@link #SONAR_LOG_LEVEL_PROPERTY} and + * {@link #SONAR_PROCESS_LOG_LEVEL_PROPERTY}. + */ + public Builder rootLevelFor(ProcessId processId) { + checkProcessId(processId); + + levelByProperty(ROOT_LOGGER_NAME, SONAR_LOG_LEVEL_PROPERTY, SONAR_PROCESS_LOG_LEVEL_PROPERTY.replace(PROCESS_NAME_PLACEHOLDER, processId.getKey())); + return this; + } + + /** + * Configure the log level of the logger with the specified name to be read from the value of properties + * {@code sonar.log.level}, {@code sonar.log.level.[process_name]} and {@code sonar.log.level.[process_name].[LogDomain#getKey()]}. + */ + public Builder levelByDomain(String loggerName, ProcessId processId, LogDomain domain) { + checkLoggerName(loggerName); + checkProcessId(processId); + requireNonNull(domain, "LogDomain can't be null"); + String processProperty = SONAR_PROCESS_LOG_LEVEL_PROPERTY.replace(PROCESS_NAME_PLACEHOLDER, processId.getKey()); + levelByProperty(loggerName, SONAR_LOG_LEVEL_PROPERTY, processProperty, processProperty + "." + domain.getKey()); + return this; + } + + private void levelByProperty(String loggerName, String property, String... otherProperties) { + ensureUniqueConfiguration(loggerName); + configuredByProperties.put(loggerName, Stream.concat(Stream.of(property), Arrays.stream(otherProperties)).collect(Collectors.toList())); + } + + /** + * Configure the log level of the logger with the specified name to be the specified one and it should never be + * changed. + */ + public Builder immutableLevel(String loggerName, Level level) { + checkLoggerName(loggerName); + requireNonNull(level, "level can't be null"); + ensureUniqueConfiguration(loggerName); + configuredByHardcodedLevel.put(loggerName, level); + return this; + } + + private void ensureUniqueConfiguration(String loggerName) { + if (configuredByProperties.containsKey(loggerName)) { + throw new IllegalStateException("Configuration by property already registered for " + loggerName); + } + if (configuredByHardcodedLevel.containsKey(loggerName)) { + throw new IllegalStateException("Configuration hardcoded level already registered for " + loggerName); + } + } + + private static void checkProcessId(ProcessId processId) { + requireNonNull(processId, "ProcessId can't be null"); + } + + private static void checkLoggerName(String loggerName) { + requireNonNull(loggerName, "loggerName can't be null"); + if (loggerName.isEmpty()) { + throw new IllegalArgumentException("loggerName can't be empty"); + } + } + + public LogLevelConfig build() { + return new LogLevelConfig(this); + } + } +} diff --git a/server/sonar-process/src/main/java/org/sonar/process/logging/LogbackHelper.java b/server/sonar-process/src/main/java/org/sonar/process/logging/LogbackHelper.java index 6d7589a618a..5a55bade6d3 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 @@ -38,13 +38,11 @@ import ch.qos.logback.core.rolling.TimeBasedRollingPolicy; import java.io.File; import java.util.Arrays; import java.util.Collection; -import java.util.Collections; -import java.util.Set; +import java.util.List; import javax.annotation.CheckForNull; import org.apache.commons.lang.StringUtils; import org.slf4j.LoggerFactory; import org.sonar.process.MessageException; -import org.sonar.process.ProcessId; import org.sonar.process.ProcessProperties; import org.sonar.process.Props; @@ -59,8 +57,6 @@ public class LogbackHelper { private static final String ALL_LOGS_TO_CONSOLE_PROPERTY = "sonar.log.console"; private static final String PROCESS_NAME_PLACEHOLDER = "XXXX"; private static final String THREAD_ID_PLACEHOLDER = "ZZZZ"; - private static final String SONAR_LOG_LEVEL_PROPERTY = "sonar.log.level"; - private static final String SONAR_PROCESS_LOG_LEVEL_PROPERTY = "sonar.log.level." + PROCESS_NAME_PLACEHOLDER; private static final String ROLLING_POLICY_PROPERTY = "sonar.log.rollingPolicy"; private static final String MAX_FILES_PROPERTY = "sonar.log.maxFiles"; private static final String LOG_FORMAT = "%d{yyyy.MM.dd HH:mm:ss} %-5level " + PROCESS_NAME_PLACEHOLDER + "[" + THREAD_ID_PLACEHOLDER + "][%logger{20}] %msg%n"; @@ -96,6 +92,60 @@ public class LogbackHelper { return propagator; } + /** + * Applies the specified {@link LogLevelConfig} reading the specified {@link Props}. + * + * @throws IllegalArgumentException if the any level specified in a property is not one of {@link #ALLOWED_ROOT_LOG_LEVELS} + */ + public LoggerContext apply(LogLevelConfig logLevelConfig, Props props) { + LoggerContext rootContext = getRootContext(); + logLevelConfig.getConfiguredByProperties().entrySet().forEach(entry -> applyLevelByProperty(props, rootContext.getLogger(entry.getKey()), entry.getValue())); + logLevelConfig.getConfiguredByHardcodedLevel().entrySet().forEach(entry -> applyHardcodedLevel(rootContext, entry.getKey(), entry.getValue())); + return rootContext; + } + + private void applyLevelByProperty(Props props, Logger logger, List properties) { + 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} + */ + private static Level resolveLevel(Props props, String... propertyKeys) { + Level newLevel = Level.INFO; + for (String propertyKey : propertyKeys) { + Level level = getPropertyValueAsLevel(props, propertyKey); + if (level != null) { + newLevel = level; + } + } + return newLevel; + } + + private static void applyHardcodedLevel(LoggerContext rootContext, String loggerName, Level newLevel) { + rootContext.getLogger(loggerName).setLevel(newLevel); + } + + public void changeRoot(LogLevelConfig logLevelConfig, Level newLevel) { + ensureSupportedLevel(newLevel); + LoggerContext rootContext = getRootContext(); + rootContext.getLogger(ROOT_LOGGER_NAME).setLevel(newLevel); + logLevelConfig.getConfiguredByProperties().entrySet().forEach(entry -> rootContext.getLogger(entry.getKey()).setLevel(newLevel)); + } + + private static void ensureSupportedLevel(Level newLevel) { + if (!isAllowed(newLevel)) { + throw new IllegalArgumentException(format("%s log level is not supported (allowed levels are %s)", newLevel, Arrays.toString(ALLOWED_ROOT_LOG_LEVELS))); + } + } + public String buildLogPattern(RootLoggerConfig config) { return LOG_FORMAT .replace(PROCESS_NAME_PLACEHOLDER, config.getProcessId().getKey()) @@ -176,38 +226,6 @@ public class LogbackHelper { return props.valueAsBoolean(ALL_LOGS_TO_CONSOLE_PROPERTY, false); } - /** - * Configure the log level of the root logger reading the value of property {@link #SONAR_LOG_LEVEL_PROPERTY}. - * - * @throws IllegalArgumentException if the value of {@link #SONAR_LOG_LEVEL_PROPERTY} is not one of {@link #ALLOWED_ROOT_LOG_LEVELS} - */ - public Level configureRootLogLevel(Props props, ProcessId processId) { - Level newLevel = resolveLevel(props, SONAR_LOG_LEVEL_PROPERTY, SONAR_PROCESS_LOG_LEVEL_PROPERTY.replace(PROCESS_NAME_PLACEHOLDER, processId.getKey())); - getRootContext().getLogger(ROOT_LOGGER_NAME).setLevel(newLevel); - return newLevel; - } - - /** - * Resolve a log level reading the value of specified properties. - *

- * 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} - */ - private static Level resolveLevel(Props props, String... propertyKeys) { - Level newLevel = Level.INFO; - for (String propertyKey : propertyKeys) { - Level level = getPropertyValueAsLevel(props, propertyKey); - if (level != null) { - newLevel = level; - } - } - return newLevel; - } - @CheckForNull private static Level getPropertyValueAsLevel(Props props, String propertyKey) { String value = props.value(propertyKey); @@ -232,50 +250,6 @@ public class LogbackHelper { return false; } - /** - * Configure the log level of the root logger to the specified level. - * - * @throws IllegalArgumentException if the specified level is not one of {@link #ALLOWED_ROOT_LOG_LEVELS} - */ - public Level configureRootLogLevel(Level newLevel) { - Logger rootLogger = getRootContext().getLogger(ROOT_LOGGER_NAME); - ensureSupportedLevel(newLevel); - rootLogger.setLevel(newLevel); - return newLevel; - } - - private static void ensureSupportedLevel(Level newLevel) { - if (!isAllowed(newLevel)) { - throw new IllegalArgumentException(format("%s log level is not supported (allowed levels are %s)", newLevel, Arrays.toString(ALLOWED_ROOT_LOG_LEVELS))); - } - } - - public Level configureLoggerLogLevelFromDomain(String loggerName, Props props, ProcessId processId, LogDomain domain) { - return configureLoggersLogLevelFromDomain(Collections.singleton(loggerName), props, processId, domain); - } - - public Level configureLoggersLogLevelFromDomain(Set loggerNames, Props props, ProcessId processId, LogDomain domain) { - String processProperty = SONAR_PROCESS_LOG_LEVEL_PROPERTY.replace(PROCESS_NAME_PLACEHOLDER, processId.getKey()); - Level newLevel = resolveLevel(props, SONAR_LOG_LEVEL_PROPERTY, processProperty, processProperty + "." + domain.key); - loggerNames.forEach(loggerName -> { - Logger logger = getRootContext().getLogger(loggerName); - logger.setLevel(newLevel); - }); - return newLevel; - } - - /** - * Configure the log level of the specified logger to specified level. - *

- * Any level is allowed. - *

- */ - public Logger configureLogger(String loggerName, Level level) { - Logger logger = getRootContext().getLogger(loggerName); - logger.setLevel(level); - return logger; - } - public Level getLoggerLevel(String loggerName) { return getRootContext().getLogger(loggerName).getLevel(); } @@ -414,15 +388,4 @@ public class LogbackHelper { } } - public enum LogDomain { - SQL("sql"), - ES_CLIENT("es"), - JMX("jmx"); - - private final String key; - - LogDomain(String key) { - this.key = key; - } - } } diff --git a/server/sonar-process/src/test/java/org/sonar/process/logging/LogLevelConfigTest.java b/server/sonar-process/src/test/java/org/sonar/process/logging/LogLevelConfigTest.java new file mode 100644 index 00000000000..88025203b88 --- /dev/null +++ b/server/sonar-process/src/test/java/org/sonar/process/logging/LogLevelConfigTest.java @@ -0,0 +1,191 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.process.logging; + +import ch.qos.logback.classic.Level; +import java.util.Collections; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.sonar.process.ProcessId; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Fail.fail; +import static org.slf4j.Logger.ROOT_LOGGER_NAME; +import static org.sonar.process.logging.LogLevelConfig.newBuilder; + +public class LogLevelConfigTest { + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + private LogLevelConfig.Builder underTest = newBuilder(); + + @Test + public void build_can_create_empty_config_and_returned_maps_are_unmodifiable() { + LogLevelConfig underTest = newBuilder().build(); + + expectUnsupportedOperationException(() -> underTest.getConfiguredByProperties().put("1", Collections.emptyList())); + expectUnsupportedOperationException(() -> underTest.getConfiguredByHardcodedLevel().put("1", Level.ERROR)); + } + + @Test + public void builder_rootLevelFor_add_global_and_process_property_in_order_for_root_logger() { + LogLevelConfig underTest = newBuilder().rootLevelFor(ProcessId.ELASTICSEARCH).build(); + + assertThat(underTest.getConfiguredByProperties()).hasSize(1); + assertThat(underTest.getConfiguredByProperties().get(ROOT_LOGGER_NAME)) + .containsExactly("sonar.log.level", "sonar.log.level.es"); + assertThat(underTest.getConfiguredByHardcodedLevel()).hasSize(0); + } + + @Test + public void builder_rootLevelFor_fails_with_ProcessId_if_loggerName_is_null() { + expectedException.expect(NullPointerException.class); + expectedException.expectMessage("ProcessId can't be null"); + + underTest.rootLevelFor(null); + } + + @Test + public void builder_rootLevelFor_fails_with_ISE_if_called_twice() { + underTest.rootLevelFor(ProcessId.ELASTICSEARCH); + + expectedException.expect(IllegalStateException.class); + expectedException.expectMessage("Configuration by property already registered for " + ROOT_LOGGER_NAME); + + underTest.rootLevelFor(ProcessId.WEB_SERVER); + } + + @Test + public void builder_levelByDomain_fails_with_NPE_if_loggerName_is_null() { + expectedException.expect(NullPointerException.class); + expectedException.expectMessage("loggerName can't be null"); + + underTest.levelByDomain(null, ProcessId.WEB_SERVER, LogDomain.JMX); + } + + @Test + public void builder_levelByDomain_fails_with_IAE_if_loggerName_is_empty() { + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("loggerName can't be empty"); + + underTest.levelByDomain("", ProcessId.WEB_SERVER, LogDomain.JMX); + } + + @Test + public void builder_levelByDomain_fails_with_NPE_if_ProcessId_is_null() { + expectedException.expect(NullPointerException.class); + expectedException.expectMessage("ProcessId can't be null"); + + underTest.levelByDomain("bar", null, LogDomain.JMX); + } + + @Test + public void builder_levelByDomain_fails_with_NPE_if_LogDomain_is_null() { + expectedException.expect(NullPointerException.class); + expectedException.expectMessage("LogDomain can't be null"); + + underTest.levelByDomain("bar", ProcessId.WEB_SERVER, null); + } + + @Test + public void builder_levelByDomain_adds_global_process_and_domain_properties_in_order_for_specified_logger() { + LogLevelConfig underTest = newBuilder() + .levelByDomain("foo", ProcessId.WEB_SERVER, LogDomain.SQL) + .build(); + + assertThat(underTest.getConfiguredByProperties()).hasSize(1); + assertThat(underTest.getConfiguredByProperties().get("foo")) + .containsExactly("sonar.log.level", "sonar.log.level.web", "sonar.log.level.web.sql"); + assertThat(underTest.getConfiguredByHardcodedLevel()).hasSize(0); + } + + @Test + public void builder_levelByDomain_fails_with_ISE_if_loggerName_has_immutableLevel() { + underTest.immutableLevel("bar", Level.INFO); + + expectedException.expect(IllegalStateException.class); + expectedException.expectMessage("Configuration hardcoded level already registered for bar"); + + underTest.levelByDomain("bar", ProcessId.WEB_SERVER, LogDomain.JMX); + } + + @Test + public void builder_immutableLevel_fails_with_NPE_if_logger_name_is_null() { + expectedException.expect(NullPointerException.class); + expectedException.expectMessage("loggerName can't be null"); + + underTest.immutableLevel(null, Level.ERROR); + } + + @Test + public void builder_immutableLevel_fails_with_IAE_if_logger_name_is_empty() { + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("loggerName can't be empty"); + + underTest.immutableLevel("", Level.ERROR); + } + + @Test + public void builder_immutableLevel_fails_with_NPE_if_level_is_null() { + expectedException.expect(NullPointerException.class); + expectedException.expectMessage("level can't be null"); + + underTest.immutableLevel("foo", null); + } + + @Test + public void builder_immutableLevel_set_specified_level_for_specified_logger() { + LogLevelConfig config = underTest.immutableLevel("bar", Level.INFO).build(); + + assertThat(config.getConfiguredByProperties()).isEmpty(); + assertThat(config.getConfiguredByHardcodedLevel()).hasSize(1); + assertThat(config.getConfiguredByHardcodedLevel().get("bar")).isEqualTo(Level.INFO); + } + + @Test + public void builder_fails_with_ISE_if_immutableLevel_called_twice_for_same_logger() { + underTest.immutableLevel("foo", Level.INFO); + + expectedException.expect(IllegalStateException.class); + expectedException.expectMessage("Configuration hardcoded level already registered for foo"); + + underTest.immutableLevel("foo", Level.DEBUG); + } + + @Test + public void builder_fails_with_ISE_if_logger_has_domain_config() { + underTest.levelByDomain("pop", ProcessId.WEB_SERVER, LogDomain.JMX); + + expectedException.expect(IllegalStateException.class); + expectedException.expectMessage("Configuration by property already registered for pop"); + + underTest.immutableLevel("pop", Level.DEBUG); + } + + private static void expectUnsupportedOperationException(Runnable runnable) { + try { + runnable.run(); + fail("a UnsupportedOperationException should have been raised"); + } catch (Exception e) { + assertThat(e).isInstanceOf(UnsupportedOperationException.class); + } + } +} diff --git a/server/sonar-process/src/test/java/org/sonar/process/logging/LogbackHelperTest.java b/server/sonar-process/src/test/java/org/sonar/process/logging/LogbackHelperTest.java index 1037de3f863..023d8494875 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 @@ -20,7 +20,6 @@ package org.sonar.process.logging; import ch.qos.logback.classic.Level; -import ch.qos.logback.classic.Logger; import ch.qos.logback.classic.LoggerContext; import ch.qos.logback.classic.encoder.PatternLayoutEncoder; import ch.qos.logback.classic.spi.LoggerContextListener; @@ -31,24 +30,34 @@ import ch.qos.logback.core.rolling.FixedWindowRollingPolicy; import ch.qos.logback.core.rolling.RollingFileAppender; import ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy; import ch.qos.logback.core.rolling.TimeBasedRollingPolicy; +import com.tngtech.java.junit.dataprovider.DataProvider; +import com.tngtech.java.junit.dataprovider.DataProviderRunner; +import com.tngtech.java.junit.dataprovider.UseDataProvider; import java.io.File; import java.util.Properties; import org.junit.AfterClass; import org.junit.Before; import org.junit.Rule; import org.junit.Test; +import org.junit.rules.ExpectedException; import org.junit.rules.TemporaryFolder; +import org.junit.runner.RunWith; import org.sonar.process.MessageException; +import org.sonar.process.ProcessId; import org.sonar.process.ProcessProperties; import org.sonar.process.Props; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.fail; +import static org.slf4j.Logger.ROOT_LOGGER_NAME; +@RunWith(DataProviderRunner.class) public class LogbackHelperTest { @Rule public TemporaryFolder temp = new TemporaryFolder(); + @Rule + public ExpectedException expectedException = ExpectedException.none(); private Props props = new Props(new Properties()); private LogbackHelper underTest = new LogbackHelper(); @@ -92,14 +101,6 @@ public class LogbackHelperTest { assertThat(appender.getCopyOfAttachedFiltersList()).isEmpty(); } - @Test - public void configureLogger() { - Logger logger = underTest.configureLogger("my_logger", Level.WARN); - - assertThat(logger.getLevel()).isEqualTo(Level.WARN); - assertThat(logger.getName()).isEqualTo("my_logger"); - } - @Test public void createRollingPolicy_defaults() { LoggerContext ctx = underTest.getRootContext(); @@ -170,4 +171,164 @@ public class LogbackHelperTest { assertThat(e).hasMessage("Unsupported value for property sonar.log.rollingPolicy: unknown:foo"); } } + + @Test + public void apply_fails_with_IAE_if_global_property_has_unsupported_level() { + LogLevelConfig config = LogLevelConfig.newBuilder().rootLevelFor(ProcessId.WEB_SERVER).build(); + + props.set("sonar.log.level", "ERROR"); + + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("log level ERROR in property sonar.log.level is not a supported value (allowed levels are [TRACE, DEBUG, INFO])"); + + underTest.apply(config, props); + } + + @Test + public void apply_fails_with_IAE_if_process_property_has_unsupported_level() { + LogLevelConfig config = LogLevelConfig.newBuilder().rootLevelFor(ProcessId.WEB_SERVER).build(); + + props.set("sonar.log.level.web", "ERROR"); + + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("log level ERROR in property sonar.log.level.web is not a supported value (allowed levels are [TRACE, DEBUG, INFO])"); + + underTest.apply(config, props); + } + + @Test + public void apply_sets_logger_to_INFO_if_no_property_is_set() { + LogLevelConfig config = LogLevelConfig.newBuilder().rootLevelFor(ProcessId.WEB_SERVER).build(); + + LoggerContext context = underTest.apply(config, props); + + assertThat(context.getLogger(ROOT_LOGGER_NAME).getLevel()).isEqualTo(Level.INFO); + } + + @Test + public void apply_sets_logger_to_globlal_property_if_set() { + LogLevelConfig config = LogLevelConfig.newBuilder().rootLevelFor(ProcessId.WEB_SERVER).build(); + + props.set("sonar.log.level", "TRACE"); + + LoggerContext context = underTest.apply(config, props); + + assertThat(context.getLogger(ROOT_LOGGER_NAME).getLevel()).isEqualTo(Level.TRACE); + } + + @Test + public void apply_sets_logger_to_process_property_if_set() { + LogLevelConfig config = LogLevelConfig.newBuilder().rootLevelFor(ProcessId.WEB_SERVER).build(); + + props.set("sonar.log.level.web", "DEBUG"); + + LoggerContext context = underTest.apply(config, props); + + assertThat(context.getLogger(ROOT_LOGGER_NAME).getLevel()).isEqualTo(Level.DEBUG); + } + + @Test + public void apply_sets_logger_to_process_property_over_global_property_if_both_set() { + LogLevelConfig config = LogLevelConfig.newBuilder().rootLevelFor(ProcessId.WEB_SERVER).build(); + props.set("sonar.log.level", "DEBUG"); + props.set("sonar.log.level.web", "TRACE"); + + LoggerContext context = underTest.apply(config, props); + + assertThat(context.getLogger(ROOT_LOGGER_NAME).getLevel()).isEqualTo(Level.TRACE); + } + + @Test + public void apply_sets_domain_property_over_process_and_global_property_if_all_set() { + LogLevelConfig config = LogLevelConfig.newBuilder().levelByDomain("foo", ProcessId.WEB_SERVER, LogDomain.ES_CLIENT).build(); + props.set("sonar.log.level", "DEBUG"); + props.set("sonar.log.level.web", "DEBUG"); + props.set("sonar.log.level.web.es", "TRACE"); + + LoggerContext context = underTest.apply(config, props); + + assertThat(context.getLogger("foo").getLevel()).isEqualTo(Level.TRACE); + } + + @Test + public void apply_sets_domain_property_over_process_property_if_both_set() { + LogLevelConfig config = LogLevelConfig.newBuilder().levelByDomain("foo", ProcessId.WEB_SERVER, LogDomain.ES_CLIENT).build(); + props.set("sonar.log.level.web", "DEBUG"); + props.set("sonar.log.level.web.es", "TRACE"); + + LoggerContext context = underTest.apply(config, props); + + assertThat(context.getLogger("foo").getLevel()).isEqualTo(Level.TRACE); + } + + @Test + public void apply_sets_domain_property_over_global_property_if_both_set() { + LogLevelConfig config = LogLevelConfig.newBuilder().levelByDomain("foo", ProcessId.WEB_SERVER, LogDomain.ES_CLIENT).build(); + props.set("sonar.log.level", "DEBUG"); + props.set("sonar.log.level.web.es", "TRACE"); + + LoggerContext context = underTest.apply(config, props); + + assertThat(context.getLogger("foo").getLevel()).isEqualTo(Level.TRACE); + } + + @Test + public void apply_fails_with_IAE_if_domain_property_has_unsupported_level() { + LogLevelConfig config = LogLevelConfig.newBuilder().levelByDomain("foo", ProcessId.WEB_SERVER, LogDomain.JMX).build(); + + props.set("sonar.log.level.web.jmx", "ERROR"); + + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("log level ERROR in property sonar.log.level.web.jmx is not a supported value (allowed levels are [TRACE, DEBUG, INFO])"); + + underTest.apply(config, props); + } + + @Test + @UseDataProvider("logbackLevels") + public void apply_accepts_any_level_as_hardcoded_level(Level level) { + LogLevelConfig config = LogLevelConfig.newBuilder().immutableLevel("bar", level).build(); + + LoggerContext context = underTest.apply(config, props); + + assertThat(context.getLogger("bar").getLevel()).isEqualTo(level); + } + + @Test + public void changeRoot_sets_level_of_ROOT_and_all_loggers_with_a_config_but_the_hardcoded_one() { + LogLevelConfig config = LogLevelConfig.newBuilder() + .rootLevelFor(ProcessId.WEB_SERVER) + .levelByDomain("foo", ProcessId.WEB_SERVER, LogDomain.JMX) + .levelByDomain("bar", ProcessId.COMPUTE_ENGINE, LogDomain.ES_CLIENT) + .immutableLevel("doh", Level.ERROR) + .immutableLevel("pif", Level.TRACE) + .build(); + LoggerContext context = underTest.apply(config, props); + assertThat(context.getLogger(ROOT_LOGGER_NAME).getLevel()).isEqualTo(Level.INFO); + assertThat(context.getLogger("foo").getLevel()).isEqualTo(Level.INFO); + assertThat(context.getLogger("bar").getLevel()).isEqualTo(Level.INFO); + assertThat(context.getLogger("doh").getLevel()).isEqualTo(Level.ERROR); + assertThat(context.getLogger("pif").getLevel()).isEqualTo(Level.TRACE); + + underTest.changeRoot(config, Level.DEBUG); + + assertThat(context.getLogger(ROOT_LOGGER_NAME).getLevel()).isEqualTo(Level.DEBUG); + assertThat(context.getLogger("foo").getLevel()).isEqualTo(Level.DEBUG); + assertThat(context.getLogger("bar").getLevel()).isEqualTo(Level.DEBUG); + assertThat(context.getLogger("doh").getLevel()).isEqualTo(Level.ERROR); + assertThat(context.getLogger("pif").getLevel()).isEqualTo(Level.TRACE); + } + + @DataProvider + public static Object[][] logbackLevels() { + return new Object[][] { + {Level.OFF}, + {Level.ERROR}, + {Level.WARN}, + {Level.INFO}, + {Level.DEBUG}, + {Level.TRACE}, + {Level.ALL} + }; + } } 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 38e26b7621a..d4ef440abd4 100644 --- a/server/sonar-search/src/main/java/org/sonar/search/SearchLogging.java +++ b/server/sonar-search/src/main/java/org/sonar/search/SearchLogging.java @@ -20,6 +20,7 @@ package org.sonar.search; import ch.qos.logback.classic.LoggerContext; +import org.sonar.process.logging.LogLevelConfig; import org.sonar.process.logging.LogbackHelper; import org.sonar.process.ProcessId; import org.sonar.process.Props; @@ -40,7 +41,8 @@ public class SearchLogging { String logPattern = helper.buildLogPattern(config); helper.configureGlobalFileLog(props, config, logPattern); helper.configureForSubprocessGobbler(props, logPattern); - helper.configureRootLogLevel(props, ProcessId.ELASTICSEARCH); + + helper.apply(LogLevelConfig.newBuilder().rootLevelFor(ProcessId.ELASTICSEARCH).build(), props); return ctx; } diff --git a/server/sonar-server/src/main/java/org/sonar/ce/log/CeProcessLogging.java b/server/sonar-server/src/main/java/org/sonar/ce/log/CeProcessLogging.java index 76fdafff714..52a385de58f 100644 --- a/server/sonar-server/src/main/java/org/sonar/ce/log/CeProcessLogging.java +++ b/server/sonar-server/src/main/java/org/sonar/ce/log/CeProcessLogging.java @@ -19,13 +19,12 @@ */ package org.sonar.ce.log; -import org.sonar.process.logging.LogbackHelper; import org.sonar.process.ProcessId; -import org.sonar.process.Props; +import org.sonar.process.logging.LogDomain; +import org.sonar.process.logging.LogLevelConfig; import org.sonar.server.app.ServerProcessLogging; import static org.sonar.ce.log.CeLogging.MDC_CE_TASK_UUID; -import static org.sonar.process.logging.LogbackHelper.LogDomain; /** * Configure logback for the Compute Engine process. Logs are written to file "ce.log" in SQ's log directory. @@ -37,9 +36,14 @@ public class CeProcessLogging extends ServerProcessLogging { } @Override - protected void extendConfiguration(LogbackHelper helper, Props props) { - helper.configureLoggerLogLevelFromDomain("sql", props, ProcessId.COMPUTE_ENGINE, LogDomain.SQL); - helper.configureLoggerLogLevelFromDomain("es", props, ProcessId.COMPUTE_ENGINE, LogDomain.ES_CLIENT); - helper.configureLoggersLogLevelFromDomain(JMX_RMI_LOGGER_NAMES, props, ProcessId.COMPUTE_ENGINE, LogDomain.JMX); + protected void extendLogLevelConfiguration(LogLevelConfig.Builder logLevelConfigBuilder) { + logLevelConfigBuilder.levelByDomain("sql", ProcessId.COMPUTE_ENGINE, LogDomain.SQL); + logLevelConfigBuilder.levelByDomain("es", ProcessId.COMPUTE_ENGINE, LogDomain.ES_CLIENT); + JMX_RMI_LOGGER_NAMES.forEach(loggerName -> logLevelConfigBuilder.levelByDomain(loggerName, ProcessId.COMPUTE_ENGINE, LogDomain.JMX)); + } + + @Override + protected void extendConfigure() { + // nothing to do } } 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 daadb4f1ddf..baeaa88d318 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/app/ServerProcessLogging.java +++ b/server/sonar-server/src/main/java/org/sonar/server/app/ServerProcessLogging.java @@ -19,35 +19,60 @@ */ package org.sonar.server.app; +import ch.qos.logback.classic.Level; import ch.qos.logback.classic.LoggerContext; import com.google.common.collect.ImmutableSet; import java.util.Set; +import org.sonar.process.logging.LogLevelConfig; import org.sonar.process.logging.LogbackHelper; import org.sonar.process.ProcessId; import org.sonar.process.Props; import org.sonar.process.logging.RootLoggerConfig; -import org.sonar.server.platform.ServerLogging; import static org.sonar.process.logging.RootLoggerConfig.newRootLoggerConfigBuilder; public abstract class ServerProcessLogging { protected static final Set JMX_RMI_LOGGER_NAMES = ImmutableSet.of( - "javax.management.remote.timeout", - "javax.management.remote.misc", - "javax.management.remote.rmi", - "javax.management.mbeanserver", - "sun.rmi.loader", - "sun.rmi.transport.tcp", - "sun.rmi.transport.misc", - "sun.rmi.server.call", - "sun.rmi.dgc"); + "javax.management.remote.timeout", + "javax.management.remote.misc", + "javax.management.remote.rmi", + "javax.management.mbeanserver", + "sun.rmi.loader", + "sun.rmi.transport.tcp", + "sun.rmi.transport.misc", + "sun.rmi.server.call", + "sun.rmi.dgc"); private final ProcessId processId; private final String threadIdFieldPattern; private final LogbackHelper helper = new LogbackHelper(); + private final LogLevelConfig logLevelConfig; protected ServerProcessLogging(ProcessId processId, String threadIdFieldPattern) { this.processId = processId; this.threadIdFieldPattern = threadIdFieldPattern; + this.logLevelConfig = createLogLevelConfiguration(processId); + } + + private LogLevelConfig createLogLevelConfiguration(ProcessId processId) { + LogLevelConfig.Builder builder = LogLevelConfig.newBuilder(); + builder.rootLevelFor(processId); + builder.immutableLevel("rails", Level.WARN); + builder.immutableLevel("org.apache.ibatis", Level.WARN); + builder.immutableLevel("java.sql", Level.WARN); + builder.immutableLevel("java.sql.ResultSet", Level.WARN); + builder.immutableLevel("org.sonar.MEASURE_FILTER", Level.WARN); + builder.immutableLevel("org.elasticsearch", Level.INFO); + builder.immutableLevel("org.elasticsearch.node", Level.INFO); + builder.immutableLevel("org.elasticsearch.http", Level.INFO); + builder.immutableLevel("ch.qos.logback", Level.WARN); + builder.immutableLevel("org.apache.catalina", Level.INFO); + builder.immutableLevel("org.apache.coyote", Level.INFO); + builder.immutableLevel("org.apache.jasper", Level.INFO); + builder.immutableLevel("org.apache.tomcat", Level.INFO); + + extendLogLevelConfiguration(builder); + + return builder.build(); } public LoggerContext configure(Props props) { @@ -56,13 +81,20 @@ public abstract class ServerProcessLogging { helper.enableJulChangePropagation(ctx); configureRootLogger(props); + helper.apply(logLevelConfig, props); - extendConfiguration(helper, props); + extendConfigure(); return ctx; } - protected abstract void extendConfiguration(LogbackHelper helper, Props props); + public LogLevelConfig getLogLevelConfig() { + return this.logLevelConfig; + } + + protected abstract void extendLogLevelConfiguration(LogLevelConfig.Builder logLevelConfigBuilder); + + protected abstract void extendConfigure(); private void configureRootLogger(Props props) { RootLoggerConfig config = newRootLoggerConfigBuilder() @@ -73,9 +105,6 @@ public abstract class ServerProcessLogging { helper.configureGlobalFileLog(props, config, logPattern); helper.configureForSubprocessGobbler(props, logPattern); - - helper.configureRootLogLevel(props, processId); - ServerLogging.configureHardcodedLevels(helper); } } diff --git a/server/sonar-server/src/main/java/org/sonar/server/app/WebServerProcessLogging.java b/server/sonar-server/src/main/java/org/sonar/server/app/WebServerProcessLogging.java index 202b9e1f5ce..da2a4f6ad48 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/app/WebServerProcessLogging.java +++ b/server/sonar-server/src/main/java/org/sonar/server/app/WebServerProcessLogging.java @@ -21,11 +21,10 @@ package org.sonar.server.app; import java.util.logging.LogManager; import org.slf4j.bridge.SLF4JBridgeHandler; -import org.sonar.process.logging.LogbackHelper; +import org.sonar.process.logging.LogLevelConfig; import org.sonar.process.ProcessId; -import org.sonar.process.Props; -import static org.sonar.process.logging.LogbackHelper.LogDomain; +import org.sonar.process.logging.LogDomain; import static org.sonar.server.platform.web.requestid.RequestIdMDCStorage.HTTP_REQUEST_ID_MDC_KEY; /** @@ -38,13 +37,16 @@ public class WebServerProcessLogging extends ServerProcessLogging { } @Override - protected void extendConfiguration(LogbackHelper helper, Props props) { + protected void extendLogLevelConfiguration(LogLevelConfig.Builder logLevelConfigBuilder) { + logLevelConfigBuilder.levelByDomain("sql", ProcessId.WEB_SERVER, LogDomain.SQL); + logLevelConfigBuilder.levelByDomain("es", ProcessId.WEB_SERVER, LogDomain.ES_CLIENT); + JMX_RMI_LOGGER_NAMES.forEach(loggerName -> logLevelConfigBuilder.levelByDomain(loggerName, ProcessId.WEB_SERVER, LogDomain.JMX)); + } + + @Override + protected void extendConfigure() { // Configure java.util.logging, used by Tomcat, in order to forward to slf4j LogManager.getLogManager().reset(); SLF4JBridgeHandler.install(); - - helper.configureLoggerLogLevelFromDomain("sql", props, ProcessId.WEB_SERVER, LogDomain.SQL); - helper.configureLoggerLogLevelFromDomain("es", props, ProcessId.WEB_SERVER, LogDomain.ES_CLIENT); - helper.configureLoggersLogLevelFromDomain(JMX_RMI_LOGGER_NAMES, props, ProcessId.WEB_SERVER, LogDomain.JMX); } } diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/ServerLogging.java b/server/sonar-server/src/main/java/org/sonar/server/platform/ServerLogging.java index 9dd8c447592..08feeea802c 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/platform/ServerLogging.java +++ b/server/sonar-server/src/main/java/org/sonar/server/platform/ServerLogging.java @@ -31,6 +31,7 @@ import org.sonar.api.utils.log.LoggerLevel; import org.sonar.api.utils.log.Loggers; import org.sonar.process.logging.LogbackHelper; import org.sonar.process.ProcessProperties; +import org.sonar.server.app.ServerProcessLogging; @ServerSide @ComputeEngineSide @@ -49,10 +50,9 @@ public class ServerLogging { this.settings = settings; } - public void changeLevel(LoggerLevel level) { + public void changeLevel(ServerProcessLogging serverProcessLogging, LoggerLevel level) { Level logbackLevel = Level.toLevel(level.name()); - helper.configureRootLogLevel(logbackLevel); - configureHardcodedLevels(helper); + helper.changeRoot(serverProcessLogging.getLogLevelConfig(), logbackLevel); LoggerFactory.getLogger(ServerLogging.class).info("Level of logs changed to {}", level); } @@ -60,22 +60,6 @@ public class ServerLogging { return Loggers.get(Logger.ROOT_LOGGER_NAME).getLevel(); } - public static void configureHardcodedLevels(LogbackHelper helper) { - helper.configureLogger("rails", Level.WARN); - helper.configureLogger("org.apache.ibatis", Level.WARN); - helper.configureLogger("java.sql", Level.WARN); - helper.configureLogger("java.sql.ResultSet", Level.WARN); - helper.configureLogger("org.sonar.MEASURE_FILTER", Level.WARN); - helper.configureLogger("org.elasticsearch", Level.INFO); - helper.configureLogger("org.elasticsearch.node", Level.INFO); - helper.configureLogger("org.elasticsearch.http", Level.INFO); - helper.configureLogger("ch.qos.logback", Level.WARN); - helper.configureLogger("org.apache.catalina", Level.INFO); - helper.configureLogger("org.apache.coyote", Level.INFO); - helper.configureLogger("org.apache.jasper", Level.INFO); - helper.configureLogger("org.apache.tomcat", Level.INFO); - } - /** * The directory that contains log files. May not exist. */ diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel1.java b/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel1.java index 6fecd6991dc..7bc4c362646 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel1.java +++ b/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel1.java @@ -40,6 +40,7 @@ import org.sonar.db.version.DatabaseVersion; import org.sonar.process.logging.LogbackHelper; import org.sonar.server.app.ProcessCommandWrapperImpl; import org.sonar.server.app.RestartFlagHolderImpl; +import org.sonar.server.app.WebServerProcessLogging; import org.sonar.server.issue.index.IssueIndex; import org.sonar.server.platform.DatabaseServerCompatibility; import org.sonar.server.platform.LogServerVersion; @@ -87,6 +88,7 @@ public class PlatformLevel1 extends PlatformLevel { UrlSettings.class, EmbeddedDatabaseFactory.class, LogbackHelper.class, + WebServerProcessLogging.class, DefaultDatabase.class, DatabaseChecker.class, // must instantiate deprecated class in 5.2 and only this one (and not its replacement) diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/ws/ChangeLogLevelAction.java b/server/sonar-server/src/main/java/org/sonar/server/platform/ws/ChangeLogLevelAction.java index be2d3602580..1ccb94afe40 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/platform/ws/ChangeLogLevelAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/platform/ws/ChangeLogLevelAction.java @@ -25,6 +25,7 @@ import org.sonar.api.server.ws.WebService; import org.sonar.api.utils.log.LoggerLevel; import org.sonar.ce.http.CeHttpClient; import org.sonar.db.Database; +import org.sonar.server.app.WebServerProcessLogging; import org.sonar.server.platform.ServerLogging; import org.sonar.server.user.UserSession; @@ -38,12 +39,14 @@ public class ChangeLogLevelAction implements SystemWsAction { private final ServerLogging logging; private final Database db; private final CeHttpClient ceHttpClient; + private final WebServerProcessLogging webServerProcessLogging; - public ChangeLogLevelAction(UserSession userSession, ServerLogging logging, Database db, CeHttpClient ceHttpClient) { + public ChangeLogLevelAction(UserSession userSession, ServerLogging logging, Database db, CeHttpClient ceHttpClient, WebServerProcessLogging webServerProcessLogging) { this.userSession = userSession; this.logging = logging; this.db = db; this.ceHttpClient = ceHttpClient; + this.webServerProcessLogging = webServerProcessLogging; } @Override @@ -67,7 +70,7 @@ public class ChangeLogLevelAction implements SystemWsAction { LoggerLevel level = LoggerLevel.valueOf(wsRequest.mandatoryParam(PARAM_LEVEL)); db.enableSqlLogging(level.equals(LoggerLevel.TRACE)); - logging.changeLevel(level); + logging.changeLevel(webServerProcessLogging, level); ceHttpClient.changeLogLevel(level); wsResponse.noContent(); } diff --git a/server/sonar-server/src/test/java/org/sonar/ce/log/CeProcessLoggingTest.java b/server/sonar-server/src/test/java/org/sonar/ce/log/CeProcessLoggingTest.java index f16620049ef..0058688f056 100644 --- a/server/sonar-server/src/test/java/org/sonar/ce/log/CeProcessLoggingTest.java +++ b/server/sonar-server/src/test/java/org/sonar/ce/log/CeProcessLoggingTest.java @@ -36,9 +36,9 @@ import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; import org.junit.rules.TemporaryFolder; -import org.sonar.process.logging.LogbackHelper; import org.sonar.process.ProcessProperties; import org.sonar.process.Props; +import org.sonar.process.logging.LogbackHelper; import static org.assertj.core.api.Assertions.assertThat; import static org.slf4j.Logger.ROOT_LOGGER_NAME; @@ -385,6 +385,31 @@ public class CeProcessLoggingTest { underTest.configure(props); } + @Test + public void configure_defines_hardcoded_levels() { + LoggerContext context = underTest.configure(props); + + verifyImmutableLogLevels(context); + } + + @Test + public void configure_defines_hardcoded_levels_unchanged_by_global_property() { + props.set("sonar.log.level", "TRACE"); + + LoggerContext context = underTest.configure(props); + + verifyImmutableLogLevels(context); + } + + @Test + public void configure_defines_hardcoded_levels_unchanged_by_ce_property() { + props.set("sonar.log.level.ce", "TRACE"); + + LoggerContext context = underTest.configure(props); + + verifyImmutableLogLevels(context); + } + private void verifyRootLogLevel(LoggerContext ctx, Level expected) { assertThat(ctx.getLogger(ROOT_LOGGER_NAME).getLevel()).isEqualTo(expected); } @@ -408,4 +433,20 @@ public class CeProcessLoggingTest { assertThat(ctx.getLogger("sun.rmi.server.call").getLevel()).isEqualTo(expected); assertThat(ctx.getLogger("sun.rmi.dgc").getLevel()).isEqualTo(expected); } + + private void verifyImmutableLogLevels(LoggerContext ctx) { + assertThat(ctx.getLogger("rails").getLevel()).isEqualTo(Level.WARN); + assertThat(ctx.getLogger("org.apache.ibatis").getLevel()).isEqualTo(Level.WARN); + assertThat(ctx.getLogger("java.sql").getLevel()).isEqualTo(Level.WARN); + assertThat(ctx.getLogger("java.sql.ResultSet").getLevel()).isEqualTo(Level.WARN); + assertThat(ctx.getLogger("org.sonar.MEASURE_FILTER").getLevel()).isEqualTo(Level.WARN); + assertThat(ctx.getLogger("org.elasticsearch").getLevel()).isEqualTo(Level.INFO); + assertThat(ctx.getLogger("org.elasticsearch.node").getLevel()).isEqualTo(Level.INFO); + assertThat(ctx.getLogger("org.elasticsearch.http").getLevel()).isEqualTo(Level.INFO); + assertThat(ctx.getLogger("ch.qos.logback").getLevel()).isEqualTo(Level.WARN); + assertThat(ctx.getLogger("org.apache.catalina").getLevel()).isEqualTo(Level.INFO); + assertThat(ctx.getLogger("org.apache.coyote").getLevel()).isEqualTo(Level.INFO); + assertThat(ctx.getLogger("org.apache.jasper").getLevel()).isEqualTo(Level.INFO); + assertThat(ctx.getLogger("org.apache.tomcat").getLevel()).isEqualTo(Level.INFO); + } } diff --git a/server/sonar-server/src/test/java/org/sonar/server/app/WebServerProcessLoggingTest.java b/server/sonar-server/src/test/java/org/sonar/server/app/WebServerProcessLoggingTest.java index 05b05b77f30..02dba16937a 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/app/WebServerProcessLoggingTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/app/WebServerProcessLoggingTest.java @@ -385,6 +385,31 @@ public class WebServerProcessLoggingTest { underTest.configure(props); } + @Test + public void configure_defines_hardcoded_levels() { + LoggerContext context = underTest.configure(props); + + verifyImmutableLogLevels(context); + } + + @Test + public void configure_defines_hardcoded_levels_unchanged_by_global_property() { + props.set("sonar.log.level", "TRACE"); + + LoggerContext context = underTest.configure(props); + + verifyImmutableLogLevels(context); + } + + @Test + public void configure_defines_hardcoded_levels_unchanged_by_ce_property() { + props.set("sonar.log.level.ce", "TRACE"); + + LoggerContext context = underTest.configure(props); + + verifyImmutableLogLevels(context); + } + private void verifyRootLogLevel(LoggerContext ctx, Level expected) { Logger rootLogger = ctx.getLogger(ROOT_LOGGER_NAME); assertThat(rootLogger.getLevel()).isEqualTo(expected); @@ -410,4 +435,20 @@ public class WebServerProcessLoggingTest { assertThat(ctx.getLogger("sun.rmi.dgc").getLevel()).isEqualTo(expected); } + private void verifyImmutableLogLevels(LoggerContext ctx) { + assertThat(ctx.getLogger("rails").getLevel()).isEqualTo(Level.WARN); + assertThat(ctx.getLogger("org.apache.ibatis").getLevel()).isEqualTo(Level.WARN); + assertThat(ctx.getLogger("java.sql").getLevel()).isEqualTo(Level.WARN); + assertThat(ctx.getLogger("java.sql.ResultSet").getLevel()).isEqualTo(Level.WARN); + assertThat(ctx.getLogger("org.sonar.MEASURE_FILTER").getLevel()).isEqualTo(Level.WARN); + assertThat(ctx.getLogger("org.elasticsearch").getLevel()).isEqualTo(Level.INFO); + assertThat(ctx.getLogger("org.elasticsearch.node").getLevel()).isEqualTo(Level.INFO); + assertThat(ctx.getLogger("org.elasticsearch.http").getLevel()).isEqualTo(Level.INFO); + assertThat(ctx.getLogger("ch.qos.logback").getLevel()).isEqualTo(Level.WARN); + assertThat(ctx.getLogger("org.apache.catalina").getLevel()).isEqualTo(Level.INFO); + assertThat(ctx.getLogger("org.apache.coyote").getLevel()).isEqualTo(Level.INFO); + assertThat(ctx.getLogger("org.apache.jasper").getLevel()).isEqualTo(Level.INFO); + assertThat(ctx.getLogger("org.apache.tomcat").getLevel()).isEqualTo(Level.INFO); + } + } 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 4092a0cce72..9dc813ddaf5 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/platform/ServerLoggingTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/platform/ServerLoggingTest.java @@ -20,24 +20,32 @@ package org.sonar.server.platform; import ch.qos.logback.classic.Level; +import com.tngtech.java.junit.dataprovider.DataProvider; +import com.tngtech.java.junit.dataprovider.DataProviderRunner; +import com.tngtech.java.junit.dataprovider.UseDataProvider; import java.io.File; import java.io.IOException; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; import org.junit.rules.TemporaryFolder; +import org.junit.runner.RunWith; import org.sonar.api.config.MapSettings; import org.sonar.api.config.Settings; import org.sonar.api.utils.log.LogTester; import org.sonar.api.utils.log.LoggerLevel; import org.sonar.process.ProcessProperties; +import org.sonar.process.logging.LogLevelConfig; import org.sonar.process.logging.LogbackHelper; +import org.sonar.server.app.ServerProcessLogging; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +@RunWith(DataProviderRunner.class) public class ServerLoggingTest { @Rule @@ -67,66 +75,39 @@ public class ServerLoggingTest { } @Test - public void configureHardcodedLevels() { - LogbackHelper logbackHelper = mock(LogbackHelper.class); - ServerLogging.configureHardcodedLevels(logbackHelper); + @UseDataProvider("supportedSonarApiLevels") + public void changeLevel_calls_changeRoot_with_LogLevelConfig_and_level_converted_to_logback_class_then_log_INFO_message(LoggerLevel level) { + ServerProcessLogging serverProcessLogging = mock(ServerProcessLogging.class); + LogLevelConfig logLevelConfig = LogLevelConfig.newBuilder().build(); + when(serverProcessLogging.getLogLevelConfig()).thenReturn(logLevelConfig); - verifyHardcodedLevels(logbackHelper); - } - - @Test - public void changeLevel_throws_IAE_if_level_is_WARN() { - expectedException.expect(IllegalArgumentException.class); - expectedException.expectMessage("WARN log level is not supported (allowed levels are ["); + underTest.changeLevel(serverProcessLogging, level); - underTest.changeLevel(LoggerLevel.WARN); + verify(logbackHelper).changeRoot(logLevelConfig, Level.valueOf(level.name())); } - @Test - public void changeLevel_throws_IAE_if_level_is_ERROR() { - expectedException.expect(IllegalArgumentException.class); - expectedException.expectMessage("ERROR log level is not supported (allowed levels are ["); - - underTest.changeLevel(LoggerLevel.ERROR); + @DataProvider + public static Object[][] supportedSonarApiLevels() { + return new Object[][] { + {LoggerLevel.INFO}, + {LoggerLevel.DEBUG}, + {LoggerLevel.TRACE} + }; } @Test - public void changeLevel_changes_root_logger_level_to_INFO() { - underTest.changeLevel(LoggerLevel.INFO); - - verify(logbackHelper).configureRootLogLevel(Level.INFO); - verifyHardcodedLevels(logbackHelper); - } - - @Test - public void changeLevel_changes_root_logger_level_to_DEBUG() { - underTest.changeLevel(LoggerLevel.DEBUG); + public void changeLevel_fails_with_IAE_when_level_is_ERROR() { + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("ERROR log level is not supported (allowed levels are [TRACE, DEBUG, INFO])"); - verify(logbackHelper).configureRootLogLevel(Level.DEBUG); - verifyHardcodedLevels(logbackHelper); + underTest.changeLevel(mock(ServerProcessLogging.class), LoggerLevel.ERROR); } @Test - public void changeLevel_changes_root_logger_level_to_TRACE() { - underTest.changeLevel(LoggerLevel.TRACE); - - verify(logbackHelper).configureRootLogLevel(Level.TRACE); - verifyHardcodedLevels(logbackHelper); - } + public void changeLevel_fails_with_IAE_when_level_is_WARN() { + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("WARN log level is not supported (allowed levels are [TRACE, DEBUG, INFO])"); - private void verifyHardcodedLevels(LogbackHelper logbackHelper) { - verify(logbackHelper).configureLogger("rails", Level.WARN); - verify(logbackHelper).configureLogger("org.apache.ibatis", Level.WARN); - verify(logbackHelper).configureLogger("java.sql", Level.WARN); - verify(logbackHelper).configureLogger("java.sql.ResultSet", Level.WARN); - verify(logbackHelper).configureLogger("org.sonar.MEASURE_FILTER", Level.WARN); - verify(logbackHelper).configureLogger("org.elasticsearch", Level.INFO); - verify(logbackHelper).configureLogger("org.elasticsearch.node", Level.INFO); - verify(logbackHelper).configureLogger("org.elasticsearch.http", Level.INFO); - verify(logbackHelper).configureLogger("ch.qos.logback", Level.WARN); - verify(logbackHelper).configureLogger("org.apache.catalina", Level.INFO); - verify(logbackHelper).configureLogger("org.apache.coyote", Level.INFO); - verify(logbackHelper).configureLogger("org.apache.jasper", Level.INFO); - verify(logbackHelper).configureLogger("org.apache.tomcat", Level.INFO); + underTest.changeLevel(mock(ServerProcessLogging.class), LoggerLevel.WARN); } } diff --git a/server/sonar-server/src/test/java/org/sonar/server/platform/ws/ChangeLogLevelActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/platform/ws/ChangeLogLevelActionTest.java index bdf1f6abf0f..ca30fa204e7 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/platform/ws/ChangeLogLevelActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/platform/ws/ChangeLogLevelActionTest.java @@ -25,6 +25,7 @@ import org.junit.rules.ExpectedException; import org.sonar.api.utils.log.LoggerLevel; import org.sonar.ce.http.CeHttpClient; import org.sonar.db.Database; +import org.sonar.server.app.WebServerProcessLogging; import org.sonar.server.exceptions.ForbiddenException; import org.sonar.server.platform.ServerLogging; import org.sonar.server.tester.UserSessionRule; @@ -43,7 +44,8 @@ public class ChangeLogLevelActionTest { private ServerLogging serverLogging = mock(ServerLogging.class); private Database db = mock(Database.class); private CeHttpClient ceHttpClient = mock(CeHttpClient.class); - private ChangeLogLevelAction underTest = new ChangeLogLevelAction(userSession, serverLogging, db, ceHttpClient); + private WebServerProcessLogging webServerProcessLogging = new WebServerProcessLogging(); + private ChangeLogLevelAction underTest = new ChangeLogLevelAction(userSession, serverLogging, db, ceHttpClient, webServerProcessLogging); private WsActionTester actionTester = new WsActionTester(underTest); @Test @@ -71,7 +73,7 @@ public class ChangeLogLevelActionTest { .setMethod("POST") .execute(); - verify(serverLogging).changeLevel(LoggerLevel.DEBUG); + verify(serverLogging).changeLevel(webServerProcessLogging, LoggerLevel.DEBUG); verify(ceHttpClient).changeLogLevel(LoggerLevel.DEBUG); verify(db).enableSqlLogging(false); } @@ -85,7 +87,7 @@ public class ChangeLogLevelActionTest { .setMethod("POST") .execute(); - verify(serverLogging).changeLevel(LoggerLevel.TRACE); + verify(serverLogging).changeLevel(webServerProcessLogging, LoggerLevel.TRACE); verify(ceHttpClient).changeLogLevel(LoggerLevel.TRACE); verify(db).enableSqlLogging(true); } diff --git a/sonar-application/src/main/java/org/sonar/application/AppLogging.java b/sonar-application/src/main/java/org/sonar/application/AppLogging.java index 84d0b074b0f..284fef1466d 100644 --- a/sonar-application/src/main/java/org/sonar/application/AppLogging.java +++ b/sonar-application/src/main/java/org/sonar/application/AppLogging.java @@ -24,6 +24,7 @@ import ch.qos.logback.classic.LoggerContext; import ch.qos.logback.classic.spi.ILoggingEvent; import ch.qos.logback.core.ConsoleAppender; import ch.qos.logback.core.FileAppender; +import org.sonar.process.logging.LogLevelConfig; import org.sonar.process.logging.LogbackHelper; import org.sonar.process.ProcessId; import org.sonar.process.Props; @@ -127,7 +128,7 @@ class AppLogging { } else { configureWithWrapperWritingToFile(ctx); } - helper.configureRootLogLevel(props, ProcessId.APP); + helper.apply(LogLevelConfig.newBuilder().rootLevelFor(ProcessId.APP).build(), props); return ctx; } -- 2.39.5