]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-22036 Add a main to the scanner engine
authorJulien HENRY <julien.henry@sonarsource.com>
Wed, 10 Apr 2024 14:01:48 +0000 (16:01 +0200)
committersonartech <sonartech@sonarsource.com>
Mon, 15 Apr 2024 20:02:44 +0000 (20:02 +0000)
12 files changed:
build.gradle
sonar-scanner-engine-shaded/build.gradle
sonar-scanner-engine/build.gradle
sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/bootstrap/BootstrapMediumIT.java [new file with mode: 0644]
sonar-scanner-engine/src/it/java/testutils/TestUtils.java [new file with mode: 0644]
sonar-scanner-engine/src/main/java/org/sonar/batch/bootstrapper/LoggingConfiguration.java
sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/ScannerLogbackEncoder.java [new file with mode: 0644]
sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/ScannerMain.java [new file with mode: 0644]
sonar-scanner-engine/src/main/resources/logback-shared.xml [new file with mode: 0644]
sonar-scanner-engine/src/main/resources/logback.xml [new file with mode: 0644]
sonar-scanner-engine/src/main/resources/org/sonar/batch/bootstrapper/logback.xml
sonar-scanner-engine/src/test/java/org/sonar/scanner/bootstrap/ScannerLogbackEncoderTest.java [new file with mode: 0644]

index 8a3216ffb65276dc99c08945c8538308e873beaa..2ae30f04359c5ad190ead8ee856ba4120a5d89df 100644 (file)
@@ -461,6 +461,7 @@ subprojects {
       dependency 'javax.el:javax.el-api:3.0.0'
       dependency 'org.glassfish:jakarta.el:3.0.4'
       dependency 'org.kohsuke:github-api:1.318'
+      dependency 'org.wiremock:wiremock-standalone:3.5.2'
 
       // please keep this list alphabetically ordered
     }
index 77df9b346b9b4989ba3a8de8c0d53b79a2479bbf..dcb53b4294d102d43eab81273b90dc20ff9c68fa 100644 (file)
@@ -9,3 +9,11 @@ apply plugin: 'com.github.johnrengelman.shadow'
 dependencies {
   api project(':sonar-scanner-engine')
 }
