diff options
author | antoine.vinot <antoine.vinot@sonarsource.com> | 2023-12-12 09:07:34 +0100 |
---|---|---|
committer | sonartech <sonartech@sonarsource.com> | 2023-12-19 20:02:55 +0000 |
commit | 6a2f77f0e34e09ddd7202e92e685948229570f3f (patch) | |
tree | 676d6a86b5a26dfc936359ee6f6c53b75425a691 /server | |
parent | 7fe24812e4dccac7aebff9b2c5109727a76af56c (diff) | |
download | sonarqube-6a2f77f0e34e09ddd7202e92e685948229570f3f.tar.gz sonarqube-6a2f77f0e34e09ddd7202e92e685948229570f3f.zip |
SONAR-21227 Configure a new Logger file for deprecated API usages
Diffstat (limited to 'server')
16 files changed, 498 insertions, 44 deletions
diff --git a/server/sonar-ce/src/main/java/org/sonar/ce/logging/CeProcessLogging.java b/server/sonar-ce/src/main/java/org/sonar/ce/logging/CeProcessLogging.java index e1b4da2ae87..523993925f1 100644 --- a/server/sonar-ce/src/main/java/org/sonar/ce/logging/CeProcessLogging.java +++ b/server/sonar-ce/src/main/java/org/sonar/ce/logging/CeProcessLogging.java @@ -21,6 +21,7 @@ package org.sonar.ce.logging; import ch.qos.logback.classic.Level; 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.log.ServerProcessLogging; @@ -45,7 +46,7 @@ public class CeProcessLogging extends ServerProcessLogging { } @Override - protected void extendConfigure() { + protected void extendConfigure(Props props) { // nothing to do } } 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 index ed8919c7659..82086d0ebf8 100644 --- 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 @@ -29,8 +29,8 @@ 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}; - private static final String PREFIX_LOG_FORMAT = "%d{yyyy.MM.dd HH:mm:ss} %-5level "; - private static final String SUFFIX_LOG_FORMAT = " %msg%n"; + public static final String PREFIX_LOG_FORMAT = "%d{yyyy.MM.dd HH:mm:ss} %-5level "; + public static final String SUFFIX_LOG_FORMAT = " %msg%n"; private final String loggerNamePattern; protected AbstractLogHelper(String loggerNamePattern) { 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 7c9ccab6e6f..f9ed67629f8 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 @@ -187,8 +187,12 @@ public class LogbackHelper extends AbstractLogHelper { } public FileAppender<ILoggingEvent> newFileAppender(LoggerContext ctx, Props props, RootLoggerConfig config, Encoder<ILoggingEvent> encoder) { - RollingPolicy rollingPolicy = createRollingPolicy(ctx, props, config.getProcessId().getLogFilenamePrefix()); - FileAppender<ILoggingEvent> fileAppender = rollingPolicy.createAppender("file_" + config.getProcessId().getLogFilenamePrefix()); + return newFileAppender(ctx, props, config.getProcessId().getLogFilenamePrefix(), encoder); + } + + public FileAppender<ILoggingEvent> newFileAppender(LoggerContext ctx, Props props, String fileNamePrefix, Encoder<ILoggingEvent> encoder) { + RollingPolicy rollingPolicy = createRollingPolicy(ctx, props, fileNamePrefix); + FileAppender<ILoggingEvent> fileAppender = rollingPolicy.createAppender("file_" + fileNamePrefix); fileAppender.setContext(ctx); fileAppender.setEncoder(encoder); fileAppender.start(); @@ -230,16 +234,23 @@ public class LogbackHelper extends AbstractLogHelper { } public Encoder<ILoggingEvent> createEncoder(Props props, RootLoggerConfig config, LoggerContext context) { - if (props.valueAsBoolean(LOG_JSON_OUTPUT.getKey(), Boolean.parseBoolean(LOG_JSON_OUTPUT.getDefaultValue()))) { - LayoutWrappingEncoder encoder = new LayoutWrappingEncoder<>(); - encoder.setLayout(new LogbackJsonLayout(config.getProcessId().getKey(), config.getNodeNameField())); - encoder.setContext(context); - encoder.start(); - return encoder; - } + return props.valueAsBoolean(LOG_JSON_OUTPUT.getKey(), Boolean.parseBoolean(LOG_JSON_OUTPUT.getDefaultValue())) + ? createJsonEncoder(context, config) + : createPatternLayoutEncoder(context, buildLogPattern(config)); + } + + public Encoder<ILoggingEvent> createJsonEncoder(LoggerContext context, RootLoggerConfig config) { + LayoutWrappingEncoder<ILoggingEvent> encoder = new LayoutWrappingEncoder<>(); + encoder.setLayout(new LogbackJsonLayout(config.getProcessId().getKey(), config.getNodeNameField())); + encoder.setContext(context); + encoder.start(); + return encoder; + } + + public PatternLayoutEncoder createPatternLayoutEncoder(LoggerContext context, String pattern) { PatternLayoutEncoder encoder = new PatternLayoutEncoder(); encoder.setContext(context); - encoder.setPattern(buildLogPattern(config)); + encoder.setPattern(pattern); encoder.start(); return encoder; } @@ -299,7 +310,7 @@ public class LogbackHelper extends AbstractLogHelper { /** * Log files are rotated according to time (one file per day, month or year). - * See http://logback.qos.ch/manual/appenders.html#TimeBasedRollingPolicy + * See <a href="http://logback.qos.ch/manual/appenders.html#TimeBasedRollingPolicy">TimeBasedRollingPolicy</a> */ private static class TimeRollingPolicy extends RollingPolicy { private final String datePattern; @@ -317,7 +328,7 @@ public class LogbackHelper extends AbstractLogHelper { String filePath = new File(logsDir, filenamePrefix + ".log").getAbsolutePath(); appender.setFile(filePath); - TimeBasedRollingPolicy rollingPolicy = new TimeBasedRollingPolicy(); + TimeBasedRollingPolicy<ILoggingEvent> rollingPolicy = new TimeBasedRollingPolicy<>(); rollingPolicy.setContext(context); rollingPolicy.setFileNamePattern(StringUtils.replace(filePath, filenamePrefix + ".log", filenamePrefix + ".%d{" + datePattern + "}.log")); rollingPolicy.setMaxHistory(maxFiles); @@ -331,7 +342,7 @@ public class LogbackHelper extends AbstractLogHelper { /** * Log files are rotated according to their size. - * See http://logback.qos.ch/manual/appenders.html#FixedWindowRollingPolicy + * See <a href="http://logback.qos.ch/manual/appenders.html#FixedWindowRollingPolicy">FixedWindowRollingPolicy</a> */ private static class SizeRollingPolicy extends RollingPolicy { private final String size; 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 42aa134a03b..6be0b22045a 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 @@ -61,6 +61,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.Assert.fail; import static org.slf4j.Logger.ROOT_LOGGER_NAME; +import static org.sonar.process.ProcessId.WEB_SERVER; import static org.sonar.process.ProcessProperties.Property.PATH_LOGS; import static org.sonar.process.logging.RootLoggerConfig.newRootLoggerConfigBuilder; @@ -134,7 +135,7 @@ public class LogbackHelperTest { public void buildLogPattern_does_not_put_threadIdFieldPattern_from_RootLoggerConfig_is_empty() { String pattern = underTest.buildLogPattern( newRootLoggerConfigBuilder() - .setProcessId(ProcessId.WEB_SERVER) + .setProcessId(WEB_SERVER) .setThreadIdFieldPattern("") .build()); @@ -157,7 +158,7 @@ public class LogbackHelperTest { LoggerContext ctx = underTest.getRootContext(); String logbackRootLoggerName = underTest.getRootLoggerName(); LogLevelConfig config = LogLevelConfig.newBuilder(logbackRootLoggerName) - .levelByDomain(logbackRootLoggerName, ProcessId.WEB_SERVER, LogDomain.JMX).build(); + .levelByDomain(logbackRootLoggerName, WEB_SERVER, LogDomain.JMX).build(); props.set("sonar.log.level.web", "TRACE"); underTest.apply(config, props); @@ -313,7 +314,7 @@ public class LogbackHelperTest { @Test public void apply_fails_with_IAE_if_global_property_has_unsupported_level() { - LogLevelConfig config = newLogLevelConfig().rootLevelFor(ProcessId.WEB_SERVER).build(); + LogLevelConfig config = newLogLevelConfig().rootLevelFor(WEB_SERVER).build(); props.set("sonar.log.level", "ERROR"); @@ -324,7 +325,7 @@ public class LogbackHelperTest { @Test public void apply_fails_with_IAE_if_process_property_has_unsupported_level() { - LogLevelConfig config = newLogLevelConfig().rootLevelFor(ProcessId.WEB_SERVER).build(); + LogLevelConfig config = newLogLevelConfig().rootLevelFor(WEB_SERVER).build(); props.set("sonar.log.level.web", "ERROR"); @@ -335,7 +336,7 @@ public class LogbackHelperTest { @Test public void apply_sets_logger_to_INFO_if_no_property_is_set() { - LogLevelConfig config = newLogLevelConfig().rootLevelFor(ProcessId.WEB_SERVER).build(); + LogLevelConfig config = newLogLevelConfig().rootLevelFor(WEB_SERVER).build(); LoggerContext context = underTest.apply(config, props); @@ -344,7 +345,7 @@ public class LogbackHelperTest { @Test public void apply_sets_logger_to_globlal_property_if_set() { - LogLevelConfig config = newLogLevelConfig().rootLevelFor(ProcessId.WEB_SERVER).build(); + LogLevelConfig config = newLogLevelConfig().rootLevelFor(WEB_SERVER).build(); props.set("sonar.log.level", "TRACE"); @@ -355,7 +356,7 @@ public class LogbackHelperTest { @Test public void apply_sets_logger_to_process_property_if_set() { - LogLevelConfig config = newLogLevelConfig().rootLevelFor(ProcessId.WEB_SERVER).build(); + LogLevelConfig config = newLogLevelConfig().rootLevelFor(WEB_SERVER).build(); props.set("sonar.log.level.web", "DEBUG"); @@ -366,7 +367,7 @@ public class LogbackHelperTest { @Test public void apply_sets_logger_to_process_property_over_global_property_if_both_set() { - LogLevelConfig config = newLogLevelConfig().rootLevelFor(ProcessId.WEB_SERVER).build(); + LogLevelConfig config = newLogLevelConfig().rootLevelFor(WEB_SERVER).build(); props.set("sonar.log.level", "DEBUG"); props.set("sonar.log.level.web", "TRACE"); @@ -377,7 +378,7 @@ public class LogbackHelperTest { @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(); + LogLevelConfig config = newLogLevelConfig().levelByDomain("foo", 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"); @@ -389,7 +390,7 @@ public class LogbackHelperTest { @Test public void apply_sets_domain_property_over_process_property_if_both_set() { - LogLevelConfig config = newLogLevelConfig().levelByDomain("foo", ProcessId.WEB_SERVER, LogDomain.ES).build(); + LogLevelConfig config = newLogLevelConfig().levelByDomain("foo", WEB_SERVER, LogDomain.ES).build(); props.set("sonar.log.level.web", "DEBUG"); props.set("sonar.log.level.web.es", "TRACE"); @@ -400,7 +401,7 @@ public class LogbackHelperTest { @Test public void apply_sets_domain_property_over_global_property_if_both_set() { - LogLevelConfig config = newLogLevelConfig().levelByDomain("foo", ProcessId.WEB_SERVER, LogDomain.ES).build(); + LogLevelConfig config = newLogLevelConfig().levelByDomain("foo", WEB_SERVER, LogDomain.ES).build(); props.set("sonar.log.level", "DEBUG"); props.set("sonar.log.level.web.es", "TRACE"); @@ -411,7 +412,7 @@ public class LogbackHelperTest { @Test public void apply_fails_with_IAE_if_domain_property_has_unsupported_level() { - LogLevelConfig config = newLogLevelConfig().levelByDomain("foo", ProcessId.WEB_SERVER, LogDomain.JMX).build(); + LogLevelConfig config = newLogLevelConfig().levelByDomain("foo", WEB_SERVER, LogDomain.JMX).build(); props.set("sonar.log.level.web.jmx", "ERROR"); @@ -433,8 +434,8 @@ public class LogbackHelperTest { @Test public void changeRoot_sets_level_of_ROOT_and_all_loggers_with_a_config_but_the_hardcoded_one() { LogLevelConfig config = newLogLevelConfig() - .rootLevelFor(ProcessId.WEB_SERVER) - .levelByDomain("foo", ProcessId.WEB_SERVER, LogDomain.JMX) + .rootLevelFor(WEB_SERVER) + .levelByDomain("foo", WEB_SERVER, LogDomain.JMX) .levelByDomain("bar", ProcessId.COMPUTE_ENGINE, LogDomain.ES) .immutableLevel("doh", Level.ERROR) .immutableLevel("pif", Level.TRACE) @@ -486,7 +487,7 @@ public class LogbackHelperTest { @Test public void createEncoder_uses_pattern_by_default() { RootLoggerConfig config = newRootLoggerConfigBuilder() - .setProcessId(ProcessId.WEB_SERVER) + .setProcessId(WEB_SERVER) .build(); Encoder<ILoggingEvent> encoder = underTest.createEncoder(props, config, underTest.getRootContext()); @@ -498,7 +499,7 @@ public class LogbackHelperTest { public void createEncoder_uses_json_output() { props.set("sonar.log.jsonOutput", "true"); RootLoggerConfig config = newRootLoggerConfigBuilder() - .setProcessId(ProcessId.WEB_SERVER) + .setProcessId(WEB_SERVER) .build(); Encoder<ILoggingEvent> encoder = underTest.createEncoder(props, config, underTest.getRootContext()); @@ -554,6 +555,54 @@ public class LogbackHelperTest { assertThat(underTest.isAllLogsToConsoleEnabled(new Props(properties))).isFalse(); } + @Test + public void newFileAppender_shouldStartAppender() { + PatternLayoutEncoder patternLayoutEncoder = new PatternLayoutEncoder(); + + FileAppender<ILoggingEvent> result = underTest.newFileAppender(underTest.getRootContext(), props, "foo", patternLayoutEncoder); + + assertThat(result.isStarted()).isTrue(); + assertThat(result.getName()).isEqualTo("file_foo"); + assertThat(result.getEncoder()).isSameAs(patternLayoutEncoder); + assertThat(result.getContext()).isSameAs(underTest.getRootContext()); + } + + @Test + public void newFileAppender_whenConfig_shouldStartAppenderWithConfigFileName() { + PatternLayoutEncoder patternLayoutEncoder = new PatternLayoutEncoder(); + RootLoggerConfig config = newRootLoggerConfigBuilder() + .setProcessId(WEB_SERVER) + .build(); + + FileAppender<ILoggingEvent> result = underTest.newFileAppender(underTest.getRootContext(), props, config, patternLayoutEncoder); + + assertThat(result.isStarted()).isTrue(); + assertThat(result.getName()).isEqualTo("file_" + WEB_SERVER.getLogFilenamePrefix()); + } + + @Test + public void createJsonEncoder_shouldStartJsonEncoder() { + RootLoggerConfig config = newRootLoggerConfigBuilder().setProcessId(WEB_SERVER).build(); + LogbackJsonLayout expectedJsonLayout = new LogbackJsonLayout(config.getProcessId().getKey(), config.getNodeNameField()); + + Encoder<ILoggingEvent> result = underTest.createJsonEncoder(underTest.getRootContext(), config); + + assertThat(result.isStarted()).isTrue(); + assertThat(result).isInstanceOf(LayoutWrappingEncoder.class); + LayoutWrappingEncoder layoutWrappingEncoder = (LayoutWrappingEncoder) result; + assertThat(layoutWrappingEncoder.getLayout()).usingRecursiveComparison().isEqualTo(expectedJsonLayout); + assertThat(layoutWrappingEncoder.getContext()).isSameAs(underTest.getRootContext()); + } + + @Test + public void createPatternLayoutEncoder_shouldStartPatternEncoder() { + PatternLayoutEncoder result = underTest.createPatternLayoutEncoder(underTest.getRootContext(), "foo"); + + assertThat(result.isStarted()).isTrue(); + assertThat(result.getPattern()).isEqualTo("foo"); + assertThat(result.getContext()).isSameAs(underTest.getRootContext()); + } + public static class MemoryAppender extends AppenderBase<ILoggingEvent> { private static final List<ILoggingEvent> LOGS = new ArrayList<>(); diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/log/ServerProcessLogging.java b/server/sonar-server-common/src/main/java/org/sonar/server/log/ServerProcessLogging.java index c3d6cd21be7..092881f49b0 100644 --- a/server/sonar-server-common/src/main/java/org/sonar/server/log/ServerProcessLogging.java +++ b/server/sonar-server-common/src/main/java/org/sonar/server/log/ServerProcessLogging.java @@ -59,7 +59,7 @@ public abstract class ServerProcessLogging { private final ProcessId processId; private final String threadIdFieldPattern; - private final LogbackHelper helper = new LogbackHelper(); + protected final LogbackHelper helper = new LogbackHelper(); private final LogLevelConfig logLevelConfig; protected ServerProcessLogging(ProcessId processId, String threadIdFieldPattern) { @@ -122,7 +122,7 @@ public abstract class ServerProcessLogging { configureRootLogger(props); helper.apply(logLevelConfig, props); configureDirectToConsoleLoggers(props, ctx, STARTUP_LOGGER_NAME); - extendConfigure(); + extendConfigure(props); helper.enableJulChangePropagation(ctx); @@ -135,21 +135,25 @@ public abstract class ServerProcessLogging { protected abstract void extendLogLevelConfiguration(LogLevelConfig.Builder logLevelConfigBuilder); - protected abstract void extendConfigure(); + protected abstract void extendConfigure(Props props); private void configureRootLogger(Props props) { - RootLoggerConfig config = newRootLoggerConfigBuilder() + RootLoggerConfig config = buildRootLoggerConfig(props); + Encoder<ILoggingEvent> encoder = helper.createEncoder(props, config, helper.getRootContext()); + helper.configureGlobalFileLog(props, config, encoder); + helper.configureForSubprocessGobbler(props, encoder); + } + + protected RootLoggerConfig buildRootLoggerConfig(Props props) { + return newRootLoggerConfigBuilder() .setProcessId(processId) .setNodeNameField(getNodeNameWhenCluster(props)) .setThreadIdFieldPattern(threadIdFieldPattern) .build(); - Encoder<ILoggingEvent> encoder = helper.createEncoder(props, config, helper.getRootContext()); - helper.configureGlobalFileLog(props, config, encoder); - helper.configureForSubprocessGobbler(props, encoder); } @CheckForNull - private static String getNodeNameWhenCluster(Props props) { + protected static String getNodeNameWhenCluster(Props props) { boolean clusterEnabled = props.valueAsBoolean(CLUSTER_ENABLED.getKey(), Boolean.parseBoolean(CLUSTER_ENABLED.getDefaultValue())); return clusterEnabled ? props.value(CLUSTER_NODE_NAME.getKey(), CLUSTER_NODE_NAME.getDefaultValue()) : null; diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/log/ServerProcessLoggingTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/log/ServerProcessLoggingTest.java new file mode 100644 index 00000000000..a61333a30ef --- /dev/null +++ b/server/sonar-server-common/src/test/java/org/sonar/server/log/ServerProcessLoggingTest.java @@ -0,0 +1,59 @@ +/* + * SonarQube + * Copyright (C) 2009-2023 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.server.log; + +import org.junit.Test; +import org.mockito.Mockito; +import org.sonar.process.ProcessId; +import org.sonar.process.Props; +import org.sonar.process.logging.LogLevelConfig; +import org.sonar.process.logging.RootLoggerConfig; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.sonar.process.ProcessId.WEB_SERVER; +import static org.sonar.process.logging.RootLoggerConfig.newRootLoggerConfigBuilder; + +public class ServerProcessLoggingTest { + + @Test + public void buildRootLoggerConfig_shouldBuildConfig() { + ServerProcessLogging serverProcessLogging = getServerProcessLoggingFakeImpl(WEB_SERVER, "threadIdFieldPattern"); + Props props = Mockito.mock(Props.class); + RootLoggerConfig expected = newRootLoggerConfigBuilder() + .setProcessId(WEB_SERVER) + .setNodeNameField(null) + .setThreadIdFieldPattern("threadIdFieldPattern") + .build(); + + RootLoggerConfig result = serverProcessLogging.buildRootLoggerConfig(props); + + assertThat(result).usingRecursiveComparison().isEqualTo(expected); + } + + private ServerProcessLogging getServerProcessLoggingFakeImpl(ProcessId processId, String threadIdFieldPattern) { + return new ServerProcessLogging(processId, threadIdFieldPattern) { + @Override + protected void extendLogLevelConfiguration(LogLevelConfig.Builder logLevelConfigBuilder) {} + + @Override + protected void extendConfigure(Props props) {} + }; + } +} diff --git a/server/sonar-web/public/WEB-INF/web.xml b/server/sonar-web/public/WEB-INF/web.xml index 6ee3d23b5fa..91e874831eb 100644 --- a/server/sonar-web/public/WEB-INF/web.xml +++ b/server/sonar-web/public/WEB-INF/web.xml @@ -62,6 +62,11 @@ <filter-class>org.sonar.server.platform.web.CspFilter</filter-class> <async-supported>true</async-supported> </filter> + <filter> + <filter-name>EndpointPathFilter</filter-name> + <filter-class>org.sonar.server.platform.web.EndpointPathFilter</filter-class> + <async-supported>true</async-supported> + </filter> <!-- order of execution is important --> <filter-mapping> @@ -77,6 +82,10 @@ <url-pattern>/*</url-pattern> </filter-mapping> <filter-mapping> + <filter-name>EndpointPathFilter</filter-name> + <url-pattern>/*</url-pattern> + </filter-mapping> + <filter-mapping> <filter-name>RedirectFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> diff --git a/server/sonar-webserver-auth/src/it/java/org/sonar/server/authentication/UserSessionInitializerIT.java b/server/sonar-webserver-auth/src/it/java/org/sonar/server/authentication/UserSessionInitializerIT.java index 294edb1194c..02601ed7aa7 100644 --- a/server/sonar-webserver-auth/src/it/java/org/sonar/server/authentication/UserSessionInitializerIT.java +++ b/server/sonar-webserver-auth/src/it/java/org/sonar/server/authentication/UserSessionInitializerIT.java @@ -25,6 +25,7 @@ import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.mockito.ArgumentCaptor; +import org.slf4j.MDC; import org.sonar.api.config.internal.MapSettings; import org.sonar.api.server.authentication.BaseIdentityProvider; import org.sonar.api.server.http.Cookie; @@ -215,6 +216,37 @@ public class UserSessionInitializerIT { verify(response).addHeader("SonarQube-Authentication-Token-Expiration", formatDateTime(expirationTimestamp)); } + @Test + public void initUserSession_shouldPutLoginInMDC() { + when(threadLocalSession.isLoggedIn()).thenReturn(false); + when(authenticator.authenticate(request, response)).thenReturn(new MockUserSession("user")); + + underTest.initUserSession(request, response); + + assertThat(MDC.get("LOGIN")).isEqualTo("user"); + } + + @Test + public void initUserSession_whenSessionLoginIsNull_shouldPutDefaultLoginValueInMDC() { + when(threadLocalSession.isLoggedIn()).thenReturn(false); + when(authenticator.authenticate(request, response)).thenReturn(new AnonymousMockUserSession()); + + underTest.initUserSession(request, response); + + assertThat(MDC.get("LOGIN")).isEqualTo("-"); + } + + @Test + public void removeUserSession_shoudlRemoveMDCLogin() { + when(threadLocalSession.isLoggedIn()).thenReturn(false); + when(authenticator.authenticate(request, response)).thenReturn(new MockUserSession("user")); + underTest.initUserSession(request, response); + + underTest.removeUserSession(); + + assertThat(MDC.get("LOGIN")).isNull(); + } + private void assertPathIsIgnored(String path) { when(request.getRequestURI()).thenReturn(path); diff --git a/server/sonar-webserver-auth/src/main/java/org/sonar/server/authentication/UserSessionInitializer.java b/server/sonar-webserver-auth/src/main/java/org/sonar/server/authentication/UserSessionInitializer.java index ab36616e010..bfcbe4c89c5 100644 --- a/server/sonar-webserver-auth/src/main/java/org/sonar/server/authentication/UserSessionInitializer.java +++ b/server/sonar-webserver-auth/src/main/java/org/sonar/server/authentication/UserSessionInitializer.java @@ -21,6 +21,7 @@ package org.sonar.server.authentication; import java.util.Optional; import java.util.Set; +import org.slf4j.MDC; import org.sonar.api.config.Configuration; import org.sonar.api.impl.ws.StaticResources; import org.sonar.api.server.ServerSide; @@ -52,6 +53,8 @@ public class UserSessionInitializer { */ private static final String ACCESS_LOG_LOGIN = "LOGIN"; + public static final String USER_LOGIN_MDC_KEY = "LOGIN"; + private static final String SQ_AUTHENTICATION_TOKEN_EXPIRATION = "SonarQube-Authentication-Token-Expiration"; // SONAR-6546 these urls should be get from WebService @@ -97,6 +100,7 @@ public class UserSessionInitializer { } public boolean initUserSession(HttpRequest request, HttpResponse response) { + MDC.put(USER_LOGIN_MDC_KEY, "-"); String path = request.getRequestURI().replaceFirst(request.getContextPath(), ""); try { // Do not set user session when url is excluded @@ -137,6 +141,7 @@ public class UserSessionInitializer { threadLocalSession.set(session); checkTokenUserSession(response, session); request.setAttribute(ACCESS_LOG_LOGIN, defaultString(session.getLogin(), "-")); + MDC.put(USER_LOGIN_MDC_KEY, defaultString(session.getLogin(), "-")); } private static void checkTokenUserSession(HttpResponse response, UserSession session) { @@ -147,6 +152,7 @@ public class UserSessionInitializer { } public void removeUserSession() { + MDC.remove(USER_LOGIN_MDC_KEY); threadLocalSession.unload(); } diff --git a/server/sonar-webserver-core/src/main/java/org/sonar/server/app/WebServerProcessLogging.java b/server/sonar-webserver-core/src/main/java/org/sonar/server/app/WebServerProcessLogging.java index 616a2221dc2..1d66becda72 100644 --- a/server/sonar-webserver-core/src/main/java/org/sonar/server/app/WebServerProcessLogging.java +++ b/server/sonar-webserver-core/src/main/java/org/sonar/server/app/WebServerProcessLogging.java @@ -20,18 +20,34 @@ package org.sonar.server.app; import ch.qos.logback.classic.Level; +import ch.qos.logback.classic.Logger; +import ch.qos.logback.classic.LoggerContext; +import ch.qos.logback.classic.spi.ILoggingEvent; +import ch.qos.logback.core.FileAppender; +import ch.qos.logback.core.encoder.Encoder; 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.process.logging.RootLoggerConfig; import org.sonar.server.log.ServerProcessLogging; +import static org.apache.commons.lang.StringUtils.isBlank; +import static org.sonar.process.ProcessProperties.Property.LOG_JSON_OUTPUT; +import static org.sonar.process.logging.AbstractLogHelper.PREFIX_LOG_FORMAT; +import static org.sonar.process.logging.AbstractLogHelper.SUFFIX_LOG_FORMAT; +import static org.sonar.server.authentication.UserSessionInitializer.USER_LOGIN_MDC_KEY; +import static org.sonar.server.platform.web.logging.EntrypointMDCStorage.ENTRYPOINT_MDC_KEY; import static org.sonar.server.platform.web.requestid.RequestIdMDCStorage.HTTP_REQUEST_ID_MDC_KEY; +import static org.sonar.server.ws.WebServiceEngine.DEPRECATION_LOGGER_NAME; /** * Configure logback for the Web Server process. Logs are written to file "web.log" in SQ's log directory. */ public class WebServerProcessLogging extends ServerProcessLogging { + private static final String DEPRECATION_LOG_FILE_PREFIX = "deprecation"; + public WebServerProcessLogging() { super(ProcessId.WEB_SERVER, "%X{" + HTTP_REQUEST_ID_MDC_KEY + "}"); } @@ -51,7 +67,33 @@ public class WebServerProcessLogging extends ServerProcessLogging { } @Override - protected void extendConfigure() { - // No extension needed + protected void extendConfigure(Props props) { + configureDeprecatedApiLogger(props); } + + private void configureDeprecatedApiLogger(Props props) { + LoggerContext context = helper.getRootContext(); + + RootLoggerConfig config = buildRootLoggerConfig(props); + Encoder<ILoggingEvent> encoder = props.valueAsBoolean(LOG_JSON_OUTPUT.getKey(), Boolean.parseBoolean(LOG_JSON_OUTPUT.getDefaultValue())) + ? helper.createJsonEncoder(context, config) + : helper.createPatternLayoutEncoder(context, buildDepractedLogPatrern(config)); + + FileAppender<ILoggingEvent> appender = helper.newFileAppender(context, props, DEPRECATION_LOG_FILE_PREFIX, encoder); + + Logger deprecated = context.getLogger(DEPRECATION_LOGGER_NAME); + deprecated.setAdditive(false); + deprecated.addAppender(appender); + } + + private static String buildDepractedLogPatrern(RootLoggerConfig config) { + return PREFIX_LOG_FORMAT + + (isBlank(config.getNodeNameField()) ? "" : (config.getNodeNameField() + " ")) + + config.getProcessId().getKey() + + "[" + config.getThreadIdFieldPattern() + "]" + + " %X{" + USER_LOGIN_MDC_KEY + "}" + + " %X{" + ENTRYPOINT_MDC_KEY + "}" + + SUFFIX_LOG_FORMAT; + } + } diff --git a/server/sonar-webserver-core/src/main/java/org/sonar/server/platform/web/logging/EntrypointMDCStorage.java b/server/sonar-webserver-core/src/main/java/org/sonar/server/platform/web/logging/EntrypointMDCStorage.java new file mode 100644 index 00000000000..d41d8cf509c --- /dev/null +++ b/server/sonar-webserver-core/src/main/java/org/sonar/server/platform/web/logging/EntrypointMDCStorage.java @@ -0,0 +1,38 @@ +/* + * SonarQube + * Copyright (C) 2009-2023 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.server.platform.web.logging; + +import javax.annotation.Nullable; +import org.slf4j.MDC; + +import static org.apache.commons.lang.StringUtils.isBlank; + +public class EntrypointMDCStorage implements AutoCloseable { + public static final String ENTRYPOINT_MDC_KEY = "ENTRYPOINT"; + + public EntrypointMDCStorage(@Nullable String entrypoint) { + MDC.put(ENTRYPOINT_MDC_KEY, isBlank(entrypoint) ? "-" : entrypoint); + } + + @Override + public void close() { + MDC.remove(ENTRYPOINT_MDC_KEY); + } +} diff --git a/server/sonar-webserver-core/src/main/java/org/sonar/server/platform/web/logging/package-info.java b/server/sonar-webserver-core/src/main/java/org/sonar/server/platform/web/logging/package-info.java new file mode 100644 index 00000000000..0554a032dec --- /dev/null +++ b/server/sonar-webserver-core/src/main/java/org/sonar/server/platform/web/logging/package-info.java @@ -0,0 +1,23 @@ +/* + * SonarQube + * Copyright (C) 2009-2023 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. + */ +@ParametersAreNonnullByDefault +package org.sonar.server.platform.web.logging; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/server/sonar-webserver-core/src/test/java/org/sonar/server/app/WebServerProcessLoggingTest.java b/server/sonar-webserver-core/src/test/java/org/sonar/server/app/WebServerProcessLoggingTest.java index d346e40277a..d6614246403 100644 --- a/server/sonar-webserver-core/src/test/java/org/sonar/server/app/WebServerProcessLoggingTest.java +++ b/server/sonar-webserver-core/src/test/java/org/sonar/server/app/WebServerProcessLoggingTest.java @@ -59,8 +59,8 @@ public class WebServerProcessLoggingTest { public TemporaryFolder temp = new TemporaryFolder(); private File logDir; - private Props props = new Props(new Properties()); - private WebServerProcessLogging underTest = new WebServerProcessLogging(); + private final Props props = new Props(new Properties()); + private final WebServerProcessLogging underTest = new WebServerProcessLogging(); @Before public void setUp() throws IOException { @@ -525,6 +525,39 @@ public class WebServerProcessLoggingTest { assertThat(((LayoutWrappingEncoder) encoder).getLayout()).isInstanceOf(LogbackJsonLayout.class); } + @Test + public void configure_whenJsonPropFalse_shouldConfigureDeprecatedLoggerWithPatternLayout() { + props.set("sonar.log.jsonOutput", "false"); + + LoggerContext context = underTest.configure(props); + + Logger logger = context.getLogger("SONAR_DEPRECATION"); + assertThat(logger.isAdditive()).isFalse(); + Appender<ILoggingEvent> appender = logger.getAppender("file_deprecation"); + assertThat(appender).isNotNull() + .isInstanceOf(FileAppender.class); + FileAppender<ILoggingEvent> fileAppender = (FileAppender<ILoggingEvent>) appender; + Encoder<ILoggingEvent> encoder = fileAppender.getEncoder(); + assertThat(encoder).isInstanceOf(PatternLayoutEncoder.class); + PatternLayoutEncoder patternLayoutEncoder = (PatternLayoutEncoder) encoder; + assertThat(patternLayoutEncoder.getPattern()).isEqualTo("%d{yyyy.MM.dd HH:mm:ss} %-5level web[%X{HTTP_REQUEST_ID}] %X{LOGIN} %X{ENTRYPOINT} %msg%n"); + } + + @Test + public void configure_whenJsonPropTrue_shouldConfigureDeprecatedLoggerWithJsonLayout() { + props.set("sonar.log.jsonOutput", "true"); + + LoggerContext context = underTest.configure(props); + + Logger logger = context.getLogger("SONAR_DEPRECATION"); + assertThat(logger.isAdditive()).isFalse(); + Appender<ILoggingEvent> appender = logger.getAppender("file_deprecation"); + assertThat(appender).isNotNull() + .isInstanceOf(FileAppender.class); + FileAppender<ILoggingEvent> fileAppender = (FileAppender<ILoggingEvent>) appender; + assertThat(fileAppender.getEncoder()).isInstanceOf(LayoutWrappingEncoder.class); + } + private void verifyRootLogLevel(LoggerContext ctx, Level expected) { Logger rootLogger = ctx.getLogger(ROOT_LOGGER_NAME); assertThat(rootLogger.getLevel()).isEqualTo(expected); diff --git a/server/sonar-webserver-ws/src/main/java/org/sonar/server/ws/WebServiceEngine.java b/server/sonar-webserver-ws/src/main/java/org/sonar/server/ws/WebServiceEngine.java index eebe6812486..4bc1a22d40e 100644 --- a/server/sonar-webserver-ws/src/main/java/org/sonar/server/ws/WebServiceEngine.java +++ b/server/sonar-webserver-ws/src/main/java/org/sonar/server/ws/WebServiceEngine.java @@ -61,6 +61,8 @@ public class WebServiceEngine implements LocalConnector, Startable { private static final Logger LOGGER = LoggerFactory.getLogger(WebServiceEngine.class); + public static final String DEPRECATION_LOGGER_NAME = "SONAR_DEPRECATION"; + private final WebService[] webServices; private final ActionInterceptor[] actionInterceptors; diff --git a/server/sonar-webserver/src/it/java/org/sonar/server/platform/web/EndpointPathFilterTest.java b/server/sonar-webserver/src/it/java/org/sonar/server/platform/web/EndpointPathFilterTest.java new file mode 100644 index 00000000000..f838137cd56 --- /dev/null +++ b/server/sonar-webserver/src/it/java/org/sonar/server/platform/web/EndpointPathFilterTest.java @@ -0,0 +1,89 @@ +/* + * SonarQube + * Copyright (C) 2009-2023 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.server.platform.web; + +import java.io.IOException; +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.junit.Before; +import org.junit.Test; +import org.slf4j.MDC; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class EndpointPathFilterTest { + + private static final String ENDPOINT_PATH = "/api/system/status"; + private static final String ENTRYPOINT_MDC_KEY = "ENTRYPOINT"; + + private final HttpServletRequest servletRequest = mock(HttpServletRequest.class); + private final HttpServletResponse servletResponse = mock(HttpServletResponse.class); + private final FilterChain filterChain = mock(FilterChain.class); + private final EndpointPathFilter endpointPathFilter = new EndpointPathFilter(); + + @Before + public void setUp() { + when(servletRequest.getRequestURI()).thenReturn(ENDPOINT_PATH); + } + + @Test + public void doFilter_shouldPutEndpointToMDCAndRemoveItAfterChainExecution() throws ServletException, IOException { + doAnswer(invocation -> assertThat(MDC.get("ENTRYPOINT")).isEqualTo(ENDPOINT_PATH)) + .when(filterChain) + .doFilter(servletRequest, servletResponse); + + endpointPathFilter.doFilter(servletRequest, servletResponse, filterChain); + + assertThat(MDC.get(ENTRYPOINT_MDC_KEY)).isNull(); + } + + @Test + public void doFilter_whenChainFails_shouldPutInMDCAndRemoveItAfter() throws IOException, ServletException { + RuntimeException exception = new RuntimeException("Simulating chain failing"); + doAnswer(invocation -> { + assertThat(MDC.get(ENTRYPOINT_MDC_KEY)).isEqualTo(ENDPOINT_PATH); + throw exception; + }) + .when(filterChain) + .doFilter(servletRequest, servletResponse); + + assertThatThrownBy(() -> endpointPathFilter.doFilter(servletRequest, servletResponse, filterChain)).isEqualTo(exception); + assertThat(MDC.get(ENTRYPOINT_MDC_KEY)).isNull(); + } + + @Test + public void doFilter_whenNotHttpServletRequest_shouldAddEmptyPath() throws ServletException, IOException { + doAnswer(invocation -> assertThat(MDC.get("ENTRYPOINT")).isEqualTo("-")) + .when(filterChain) + .doFilter(servletRequest, servletResponse); + + endpointPathFilter.doFilter(mock(ServletRequest.class), servletResponse, filterChain); + + assertThat(MDC.get(ENTRYPOINT_MDC_KEY)).isNull(); + } + +} diff --git a/server/sonar-webserver/src/main/java/org/sonar/server/platform/web/EndpointPathFilter.java b/server/sonar-webserver/src/main/java/org/sonar/server/platform/web/EndpointPathFilter.java new file mode 100644 index 00000000000..c60921b8ea9 --- /dev/null +++ b/server/sonar-webserver/src/main/java/org/sonar/server/platform/web/EndpointPathFilter.java @@ -0,0 +1,56 @@ +/* + * SonarQube + * Copyright (C) 2009-2023 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.server.platform.web; + +import java.io.IOException; +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import org.sonar.server.platform.web.logging.EntrypointMDCStorage; + +public class EndpointPathFilter implements Filter { + + @Override + public void init(FilterConfig filterConfig) throws ServletException { + // nothing to do + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { + String endpointPath = null; + if (request instanceof HttpServletRequest httpRequest) { + endpointPath = httpRequest.getRequestURI(); + } + + try (EntrypointMDCStorage entrypointMDCStorage = new EntrypointMDCStorage(endpointPath)) { + chain.doFilter(request, response); + } + + } + + @Override + public void destroy() { + // nothing to do + } +} |