diff options
author | Jacek Poreda <jacek.poreda@sonarsource.com> | 2023-12-14 16:48:05 +0100 |
---|---|---|
committer | sonartech <sonartech@sonarsource.com> | 2023-12-19 20:02:55 +0000 |
commit | 3313dc56b0f6139e6aaaea6f068e6fad5a004110 (patch) | |
tree | a42a24be023ca0b9ffc47116b4491a283e3a3b2a /server | |
parent | a7a5ec9cecb3e5146094b50af61f02ab067b455c (diff) | |
download | sonarqube-3313dc56b0f6139e6aaaea6f068e6fad5a004110.tar.gz sonarqube-3313dc56b0f6139e6aaaea6f068e6fad5a004110.zip |
SONAR-21227 Support deprecation log in api/system/logs
Diffstat (limited to 'server')
2 files changed, 118 insertions, 57 deletions
diff --git a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/platform/ws/LogsActionIT.java b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/platform/ws/LogsActionIT.java index 882cbcf353f..187b045af08 100644 --- a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/platform/ws/LogsActionIT.java +++ b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/platform/ws/LogsActionIT.java @@ -21,6 +21,7 @@ package org.sonar.server.platform.ws; import java.io.File; import java.io.IOException; +import java.nio.charset.Charset; import java.util.Set; import org.apache.commons.io.FileUtils; import org.junit.Rule; @@ -29,6 +30,7 @@ import org.junit.rules.TemporaryFolder; import org.sonar.server.exceptions.ForbiddenException; import org.sonar.server.log.ServerLogging; import org.sonar.server.tester.UserSessionRule; +import org.sonar.server.ws.TestRequest; import org.sonar.server.ws.TestResponse; import org.sonar.server.ws.WsActionTester; import org.sonarqube.ws.MediaTypes; @@ -46,33 +48,35 @@ public class LogsActionIT { @Rule public TemporaryFolder temp = new TemporaryFolder(); - private ServerLogging serverLogging = mock(ServerLogging.class); - private LogsAction underTest = new LogsAction(userSession, serverLogging); - private WsActionTester actionTester = new WsActionTester(underTest); + private final ServerLogging serverLogging = mock(ServerLogging.class); + private final LogsAction underTest = new LogsAction(userSession, serverLogging); + private final WsActionTester actionTester = new WsActionTester(underTest); + // values are lower-case and alphabetically ordered @Test - public void values_of_process_parameter_are_names_of_processes() { - Set<String> values = actionTester.getDef().param("process").possibleValues(); - // values are lower-case and alphabetically ordered - assertThat(values).containsExactly("access", "app", "ce", "es", "web"); + public void possibleValues_shouldReturnPossibleLogFileValues() { + Set<String> values = actionTester.getDef().param("name").possibleValues(); + assertThat(values).containsExactly("access", "app", "ce", "deprecation", "es", "web"); } @Test - public void request_fails_with_ForbiddenException_when_user_is_not_logged_in() { - assertThatThrownBy(() -> actionTester.newRequest().execute()) + public void execute_whenUserNotLoggedIn_shouldFailWithForbiddenException() { + TestRequest request = actionTester.newRequest(); + assertThatThrownBy(request::execute) .isInstanceOf(ForbiddenException.class); } @Test - public void request_fails_with_ForbiddenException_when_user_is_not_system_administrator() { + public void execute_whenUserIsNotSystemAdministrator_shouldFailWithForbiddenException() { userSession.logIn(); - assertThatThrownBy(() -> actionTester.newRequest().execute()) + TestRequest request = actionTester.newRequest(); + assertThatThrownBy(request::execute) .isInstanceOf(ForbiddenException.class); } @Test - public void get_app_logs_by_default() throws IOException { + public void execute_whenNoLogNameParamProvided_shouldReturnAppLogs() throws IOException { logInAsSystemAdministrator(); createAllLogsFiles(); @@ -83,7 +87,20 @@ public class LogsActionIT { } @Test - public void return_404_not_found_if_file_does_not_exist() throws IOException { + public void execute_whenUsingDeprecatedProcessParameter_shouldReturnCorrectLogs() throws IOException { + logInAsSystemAdministrator(); + + createAllLogsFiles(); + + TestResponse response = actionTester.newRequest() + .setParam("process", "deprecation") + .execute(); + assertThat(response.getMediaType()).isEqualTo(MediaTypes.TXT); + assertThat(response.getInput()).isEqualTo("{deprecation}"); + } + + @Test + public void execute_whenFileDoesNotExist_shouldReturn404NotFound() throws IOException { logInAsSystemAdministrator(); createLogsDir(); @@ -93,14 +110,14 @@ public class LogsActionIT { } @Test - public void download_logs() throws IOException { + public void execute_whenLogNameProvided_shouldRespondWithLogsAccording() throws IOException { logInAsSystemAdministrator(); createAllLogsFiles(); - asList("ce", "es", "web", "access").forEach(process -> { + asList("ce", "es", "web", "access", "deprecation").forEach(process -> { TestResponse response = actionTester.newRequest() - .setParam("process", process) + .setParam("name", process) .execute(); assertThat(response.getMediaType()).isEqualTo(MediaTypes.TXT); assertThat(response.getInput()).isEqualTo("{" + process + "}"); @@ -108,43 +125,60 @@ public class LogsActionIT { } @Test - public void do_not_return_rotated_files() throws IOException { + public void execute_whenNumberRollingPolicy_shouldReturnLatestOnly() throws IOException { logInAsSystemAdministrator(); File dir = createLogsDir(); - FileUtils.write(new File(dir, "sonar.1.log"), "{old}"); - FileUtils.write(new File(dir, "sonar.log"), "{recent}"); + writeTestLogFile(dir, "sonar.1.log", "{old}"); + writeTestLogFile(dir, "sonar.log", "{recent}"); TestResponse response = actionTester.newRequest() - .setParam("process", "app") + .setParam("name", "app") .execute(); assertThat(response.getMediaType()).isEqualTo(MediaTypes.TXT); assertThat(response.getInput()).isEqualTo("{recent}"); } @Test - public void create_latest_created_file() throws IOException { + public void execute_whenDateRollingPolicy_shouldReturnLatestLogFile() throws IOException { logInAsSystemAdministrator(); File dir = createLogsDir(); - FileUtils.write(new File(dir, "sonar.20210101.log"), "{old}"); - FileUtils.write(new File(dir, "sonar.20210201.log"), "{recent}"); + writeTestLogFile(dir, "sonar.20210101.log", "{old}"); + writeTestLogFile(dir, "sonar.20210201.log", "{recent}"); TestResponse response = actionTester.newRequest() - .setParam("process", "app") + .setParam("name", "app") .execute(); assertThat(response.getMediaType()).isEqualTo(MediaTypes.TXT); assertThat(response.getInput()).isEqualTo("{recent}"); } - private File createAllLogsFiles() throws IOException { + private void createAllLogsFiles() throws IOException { File dir = createLogsDir(); - FileUtils.write(new File(dir, "access.log"), "{access}"); - FileUtils.write(new File(dir, "sonar.log"), "{app}"); - FileUtils.write(new File(dir, "ce.log"), "{ce}"); - FileUtils.write(new File(dir, "es.log"), "{es}"); - FileUtils.write(new File(dir, "web.log"), "{web}"); - return dir; + writeTestLogFile(dir, "access.log", "{access}"); + writeTestLogFile(dir, "sonar.log", "{app}"); + writeTestLogFile(dir, "ce.log", "{ce}"); + writeTestLogFile(dir, "es.log", "{es}"); + writeTestLogFile(dir, "web.log", "{web}"); + writeTestLogFile(dir, "deprecation.log", "{deprecation}"); + + writeTestLogFile(dir, "fake.access.log", "{fake-access}"); + writeTestLogFile(dir, "access.19900110.log", "{fake-access}"); + writeTestLogFile(dir, "fake.sonar.log", "{fake-app}"); + writeTestLogFile(dir, "sonar.19900110.log", "{date-app}"); + writeTestLogFile(dir, "fake.ce.log", "{fake-ce}"); + writeTestLogFile(dir, "ce.19900110.log", "{date-ce}"); + writeTestLogFile(dir, "fake.es.log", "{fake-es}"); + writeTestLogFile(dir, "es.19900110.log", "{date-es}"); + writeTestLogFile(dir, "fake.web.log", "{fake-web}"); + writeTestLogFile(dir, "web.19900110.log", "{date-web}"); + writeTestLogFile(dir, "fake.deprecation.log", "{fake-deprecation}"); + writeTestLogFile(dir, "deprecation.19900110.log", "{date-deprecation}"); + } + + private static void writeTestLogFile(File dir, String child, String data) throws IOException { + FileUtils.write(new File(dir, child), data, Charset.defaultCharset()); } private File createLogsDir() throws IOException { diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/platform/ws/LogsAction.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/platform/ws/LogsAction.java index 8d9ca0d22bc..a1c82ef700d 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/platform/ws/LogsAction.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/platform/ws/LogsAction.java @@ -27,8 +27,10 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.util.Comparator; import java.util.Optional; +import java.util.function.Predicate; import java.util.stream.Stream; import org.apache.commons.io.FileUtils; +import org.sonar.api.server.ws.Change; import org.sonar.api.server.ws.Request; import org.sonar.api.server.ws.Response; import org.sonar.api.server.ws.WebService; @@ -37,13 +39,16 @@ import org.sonar.server.log.ServerLogging; import org.sonar.server.user.UserSession; import org.sonarqube.ws.MediaTypes; +import static java.lang.String.format; import static java.util.Arrays.stream; import static java.util.stream.Collectors.toList; public class LogsAction implements SystemWsAction { - + @Deprecated(since = "10.4", forRemoval = true) private static final String PROCESS_PROPERTY = "process"; + private static final String NAME = "name"; private static final String ACCESS_LOG = "access"; + private static final String DEPRECATION_LOG = "deprecation"; private final UserSession userSession; private final ServerLogging serverLogging; @@ -57,54 +62,76 @@ public class LogsAction implements SystemWsAction { public void define(WebService.NewController controller) { var values = stream(ProcessId.values()).map(ProcessId::getKey).collect(toList()); values.add(ACCESS_LOG); + values.add(DEPRECATION_LOG); values.sort(String::compareTo); WebService.NewAction action = controller.createAction("logs") .setDescription("Get system logs in plain-text format. Requires system administration permission.") .setResponseExample(getClass().getResource("logs-example.log")) .setSince("5.2") + .setChangelog( + new Change("10.4", "Add support for deprecation logs in process property."), + new Change("10.4", format("Deprecate property '%s' in favor of '%s'.", PROCESS_PROPERTY, NAME))) .setHandler(this); action - .createParam(PROCESS_PROPERTY) + .createParam(NAME) + .setDeprecatedKey(PROCESS_PROPERTY, "10.4") .setPossibleValues(values) .setDefaultValue(ProcessId.APP.getKey()) .setSince("6.2") - .setDescription("Process to get logs from"); + .setDescription("Name of the logs to get"); } @Override public void handle(Request wsRequest, Response wsResponse) throws Exception { userSession.checkIsSystemAdministrator(); - String processKey = wsRequest.mandatoryParam(PROCESS_PROPERTY); - String filePrefix = ACCESS_LOG.equals(processKey) ? ACCESS_LOG : ProcessId.fromKey(processKey).getLogFilenamePrefix(); + String logName = wsRequest.mandatoryParam(NAME); + String filePrefix = getFilePrefix(logName); File logsDir = serverLogging.getLogsDir(); + Optional<Path> path = getLogFilePath(filePrefix, logsDir); + + if (path.isEmpty()) { + wsResponse.stream().setStatus(HttpURLConnection.HTTP_NOT_FOUND); + return; + } + + File file = new File(logsDir, path.get().getFileName().toString()); + + // filenames are defined in the enum LogProcess. Still to prevent any vulnerability, + // path is double-checked to prevent returning any file present on the file system. + if (file.exists() && file.getParentFile().equals(logsDir)) { + wsResponse.stream().setMediaType(MediaTypes.TXT); + FileUtils.copyFile(file, wsResponse.stream().output()); + } else { + wsResponse.stream().setStatus(HttpURLConnection.HTTP_NOT_FOUND); + } + + } + + private static String getFilePrefix(String logName) { + return switch (logName) { + case ACCESS_LOG -> ACCESS_LOG; + case DEPRECATION_LOG -> DEPRECATION_LOG; + default -> ProcessId.fromKey(logName).getLogFilenamePrefix(); + }; + } + + private static Optional<Path> getLogFilePath(String filePrefix, File logsDir) throws IOException { try (Stream<Path> stream = Files.list(Paths.get(logsDir.getPath()))) { - Optional<Path> path = stream - .filter(p -> p.getFileName().toString().contains(filePrefix) - && p.getFileName().toString().endsWith(".log")) + return stream + .filter(hasMatchingLogFiles(filePrefix)) .max(Comparator.comparing(Path::toString)); - - if (!path.isPresent()) { - wsResponse.stream().setStatus(HttpURLConnection.HTTP_NOT_FOUND); - return; - } - - File file = new File(logsDir, path.get().getFileName().toString()); - - // filenames are defined in the enum LogProcess. Still to prevent any vulnerability, - // path is double-checked to prevent returning any file present on the file system. - if (file.exists() && file.getParentFile().equals(logsDir)) { - wsResponse.stream().setMediaType(MediaTypes.TXT); - FileUtils.copyFile(file, wsResponse.stream().output()); - } else { - wsResponse.stream().setStatus(HttpURLConnection.HTTP_NOT_FOUND); - } - } catch (IOException e) { - throw new RuntimeException("Could not fetch logs", e); } } + + private static Predicate<Path> hasMatchingLogFiles(String filePrefix) { + return p -> { + String stringPath = p.getFileName().toString(); + return stringPath.startsWith(filePrefix) && stringPath.endsWith(".log"); + }; + } } |