Преглед изворни кода

SONAR-21227 Configure a new Logger file for deprecated API usages

tags/10.4.0.87286
antoine.vinot пре 5 месеци
родитељ
комит
6a2f77f0e3
16 измењених фајлова са 498 додато и 44 уклоњено
  1. 2
    1
      server/sonar-ce/src/main/java/org/sonar/ce/logging/CeProcessLogging.java
  2. 2
    2
      server/sonar-process/src/main/java/org/sonar/process/logging/AbstractLogHelper.java
  3. 24
    13
      server/sonar-process/src/main/java/org/sonar/process/logging/LogbackHelper.java
  4. 65
    16
      server/sonar-process/src/test/java/org/sonar/process/logging/LogbackHelperTest.java
  5. 12
    8
      server/sonar-server-common/src/main/java/org/sonar/server/log/ServerProcessLogging.java
  6. 59
    0
      server/sonar-server-common/src/test/java/org/sonar/server/log/ServerProcessLoggingTest.java
  7. 9
    0
      server/sonar-web/public/WEB-INF/web.xml
  8. 32
    0
      server/sonar-webserver-auth/src/it/java/org/sonar/server/authentication/UserSessionInitializerIT.java
  9. 6
    0
      server/sonar-webserver-auth/src/main/java/org/sonar/server/authentication/UserSessionInitializer.java
  10. 44
    2
      server/sonar-webserver-core/src/main/java/org/sonar/server/app/WebServerProcessLogging.java
  11. 38
    0
      server/sonar-webserver-core/src/main/java/org/sonar/server/platform/web/logging/EntrypointMDCStorage.java
  12. 23
    0
      server/sonar-webserver-core/src/main/java/org/sonar/server/platform/web/logging/package-info.java
  13. 35
    2
      server/sonar-webserver-core/src/test/java/org/sonar/server/app/WebServerProcessLoggingTest.java
  14. 2
    0
      server/sonar-webserver-ws/src/main/java/org/sonar/server/ws/WebServiceEngine.java
  15. 89
    0
      server/sonar-webserver/src/it/java/org/sonar/server/platform/web/EndpointPathFilterTest.java
  16. 56
    0
      server/sonar-webserver/src/main/java/org/sonar/server/platform/web/EndpointPathFilter.java

+ 2
- 1
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
}
}

+ 2
- 2
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) {

+ 24
- 13
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;

+ 65
- 16
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<>();


+ 12
- 8
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;

+ 59
- 0
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) {}
};
}
}

+ 9
- 0
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>
@@ -76,6 +81,10 @@
<filter-name>RequestUidFilter</filter-name>
<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>

+ 32
- 0
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);


+ 6
- 0
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();
}


+ 44
- 2
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;
}

}

+ 38
- 0
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);
}
}

+ 23
- 0
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;

+ 35
- 2
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);

+ 2
- 0
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;


+ 89
- 0
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();
}

}

+ 56
- 0
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
}
}

Loading…
Откажи
Сачувај