+
+jar {
+  manifest {
+    attributes(
+      'Main-Class' : "org.sonar.scanner.bootstrap.ScannerMain"
+    )
+  }
+}
index de8669b288d5de92e921586da10a1067ed592dfc..af2349194a54113567bc614b53e428ebac7d2c5e 100644 (file)
@@ -61,8 +61,9 @@ dependencies {
   testImplementation 'org.hamcrest:hamcrest-core'
   testImplementation 'org.mockito:mockito-core'
   testImplementation 'org.mockito:mockito-junit-jupiter'
-  api 'org.sonarsource.api.plugin:sonar-plugin-api-test-fixtures'
+  testImplementation 'org.sonarsource.api.plugin:sonar-plugin-api-test-fixtures'
   testImplementation project(':plugins:sonar-xoo-plugin')
+  testImplementation 'org.wiremock:wiremock-standalone'
 
   testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine'
   testRuntimeOnly 'org.junit.vintage:junit-vintage-engine'
diff --git a/sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/bootstrap/BootstrapMediumIT.java b/sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/bootstrap/BootstrapMediumIT.java
new file mode 100644 (file)
index 0000000..8320b9d
--- /dev/null
@@ -0,0 +1,169 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 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.scanner.mediumtest.bootstrap;
+
+import com.github.tomakehurst.wiremock.junit5.WireMockExtension;
+import java.io.ByteArrayInputStream;
+import java.nio.file.Path;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+import org.junit.jupiter.api.io.TempDir;
+import org.slf4j.event.Level;
+import org.sonar.api.testfixtures.log.LogTesterJUnit5;
+import org.sonar.scanner.bootstrap.ScannerMain;
+import org.sonarqube.ws.Ce;
+import org.sonarqube.ws.Qualityprofiles;
+import org.sonarqube.ws.Rules;
+
+import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
+import static com.github.tomakehurst.wiremock.client.WireMock.get;
+import static com.github.tomakehurst.wiremock.client.WireMock.okJson;
+import static com.github.tomakehurst.wiremock.client.WireMock.post;
+import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static testutils.TestUtils.protobufBody;
+
+class BootstrapMediumIT {
+
+  public static final String PROJECT_KEY = "my-project";
+  public static final String QPROFILE_KEY = "profile123";
+  @RegisterExtension
+  LogTesterJUnit5 logTester = new LogTesterJUnit5();
+
+  @RegisterExtension
+  static WireMockExtension sonarqube = WireMockExtension.newInstance()
+    .options(wireMockConfig().dynamicPort())
+    .build();
+
+  @BeforeEach
+  void mockBareMinimalServerEndpoints() {
+    sonarqube.stubFor(get("/api/plugins/installed")
+      .willReturn(okJson("{\n"
+        + "  \"plugins\": []\n"
+        + "}")));
+
+    sonarqube.stubFor(get("/api/qualityprofiles/search.protobuf?project=" + PROJECT_KEY)
+      .willReturn(aResponse()
+        .withResponseBody(protobufBody(Qualityprofiles.SearchWsResponse.newBuilder()
+          .addProfiles(Qualityprofiles.SearchWsResponse.QualityProfile.newBuilder()
+            .setKey(QPROFILE_KEY)
+            .setName("My Profile")
+            .setRulesUpdatedAt("2021-01-01T00:00:00+0000")
+            .build())
+          .build()))));
+
+    sonarqube.stubFor(get("/api/rules/list.protobuf?qprofile=" + QPROFILE_KEY + "&ps=500&p=1")
+      .willReturn(aResponse()
+        .withResponseBody(protobufBody(Rules.ListResponse.newBuilder()
+          .build()))));
+
+    sonarqube.stubFor(get("/api/languages/list")
+      .willReturn(okJson("{\n"
+        + "  \"languages\": []\n"
+        + "}")));
+
+    sonarqube.stubFor(get("/api/metrics/search?ps=500&p=1")
+      .willReturn(okJson("{\n"
+        + "  \"metrics\": [],\n"
+        + "  \"total\": 0,\n"
+        + "  \"p\": 1,\n"
+        + "  \"ps\": 100"
+        + "}")));
+
+    sonarqube.stubFor(post("/api/ce/submit?projectKey=" + PROJECT_KEY)
+      .willReturn(aResponse()
+        .withResponseBody(protobufBody(Ce.SubmitResponse.newBuilder()
+          .build()))));
+  }
+
+  @Test
+  void should_fail_if_invalid_json_input() {
+    var in = new ByteArrayInputStream("}".getBytes());
+    var e = assertThrows(IllegalArgumentException.class, () -> ScannerMain.run(in));
+    assertThat(e).hasMessage("Failed to parse JSON input");
+
+    assertThat(logTester.logs()).contains("Starting SonarScanner Engine...");
+  }
+
+  @Test
+  void should_warn_if_null_property_key() {
+    try {
+      ScannerMain.run(new ByteArrayInputStream("{\"scannerProperties\": [{\"value\": \"aValueWithoutKey\"}]}".getBytes()));
+    } catch (Exception ignored) {
+    }
+    assertThat(logTester.logs()).contains("Ignoring property with null key: 'aValueWithoutKey'");
+  }
+
+  @Test
+  void should_warn_if_duplicate_property_keys() {
+    try {
+      ScannerMain.run(new ByteArrayInputStream("{\"scannerProperties\": [{\"key\": \"aKey\"}, {\"key\": \"aKey\"}]}".getBytes()));
+    } catch (Exception ignored) {
+    }
+    assertThat(logTester.logs()).contains("Duplicated properties with key: 'aKey'");
+  }
+
+  @Test
+  void should_warn_if_null_property() {
+    try {
+      ScannerMain.run(new ByteArrayInputStream("{\"scannerProperties\": [{\"key\": \"aKey\", \"value\": \"aValue\"},]}".getBytes()));
+    } catch (Exception ignored) {
+    }
+    assertThat(logTester.logs()).contains("Ignoring null property");
+  }
+
+  /**
+   * For now this test is just checking that the scanner completes successfully, with no input files, and mocking server responses to the bare minimum.
+   */
+  @Test
+  void should_complete_successfully(@TempDir Path baseDir) {
+
+    ScannerMain.run(new ByteArrayInputStream(("{\"scannerProperties\": ["
+      + "{\"key\": \"sonar.host.url\", \"value\": \"" + sonarqube.baseUrl() + "\"},"
+      + "{\"key\": \"sonar.projectKey\", \"value\": \"" + PROJECT_KEY + "\"},"
+      + "{\"key\": \"sonar.projectBaseDir\", \"value\": \"" + baseDir + "\"}"
+      + "]}").getBytes()));
+
+    assertThat(logTester.logs()).contains("SonarScanner Engine completed successfully");
+  }
+
+  @Test
+  void should_enable_verbose(@TempDir Path baseDir) {
+
+    ScannerMain.run(new ByteArrayInputStream(("{\"scannerProperties\": ["
+      + "{\"key\": \"sonar.host.url\", \"value\": \"" + sonarqube.baseUrl() + "\"},"
+      + "{\"key\": \"sonar.projectKey\", \"value\": \"" + PROJECT_KEY + "\"},"
+      + "{\"key\": \"sonar.projectBaseDir\", \"value\": \"" + baseDir + "\"}"
+      + "]}").getBytes()));
+
+    assertThat(logTester.logs(Level.DEBUG)).isEmpty();
+
+    ScannerMain.run(new ByteArrayInputStream(("{\"scannerProperties\": ["
+      + "{\"key\": \"sonar.host.url\", \"value\": \"" + sonarqube.baseUrl() + "\"},"
+      + "{\"key\": \"sonar.projectKey\", \"value\": \"" + PROJECT_KEY + "\"},"
+      + "{\"key\": \"sonar.projectBaseDir\", \"value\": \"" + baseDir + "\"},"
+      + "{\"key\": \"sonar.verbose\", \"value\": \"true\"}"
+      + "]}").getBytes()));
+
+    assertThat(logTester.logs(Level.DEBUG)).isNotEmpty();
+  }
+}
diff --git a/sonar-scanner-engine/src/it/java/testutils/TestUtils.java b/sonar-scanner-engine/src/it/java/testutils/TestUtils.java
new file mode 100644 (file)
index 0000000..9240d36
--- /dev/null
@@ -0,0 +1,39 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 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 testutils;
+
+import com.github.tomakehurst.wiremock.http.Body;
+import com.github.tomakehurst.wiremock.http.ContentTypeHeader;
+import com.google.protobuf.Message;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+public class TestUtils {
+
+  public static Body protobufBody(Message message) {
+    var baos = new ByteArrayOutputStream();
+    try {
+      message.writeTo(baos);
+      return Body.ofBinaryOrText(baos.toByteArray(), ContentTypeHeader.absent());
+    } catch (IOException e) {
+      throw new RuntimeException(e);
+    }
+  }
+}
index a7606c4c98b92908f3a7da43354e4afbedfb1205..8ff58ff479f4e12b34e4a84e965395a17e52ecfc 100644 (file)
@@ -74,13 +74,16 @@ public final class LoggingConfiguration {
   }
 
   public LoggingConfiguration setVerbose(Map<String, String> props) {
+    verbose = isVerboseEnabled(props);
+    return setVerbose(verbose);
+  }
+
+  public static boolean isVerboseEnabled(Map<String, String> props) {
     String logLevel = props.get("sonar.log.level");
     String deprecatedProfilingLevel = props.get("sonar.log.profilingLevel");
-    verbose = "true".equals(props.get("sonar.verbose")) ||
+    return "true".equals(props.get("sonar.verbose")) ||
       "DEBUG".equals(logLevel) || "TRACE".equals(logLevel) ||
       "BASIC".equals(deprecatedProfilingLevel) || "FULL".equals(deprecatedProfilingLevel);
-
-    return setVerbose(verbose);
   }
 
   public LoggingConfiguration setRootLevel(String level) {
diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/ScannerLogbackEncoder.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/ScannerLogbackEncoder.java
new file mode 100644 (file)
index 0000000..bbad390
--- /dev/null
@@ -0,0 +1,82 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 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.scanner.bootstrap;
+
+import ch.qos.logback.classic.pattern.ThrowableProxyConverter;
+import ch.qos.logback.classic.spi.ILoggingEvent;
+import ch.qos.logback.classic.spi.IThrowableProxy;
+import ch.qos.logback.core.CoreConstants;
+import ch.qos.logback.core.encoder.EncoderBase;
+import org.apache.commons.text.StringEscapeUtils;
+
+import static ch.qos.logback.core.CoreConstants.COMMA_CHAR;
+import static ch.qos.logback.core.CoreConstants.DOUBLE_QUOTE_CHAR;
+import static ch.qos.logback.core.CoreConstants.UTF_8_CHARSET;
+
+public class ScannerLogbackEncoder extends EncoderBase<ILoggingEvent> {
+
+  private static final byte[] EMPTY_BYTES = new byte[0];
+  private static final char OPEN_OBJ = '{';
+  private static final char CLOSE_OBJ = '}';
+  private static final char VALUE_SEPARATOR = COMMA_CHAR;
+  private static final char QUOTE = DOUBLE_QUOTE_CHAR;
+  private static final String QUOTE_COL = "\":";
+
+  private final ThrowableProxyConverter tpc = new ThrowableProxyConverter();
+
+  @Override
+  public byte[] headerBytes() {
+    return EMPTY_BYTES;
+  }
+
+  @Override
+  public byte[] encode(ILoggingEvent event) {
+    StringBuilder sb = new StringBuilder();
+    sb.append(OPEN_OBJ);
+    var level = event.getLevel();
+    if (level != null) {
+      appenderMember(sb, "level", level.levelStr);
+      sb.append(VALUE_SEPARATOR);
+    }
+
+    appenderMember(sb, "message", StringEscapeUtils.escapeJson(event.getFormattedMessage()));
+
+    IThrowableProxy tp = event.getThrowableProxy();
+    String stackTrace = null;
+    if (tp != null) {
+      sb.append(VALUE_SEPARATOR);
+      stackTrace = tpc.convert(event);
+      appenderMember(sb, "stacktrace", StringEscapeUtils.escapeJson(stackTrace));
+    }
+
+    sb.append(CLOSE_OBJ);
+    sb.append(CoreConstants.JSON_LINE_SEPARATOR);
+    return sb.toString().getBytes(UTF_8_CHARSET);
+  }
+
+  private static void appenderMember(StringBuilder sb, String key, String value) {
+    sb.append(QUOTE).append(key).append(QUOTE_COL).append(QUOTE).append(value).append(QUOTE);
+  }
+
+  @Override
+  public byte[] footerBytes() {
+    return EMPTY_BYTES;
+  }
+}
diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/ScannerMain.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/ScannerMain.java
new file mode 100644 (file)
index 0000000..405f586
--- /dev/null
@@ -0,0 +1,125 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 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.scanner.bootstrap;
+
+import ch.qos.logback.classic.Level;
+import ch.qos.logback.classic.Logger;
+import com.google.gson.Gson;
+import com.google.gson.annotations.SerializedName;
+import java.io.BufferedReader;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import javax.annotation.CheckForNull;
+import org.jetbrains.annotations.NotNull;
+import org.slf4j.LoggerFactory;
+import org.sonar.batch.bootstrapper.EnvironmentInformation;
+import org.sonar.batch.bootstrapper.LoggingConfiguration;
+
+import static org.sonar.batch.bootstrapper.LoggingConfiguration.LEVEL_ROOT_DEFAULT;
+import static org.sonar.batch.bootstrapper.LoggingConfiguration.LEVEL_ROOT_VERBOSE;
+
+public class ScannerMain {
+
+  private static final org.slf4j.Logger LOG = LoggerFactory.getLogger(ScannerMain.class);
+
+  private static final String SCANNER_APP_KEY = "sonar.scanner.app";
+  private static final String SCANNER_APP_VERSION_KEY = "sonar.scanner.appVersion";
+
+  public static void main(String... args) {
+    try {
+      run(System.in);
+    } catch (Exception e) {
+      LOG.error("Error during SonarScanner Engine execution", e);
+      System.exit(1);
+    }
+  }
+
+  public static void run(InputStream in) {
+    LOG.info("Starting SonarScanner Engine...");
+
+    var properties = parseInputProperties(in);
+
+    configureLogLevel(properties);
+
+    runScannerEngine(properties);
+
+    LOG.info("SonarScanner Engine completed successfully");
+  }
+
+  private static @NotNull Map<String, String> parseInputProperties(InputStream in) {
+    Map<String, String> properties = new HashMap<>();
+    var input = parseJsonInput(in);
+    if (input != null && input.scannerProperties != null) {
+      input.scannerProperties.forEach(prop -> {
+        if (prop == null) {
+          LOG.warn("Ignoring null property");
+        } else if (prop.key == null) {
+          LOG.warn("Ignoring property with null key: '{}'", prop.value);
+        } else {
+          if (properties.containsKey(prop.key)) {
+            LOG.warn("Duplicated properties with key: '{}'", prop.key);
+          }
+          properties.put(prop.key, prop.value);
+        }
+      });
+    }
+    return properties;
+  }
+
+  @CheckForNull
+  private static Input parseJsonInput(InputStream in) {
+    try (var reader = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8))) {
+      return new Gson().fromJson(reader, Input.class);
+    } catch (Exception e) {
+      throw new IllegalArgumentException("Failed to parse JSON input", e);
+    }
+  }
+
+  private static void runScannerEngine(Map<String, String> properties) {
+    var scannerAppKey = properties.get(SCANNER_APP_KEY);
+    var scannerAppVersion = properties.get(SCANNER_APP_VERSION_KEY);
+    var env = new EnvironmentInformation(scannerAppKey, scannerAppVersion);
+    SpringGlobalContainer.create(properties, List.of(env)).execute();
+  }
+
+  private static void configureLogLevel(Map<String, String> properties) {
+    var verbose = LoggingConfiguration.isVerboseEnabled(properties);
+    var rootLogger = (Logger) LoggerFactory.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME);
+    rootLogger.setLevel(Level.toLevel(verbose ? LEVEL_ROOT_VERBOSE : LEVEL_ROOT_DEFAULT));
+  }
+
+  private static class Input {
+    @SerializedName("scannerProperties")
+    private List<ScannerProperty> scannerProperties;
+  }
+
+  private static class ScannerProperty {
+    @SerializedName("key")
+    private String key;
+
+    @SerializedName("value")
+    private String value;
+  }
+
+}
diff --git a/sonar-scanner-engine/src/main/resources/logback-shared.xml b/sonar-scanner-engine/src/main/resources/logback-shared.xml
new file mode 100644 (file)
index 0000000..83d4902
--- /dev/null
@@ -0,0 +1,16 @@
+<!-- This file is shared between the old bootstrapping entry point and new bootstrapping -->
+<included>
+  <!-- BeanUtils generate too many DEBUG logs when sonar.verbose is set -->
+  <logger name="org.apache.commons.beanutils.converters" level="WARN"/>
+
+  <!-- FileSnapshot generate too many DEBUG logs when sonar.verbose is set -->
+  <logger name="org.eclipse.jgit.internal.storage.file" level="INFO"/>
+
+  <!-- Spring generates too many DEBUG logs when sonar.verbose is set -->
+  <logger name="org.springframework" level="INFO"/>
+
+  <!-- AbstractApplicationContext generate too verbose warning if warn is Enabled -->
+  <logger name="org.springframework.context.annotation.AnnotationConfigApplicationContext" level="ERROR"/>
+
+  <logger name="org.sonar.core.platform.PriorityBeanFactory" level="INFO"/>
+</included>
diff --git a/sonar-scanner-engine/src/main/resources/logback.xml b/sonar-scanner-engine/src/main/resources/logback.xml
new file mode 100644 (file)
index 0000000..4080ae5
--- /dev/null
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE configuration>
+<!-- This logback configuration is used when the scanner engine is bootstrapped using the SonarScannerCli class. -->
+
+<configuration>
+  <import class="ch.qos.logback.core.ConsoleAppender"/>
+  <include resource="logback-shared.xml"/>
+
+  <appender name="STDOUT" class="ConsoleAppender">
+    <encoder class="org.sonar.scanner.bootstrap.ScannerLogbackEncoder"/>
+  </appender>
+
+  <root level="info">
+    <appender-ref ref="STDOUT"/>
+  </root>
+</configuration>
\ No newline at end of file
index 52120f96acf2089f28e9aba91d853369c24913da..65b12b2ed24bfeab07afb642dfcbf86f4c5faa71 100644 (file)
@@ -1,5 +1,6 @@
 <?xml version="1.0" encoding="UTF-8" ?>
 <configuration debug="false">
