diff options
author | Simon Brandhof <simon.brandhof@sonarsource.com> | 2016-11-16 15:51:13 +0100 |
---|---|---|
committer | Simon Brandhof <simon.brandhof@sonarsource.com> | 2016-11-17 07:36:51 +0100 |
commit | b612813d06583cb5021469afb6a06c15ecdc3662 (patch) | |
tree | a521ed1c5d728180f509fd41fc9822f8a7df9b9e | |
parent | 75bae1e0539dd6504c898814621262f1739d08d4 (diff) | |
download | sonarqube-b612813d06583cb5021469afb6a06c15ecdc3662.tar.gz sonarqube-b612813d06583cb5021469afb6a06c15ecdc3662.zip |
SONAR-8383 add param "process" to api/system/logs
5 files changed, 156 insertions, 17 deletions
diff --git a/server/sonar-process/src/main/java/org/sonar/process/LogbackHelper.java b/server/sonar-process/src/main/java/org/sonar/process/LogbackHelper.java index cd7ae2dae59..183aa1463a6 100644 --- a/server/sonar-process/src/main/java/org/sonar/process/LogbackHelper.java +++ b/server/sonar-process/src/main/java/org/sonar/process/LogbackHelper.java @@ -45,6 +45,7 @@ import javax.annotation.CheckForNull; import org.apache.commons.lang.StringUtils; import org.slf4j.LoggerFactory; +import static java.lang.String.format; import static java.util.Objects.requireNonNull; import static org.slf4j.Logger.ROOT_LOGGER_NAME; @@ -273,7 +274,7 @@ public class LogbackHelper { Level level = Level.toLevel(value, Level.INFO); if (!isAllowed(level)) { - throw new IllegalArgumentException(String.format("log level %s in property %s is not a supported value (allowed levels are %s)", + throw new IllegalArgumentException(format("log level %s in property %s is not a supported value (allowed levels are %s)", level, propertyKey, Arrays.toString(ALLOWED_ROOT_LOG_LEVELS))); } return level; @@ -302,7 +303,7 @@ public class LogbackHelper { private static void ensureSupportedLevel(Level newLevel) { if (!isAllowed(newLevel)) { - throw new IllegalArgumentException(String.format("%s log level is not supported (allowed levels are %s)", newLevel, Arrays.toString(ALLOWED_ROOT_LOG_LEVELS))); + throw new IllegalArgumentException(format("%s log level is not supported (allowed levels are %s)", newLevel, Arrays.toString(ALLOWED_ROOT_LOG_LEVELS))); } } @@ -362,7 +363,7 @@ public class LogbackHelper { return new NoRollingPolicy(ctx, filenamePrefix, logsDir, maxFiles); } else { - throw new MessageException(String.format("Unsupported value for property %s: %s", ROLLING_POLICY_PROPERTY, rollingPolicy)); + throw new MessageException(format("Unsupported value for property %s: %s", ROLLING_POLICY_PROPERTY, rollingPolicy)); } } diff --git a/server/sonar-process/src/main/java/org/sonar/process/ProcessId.java b/server/sonar-process/src/main/java/org/sonar/process/ProcessId.java index 37db2a08f32..4f7fa8beb14 100644 --- a/server/sonar-process/src/main/java/org/sonar/process/ProcessId.java +++ b/server/sonar-process/src/main/java/org/sonar/process/ProcessId.java @@ -19,6 +19,8 @@ */ package org.sonar.process; +import static java.lang.String.format; + public enum ProcessId { APP("app", 0, "sonar"), @@ -64,4 +66,14 @@ public enum ProcessId { sb.append(']'); return sb.toString(); } + + public static ProcessId fromKey(String key) { + for (ProcessId processId : values()) { + if (processId.getKey().equals(key)) { + return processId; + } + } + throw new IllegalArgumentException(format("Process [%s] does not exist", key)); + } + } diff --git a/server/sonar-process/src/test/java/org/sonar/process/ProcessIdTest.java b/server/sonar-process/src/test/java/org/sonar/process/ProcessIdTest.java index 4eebf2cd124..5a652bcb110 100644 --- a/server/sonar-process/src/test/java/org/sonar/process/ProcessIdTest.java +++ b/server/sonar-process/src/test/java/org/sonar/process/ProcessIdTest.java @@ -21,12 +21,17 @@ package org.sonar.process; import java.util.HashSet; import java.util.Set; +import org.junit.Rule; import org.junit.Test; +import org.junit.rules.ExpectedException; import static org.assertj.core.api.Assertions.assertThat; public class ProcessIdTest { + @Rule + public ExpectedException expectedException = ExpectedException.none(); + @Test public void test_constants() { assertThat(ProcessId.COMPUTE_ENGINE.getKey()).isEqualTo("ce"); @@ -44,4 +49,28 @@ public class ProcessIdTest { assertThat(ipcIndices).hasSize(ProcessId.values().length); assertThat(keys).hasSize(ProcessId.values().length); } + + @Test + public void fromKey_searches_process_by_its_key() { + assertThat(ProcessId.fromKey("app")).isEqualTo(ProcessId.APP); + assertThat(ProcessId.fromKey("ce")).isEqualTo(ProcessId.COMPUTE_ENGINE); + assertThat(ProcessId.fromKey("es")).isEqualTo(ProcessId.ELASTICSEARCH); + assertThat(ProcessId.fromKey("web")).isEqualTo(ProcessId.WEB_SERVER); + } + + @Test + public void fromKey_throws_IAE_if_key_is_null() { + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("Process [null] does not exist"); + + ProcessId.fromKey(null); + } + + @Test + public void fromKey_throws_IAE_if_key_does_not_exist() { + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("Process [foo] does not exist"); + + ProcessId.fromKey("foo"); + } } diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/ws/LogsAction.java b/server/sonar-server/src/main/java/org/sonar/server/platform/ws/LogsAction.java index 545144d697c..141f0d84938 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/platform/ws/LogsAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/platform/ws/LogsAction.java @@ -20,16 +20,23 @@ package org.sonar.server.platform.ws; import java.io.File; +import java.net.HttpURLConnection; import org.apache.commons.io.FileUtils; import org.sonar.api.server.ws.Request; import org.sonar.api.server.ws.Response; import org.sonar.api.server.ws.WebService; +import org.sonar.core.util.stream.Collectors; +import org.sonar.process.ProcessId; import org.sonar.server.platform.ServerLogging; import org.sonar.server.user.UserSession; import org.sonarqube.ws.MediaTypes; +import static java.util.Arrays.stream; + public class LogsAction implements SystemWsAction { + private static final String PROCESS_PROPERTY = "process"; + private final UserSession userSession; private final ServerLogging serverLogging; @@ -40,21 +47,39 @@ public class LogsAction implements SystemWsAction { @Override public void define(WebService.NewController controller) { - controller.createAction("logs") + 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") .setHandler(this); + + action + .createParam(PROCESS_PROPERTY) + .setPossibleValues(stream(ProcessId.values()) + .map(ProcessId::getKey) + .sorted() + .collect(Collectors.toList(ProcessId.values().length))) + .setDefaultValue(ProcessId.APP.getKey()) + .setSince("6.2") + .setDescription("Process to get logs from"); } @Override public void handle(Request wsRequest, Response wsResponse) throws Exception { userSession.checkIsRoot(); - wsResponse.stream().setMediaType(MediaTypes.TXT); - File file = serverLogging.getCurrentLogFile(); - if (file.exists()) { + String processKey = wsRequest.mandatoryParam(PROCESS_PROPERTY); + ProcessId processId = ProcessId.fromKey(processKey); + + File logsDir = serverLogging.getLogsDir(); + File file = new File(logsDir, processId.getLogFilenamePrefix() + ".log"); + // 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); } } } diff --git a/server/sonar-server/src/test/java/org/sonar/server/platform/ws/LogsActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/platform/ws/LogsActionTest.java index feac4ff14fd..a894e880bfa 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/platform/ws/LogsActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/platform/ws/LogsActionTest.java @@ -21,6 +21,7 @@ package org.sonar.server.platform.ws; import java.io.File; import java.io.IOException; +import java.util.Set; import org.apache.commons.io.FileUtils; import org.junit.Rule; import org.junit.Test; @@ -51,6 +52,13 @@ public class LogsActionTest { private WsActionTester actionTester = new WsActionTester(underTest); @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("app", "ce", "es", "web"); + } + + @Test public void request_fails_with_ForbiddenException_when_user_is_not_logged_in() { expectedException.expect(ForbiddenException.class); @@ -67,29 +75,93 @@ public class LogsActionTest { } @Test - public void get_logs() throws IOException { + public void get_app_logs_by_default() throws IOException { makeAuthenticatedUserRoot(); - File file = temp.newFile(); - FileUtils.write(file, "{logs}"); - when(serverLogging.getCurrentLogFile()).thenReturn(file); + createAllLogsFiles(); TestResponse response = actionTester.newRequest().execute(); assertThat(response.getMediaType()).isEqualTo(MediaTypes.TXT); - assertThat(response.getInput()).isEqualTo("{logs}"); + assertThat(response.getInput()).isEqualTo("{app}"); } @Test - public void get_empty_logs_if_file_does_not_exist() throws IOException { + public void return_404_not_found_if_file_does_not_exist() throws IOException { makeAuthenticatedUserRoot(); - File file = temp.newFile(); - file.delete(); - when(serverLogging.getCurrentLogFile()).thenReturn(file); + createLogsDir(); TestResponse response = actionTester.newRequest().execute(); + assertThat(response.getStatus()).isEqualTo(404); + } + + @Test + public void get_ce_logs() throws IOException { + makeAuthenticatedUserRoot(); + + createAllLogsFiles(); + + TestResponse response = actionTester.newRequest() + .setParam("process", "ce") + .execute(); + assertThat(response.getMediaType()).isEqualTo(MediaTypes.TXT); + assertThat(response.getInput()).isEqualTo("{ce}"); + } + + @Test + public void get_es_logs() throws IOException { + makeAuthenticatedUserRoot(); + + createAllLogsFiles(); + + TestResponse response = actionTester.newRequest() + .setParam("process", "es") + .execute(); + assertThat(response.getMediaType()).isEqualTo(MediaTypes.TXT); + assertThat(response.getInput()).isEqualTo("{es}"); + } + + @Test + public void get_web_logs() throws IOException { + makeAuthenticatedUserRoot(); + + createAllLogsFiles(); + + TestResponse response = actionTester.newRequest() + .setParam("process", "web") + .execute(); assertThat(response.getMediaType()).isEqualTo(MediaTypes.TXT); - assertThat(response.getInput()).isEqualTo(""); + assertThat(response.getInput()).isEqualTo("{web}"); + } + + @Test + public void do_not_return_rotated_files() throws IOException { + makeAuthenticatedUserRoot(); + + File dir = createLogsDir(); + FileUtils.write(new File(dir, "sonar.1.log"), "{old}"); + FileUtils.write(new File(dir, "sonar.log"), "{recent}"); + + TestResponse response = actionTester.newRequest() + .setParam("process", "app") + .execute(); + assertThat(response.getMediaType()).isEqualTo(MediaTypes.TXT); + assertThat(response.getInput()).isEqualTo("{recent}"); + } + + private File createAllLogsFiles() throws IOException { + File dir = createLogsDir(); + 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; + } + + private File createLogsDir() throws IOException { + File dir = temp.newFolder(); + when(serverLogging.getLogsDir()).thenReturn(dir); + return dir; } private void makeAuthenticatedUserRoot() { |