aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSimon Brandhof <simon.brandhof@sonarsource.com>2016-11-16 15:51:13 +0100
committerSimon Brandhof <simon.brandhof@sonarsource.com>2016-11-17 07:36:51 +0100
commitb612813d06583cb5021469afb6a06c15ecdc3662 (patch)
treea521ed1c5d728180f509fd41fc9822f8a7df9b9e
parent75bae1e0539dd6504c898814621262f1739d08d4 (diff)
downloadsonarqube-b612813d06583cb5021469afb6a06c15ecdc3662.tar.gz
sonarqube-b612813d06583cb5021469afb6a06c15ecdc3662.zip
SONAR-8383 add param "process" to api/system/logs
-rw-r--r--server/sonar-process/src/main/java/org/sonar/process/LogbackHelper.java7
-rw-r--r--server/sonar-process/src/main/java/org/sonar/process/ProcessId.java12
-rw-r--r--server/sonar-process/src/test/java/org/sonar/process/ProcessIdTest.java29
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/platform/ws/LogsAction.java33
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/platform/ws/LogsActionTest.java92
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() {