diff --git a/backend/commons/pom.xml b/backend/commons/pom.xml index 63a957c2d7..1f9a2e2e67 100644 --- a/backend/commons/pom.xml +++ b/backend/commons/pom.xml @@ -55,6 +55,19 @@ logback-classic test + + io.sentry + sentry + 7.16.0 + + + javax.inject + javax.inject + + + org.apache.commons + commons-lang3 + diff --git a/backend/commons/src/main/java/org/sonarsource/sonarlint/core/commons/log/LogOutputDelegator.java b/backend/commons/src/main/java/org/sonarsource/sonarlint/core/commons/log/LogOutputDelegator.java index 2025562e07..1832081268 100644 --- a/backend/commons/src/main/java/org/sonarsource/sonarlint/core/commons/log/LogOutputDelegator.java +++ b/backend/commons/src/main/java/org/sonarsource/sonarlint/core/commons/log/LogOutputDelegator.java @@ -19,6 +19,7 @@ */ package org.sonarsource.sonarlint.core.commons.log; +import io.sentry.Sentry; import java.util.Optional; import javax.annotation.CheckForNull; import javax.annotation.Nullable; @@ -42,6 +43,7 @@ void log(@Nullable String formattedMessage, Level level, @Nullable String stackT void log(@Nullable String formattedMessage, Level level, @Nullable Throwable t) { String stacktrace = null; if (t != null) { + Sentry.captureException(t); stacktrace = LogOutput.stackTraceToString(t); } if (formattedMessage != null || t != null) { diff --git a/backend/commons/src/main/java/org/sonarsource/sonarlint/core/commons/monitoring/DogfoodEnvironmentDetectionService.java b/backend/commons/src/main/java/org/sonarsource/sonarlint/core/commons/monitoring/DogfoodEnvironmentDetectionService.java new file mode 100644 index 0000000000..b828c4927e --- /dev/null +++ b/backend/commons/src/main/java/org/sonarsource/sonarlint/core/commons/monitoring/DogfoodEnvironmentDetectionService.java @@ -0,0 +1,34 @@ +/* + * SonarLint Core - Commons + * Copyright (C) 2016-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.sonarsource.sonarlint.core.commons.monitoring; + +import javax.inject.Named; +import javax.inject.Singleton; +import org.apache.commons.lang3.SystemUtils; + +@Named +@Singleton +public class DogfoodEnvironmentDetectionService { + private static final String SONARSOURCE_DOGFOODING_ENV_VAR_KEY = "SONARSOURCE_DOGFOODING"; + + public boolean isDogfoodEnvironment() { + return "1".equals(SystemUtils.getEnvironmentVariable(SONARSOURCE_DOGFOODING_ENV_VAR_KEY, "0")); + } +} diff --git a/backend/commons/src/main/java/org/sonarsource/sonarlint/core/commons/monitoring/MonitoringInitializationParams.java b/backend/commons/src/main/java/org/sonarsource/sonarlint/core/commons/monitoring/MonitoringInitializationParams.java new file mode 100644 index 0000000000..7a0cbda44b --- /dev/null +++ b/backend/commons/src/main/java/org/sonarsource/sonarlint/core/commons/monitoring/MonitoringInitializationParams.java @@ -0,0 +1,44 @@ +/* + * SonarLint Core - Commons + * Copyright (C) 2016-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.sonarsource.sonarlint.core.commons.monitoring; + +public class MonitoringInitializationParams { + private final String productKey; + private final String sonarQubeForIdeVersion; + private final String ideVersion; + + public MonitoringInitializationParams(String productKey, String sonarQubeForIdeVersion, String ideVersion) { + this.productKey = productKey; + this.sonarQubeForIdeVersion = sonarQubeForIdeVersion; + this.ideVersion = ideVersion; + } + + public String getProductKey() { + return productKey; + } + + public String getSonarQubeForIdeVersion() { + return sonarQubeForIdeVersion; + } + + public String getIdeVersion() { + return ideVersion; + } +} diff --git a/backend/commons/src/main/java/org/sonarsource/sonarlint/core/commons/monitoring/MonitoringService.java b/backend/commons/src/main/java/org/sonarsource/sonarlint/core/commons/monitoring/MonitoringService.java new file mode 100644 index 0000000000..61a265ce0c --- /dev/null +++ b/backend/commons/src/main/java/org/sonarsource/sonarlint/core/commons/monitoring/MonitoringService.java @@ -0,0 +1,75 @@ +/* + * SonarLint Core - Commons + * Copyright (C) 2016-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.sonarsource.sonarlint.core.commons.monitoring; + +import io.sentry.Sentry; +import javax.inject.Inject; +import javax.inject.Named; +import javax.inject.Singleton; +import org.apache.commons.lang3.SystemUtils; +import org.sonarsource.sonarlint.core.commons.SonarLintCoreVersion; +import org.sonarsource.sonarlint.core.commons.log.SonarLintLogger; + +@Named +@Singleton +public class MonitoringService { + private static final SonarLintLogger LOG = SonarLintLogger.get(); + + private final MonitoringInitializationParams initializeParams; + private final DogfoodEnvironmentDetectionService dogfoodEnvDetectionService; + + @Inject + public MonitoringService(MonitoringInitializationParams initializeParams, DogfoodEnvironmentDetectionService dogfoodEnvDetectionService) { + this.initializeParams = initializeParams; + this.dogfoodEnvDetectionService = dogfoodEnvDetectionService; + + this.init(); + } + + public void init() { + var productKey = initializeParams.getProductKey(); + var environment = "dogfood"; + var sonarQubeForIDEVersion = initializeParams.getSonarQubeForIdeVersion(); + var ideVersion = initializeParams.getIdeVersion(); + var releaseVersion = SonarLintCoreVersion.get(); + var platform = SystemUtils.OS_NAME; + var architecture = SystemUtils.OS_ARCH; + + if (dogfoodEnvDetectionService.isDogfoodEnvironment()) { + LOG.info("Initializing Sentry"); + Sentry.init(sentryOptions -> { + sentryOptions.setDsn("https://ad1c1fe3cb2b12fc2d191ecd25f89866@o1316750.ingest.us.sentry.io/4508201175089152"); + sentryOptions.setRelease(releaseVersion); + sentryOptions.setEnvironment(environment); + sentryOptions.setTag("productKey", productKey); + sentryOptions.setTag("sonarQubeForIDEVersion", sonarQubeForIDEVersion); + sentryOptions.setTag("ideVersion", ideVersion); + sentryOptions.setTag("platform", platform); + sentryOptions.setTag("architecture", architecture); + sentryOptions.addInAppInclude("org.sonarsource.sonarlint"); + + sentryOptions.setBeforeSend((event, hint) -> { + LOG.debug("Sending Sentry event" + event); + return event; + }); + }); + } + } +} diff --git a/backend/core/src/main/java/org/sonarsource/sonarlint/core/spring/SonarLintSpringAppConfig.java b/backend/core/src/main/java/org/sonarsource/sonarlint/core/spring/SonarLintSpringAppConfig.java index c2e87e992e..fe7129078f 100644 --- a/backend/core/src/main/java/org/sonarsource/sonarlint/core/spring/SonarLintSpringAppConfig.java +++ b/backend/core/src/main/java/org/sonarsource/sonarlint/core/spring/SonarLintSpringAppConfig.java @@ -45,11 +45,14 @@ import org.sonarsource.sonarlint.core.TokenGeneratorHelper; import org.sonarsource.sonarlint.core.VersionSoonUnsupportedHelper; import org.sonarsource.sonarlint.core.analysis.AnalysisEngineCache; -import org.sonarsource.sonarlint.core.analysis.UserAnalysisPropertiesRepository; import org.sonarsource.sonarlint.core.analysis.AnalysisService; import org.sonarsource.sonarlint.core.analysis.NodeJsService; +import org.sonarsource.sonarlint.core.analysis.UserAnalysisPropertiesRepository; import org.sonarsource.sonarlint.core.branch.SonarProjectBranchTrackingService; import org.sonarsource.sonarlint.core.commons.SonarLintUserHome; +import org.sonarsource.sonarlint.core.commons.monitoring.DogfoodEnvironmentDetectionService; +import org.sonarsource.sonarlint.core.commons.monitoring.MonitoringInitializationParams; +import org.sonarsource.sonarlint.core.commons.monitoring.MonitoringService; import org.sonarsource.sonarlint.core.embedded.server.AwaitingUserTokenFutureRepository; import org.sonarsource.sonarlint.core.embedded.server.EmbeddedServer; import org.sonarsource.sonarlint.core.embedded.server.GeneratedUserTokenHandler; @@ -191,7 +194,9 @@ FindingReportingService.class, PreviouslyRaisedFindingsRepository.class, UserAnalysisPropertiesRepository.class, - OpenFilesRepository.class + OpenFilesRepository.class, + DogfoodEnvironmentDetectionService.class, + MonitoringService.class }) public class SonarLintSpringAppConfig { @@ -237,6 +242,13 @@ HttpClientProvider provideHttpClientProvider(InitializeParams params, @Named("us proxySelector, proxyCredentialsProvider); } + @Bean + MonitoringInitializationParams provideMonitoringInitParams(InitializeParams params) { + return new MonitoringInitializationParams(params.getTelemetryConstantAttributes().getProductKey(), + params.getTelemetryConstantAttributes().getProductVersion(), + params.getTelemetryConstantAttributes().getIdeVersion()); + } + private static HttpConfig adapt(HttpConfigurationDto dto, @Nullable Path sonarlintUserHome) { return new HttpConfig(adapt(dto.getSslConfiguration(), sonarlintUserHome), toTimeout(dto.getConnectTimeout()), toTimeout(dto.getSocketTimeout()), toTimeout(dto.getConnectionRequestTimeout()), toTimeout(dto.getResponseTimeout())); diff --git a/medium-tests/src/test/java/mediumtest/monitoring/MonitoringMediumTest.java b/medium-tests/src/test/java/mediumtest/monitoring/MonitoringMediumTest.java new file mode 100644 index 0000000000..6c2757aa61 --- /dev/null +++ b/medium-tests/src/test/java/mediumtest/monitoring/MonitoringMediumTest.java @@ -0,0 +1,96 @@ +/* + * SonarLint Core - Medium Tests + * Copyright (C) 2016-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 mediumtest.monitoring; + +import java.nio.file.Path; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.ExecutionException; +import mediumtest.fixtures.SonarLintBackendFixture; +import mediumtest.fixtures.SonarLintTestRpcServer; +import mediumtest.fixtures.TestPlugin; +import org.assertj.core.api.Assumptions; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; +import org.sonarsource.sonarlint.core.rpc.protocol.common.ClientFileDto; + +import static mediumtest.fixtures.SonarLintBackendFixture.newBackend; +import static mediumtest.fixtures.SonarLintBackendFixture.newFakeClient; +import static org.assertj.core.api.Assertions.assertThat; +import static testutils.AnalysisUtils.analyzeFileAndGetIssues; +import static testutils.AnalysisUtils.createFile; + +class MonitoringMediumTest { + + private static SonarLintBackendFixture.FakeSonarLintRpcClient client; + + private static final String A_JAVA_FILE_PATH = "Foo.java"; + private static final String CONFIGURATION_SCOPE_ID = "configScopeId"; + private static final List logs = new CopyOnWriteArrayList<>(); + private static SonarLintTestRpcServer backend; + // commercial plugins might not be available + // (if you pass -Dcommercial to maven, a profile will be activated that downloads the commercial plugins) + private static final boolean COMMERCIAL_ENABLED = System.getProperty("commercial") != null; + + @BeforeAll + static void checkDogfoodingVariableSet() { + Assumptions.assumeThat(System.getenv("SONARSOURCE_DOGFOODING")) + .withFailMessage("Dogfooding environment variable is not set, skipping tests") + .isEqualTo("1"); + } + + @AfterAll + static void stop() throws ExecutionException, InterruptedException { + Thread.sleep(5_000); + if (backend != null) { + backend.shutdown().get(); + } + } + + @AfterEach + void cleanup() { + client.cleanRaisedIssues(); + } + + @Test + void shouldPostMonitoringPingToSentry(@TempDir Path baseDir) throws Throwable { + var content = "function foo() {\n" + + " let x;\n" + + " let y; //NOSONAR\n" + + "}"; + var inputFile = createFile(baseDir, "foo.js", content); + + client = newFakeClient() + .withInitialFs(CONFIGURATION_SCOPE_ID, List.of( + new ClientFileDto(inputFile.toUri(), baseDir.relativize(inputFile), CONFIGURATION_SCOPE_ID, false, null, inputFile, null, null, true) + )) + .build(); + + backend = newBackend() + .withUnboundConfigScope(CONFIGURATION_SCOPE_ID) + .withStandaloneEmbeddedPluginAndEnabledLanguage(TestPlugin.JAVASCRIPT) + .build(client); + + analyzeFileAndGetIssues(inputFile.toUri(), client, backend, CONFIGURATION_SCOPE_ID); + } +}