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);
+ }
+}