+  <include resource="logback-shared.xml"/>
 
   <!--
   
     </encoder>
   </appender>
 
-  <!-- BeanUtils generate too many DEBUG logs when sonar.verbose is set -->
-  <logger name="org.apache.commons.beanutils.converters" level="WARN"/>
-
-  <!-- FileSnapshot generate too many DEBUG logs when sonar.verbose is set -->
-  <logger name="org.eclipse.jgit.internal.storage.file" level="INFO"/>
-
-  <!-- Spring generates too many DEBUG logs when sonar.verbose is set -->
-  <logger name="org.springframework" level="INFO"/>
-
-  <!-- AbstractApplicationContext generate too verbose warning if warn is Enabled -->
-  <logger name="org.springframework.context.annotation.AnnotationConfigApplicationContext" level="ERROR"/>
-
-  <logger name="org.sonar.core.platform.PriorityBeanFactory" level="INFO"/>
-
-  <!-- sonar.showSql -->
-  <!-- see also org.sonar.db.MyBatis#configureLogback() -->
-  <logger name="org.mybatis" level="${SQL_LOGGER_LEVEL:-WARN}"/>
-  <logger name="org.apache.ibatis" level="${SQL_LOGGER_LEVEL:-WARN}"/>
-  <logger name="java.sql" level="${SQL_LOGGER_LEVEL:-WARN}"/>
-  <logger name="java.sql.ResultSet" level="WARN"/>
-
   <root level="${ROOT_LOGGER_LEVEL}">
     <!-- sonar.verbose -->
     <appender-ref ref="STDOUT"/>
diff --git a/sonar-scanner-engine/src/test/java/org/sonar/scanner/bootstrap/ScannerLogbackEncoderTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/bootstrap/ScannerLogbackEncoderTest.java
new file mode 100644 (file)
index 0000000..e63985d
--- /dev/null
@@ -0,0 +1,76 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 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.scanner.bootstrap;
+
+import ch.qos.logback.classic.Level;
+import ch.qos.logback.classic.spi.ILoggingEvent;
+import ch.qos.logback.classic.spi.ThrowableProxy;
+import java.nio.charset.StandardCharsets;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+class ScannerLogbackEncoderTest {
+  
+  ScannerLogbackEncoder underTest = new ScannerLogbackEncoder();
+  
+  @Test
+  void no_headers_and_footers() {
+    assertThat(underTest.headerBytes()).isEmpty();
+    assertThat(underTest.footerBytes()).isEmpty();
+  }
+
+  @Test
+  void should_encode_when_no_level_and_no_stacktrace() {
+    var logEvent = mock(ILoggingEvent.class);
+    when(logEvent.getLevel()).thenReturn(null);
+    when(logEvent.getFormattedMessage()).thenReturn("message");
+
+    var bytes = underTest.encode(logEvent);
+
+    assertThat(new String(bytes, StandardCharsets.UTF_8)).isEqualTo("{\"message\":\"message\"}\n");
+  }
+
+  @Test
+  void should_encode_when_no_stacktrace() {
+    var logEvent = mock(ILoggingEvent.class);
+    when(logEvent.getLevel()).thenReturn(Level.DEBUG);
+    when(logEvent.getFormattedMessage()).thenReturn("message");
+
+    var bytes = underTest.encode(logEvent);
+
+    assertThat(new String(bytes, StandardCharsets.UTF_8)).isEqualTo("{\"level\":\"DEBUG\",\"message\":\"message\"}\n");
+  }
+
+  @Test
+  void should_encode_with_stacktrace() {
+    var logEvent = mock(ILoggingEvent.class);
+    when(logEvent.getLevel()).thenReturn(Level.DEBUG);
+    when(logEvent.getFormattedMessage()).thenReturn("message");
+    when(logEvent.getThrowableProxy()).thenReturn(new ThrowableProxy(new IllegalArgumentException("foo")));
+
+    var bytes = underTest.encode(logEvent);
+
+    assertThat(new String(bytes, StandardCharsets.UTF_8)).isEqualTo("{\"level\":\"DEBUG\",\"message\":\"message\",\"stacktrace\":\"java.lang.IllegalArgumentException: foo\\n\"}\n");
+  }
+
+}
\ No newline at end of file