From 8dc5228eb0746f8c0532c8ef15ab9c9f8ca81065 Mon Sep 17 00:00:00 2001 From: Nikita Iarychenko Date: Tue, 10 Dec 2024 13:21:11 +0400 Subject: [PATCH] OPENIDE #49 Implement sending default errors from OpenIDE (cherry picked from commit 31c0cf21f935f6379cf458e5fab0f211cb6c5aa7) (cherry picked from commit 2e3d348b7c27f960a31a44ff8447c2e26135685c) (cherry picked from commit 690310ef3cf4863ac75ebb236b98a704a6ee50d5) (cherry picked from commit 9428cb679018db11537b20c2ab4486966d9d3558) (cherry picked from commit 8e8bc23388641565c494da188e29d18b6bc4ad91) --- .idea/libraries/io_sentry.xml | 18 +++ .../intellij.platform.ide.impl.iml | 1 + .../messages/DiagnosticBundle.properties | 20 +-- .../com/intellij/diagnostic/ITNReporter.kt | 3 - .../src/com/intellij/diagnostic/ITNUtils.kt | 118 ++++++++++++++++++ .../intellij/diagnostic/UniqueComputerId.kt | 71 +++++++++++ .../src/META-INF/PlatformExtensions.xml | 1 + 7 files changed, 219 insertions(+), 13 deletions(-) create mode 100644 .idea/libraries/io_sentry.xml create mode 100644 platform/platform-impl/src/com/intellij/diagnostic/ITNUtils.kt create mode 100644 platform/platform-impl/src/com/intellij/diagnostic/UniqueComputerId.kt diff --git a/.idea/libraries/io_sentry.xml b/.idea/libraries/io_sentry.xml new file mode 100644 index 000000000000..79d0127724dd --- /dev/null +++ b/.idea/libraries/io_sentry.xml @@ -0,0 +1,18 @@ + + + + + + 927f0dfd76c9262f3d755345d72468138ae1bdba5d8f20c44f79ba2e1efcdb91 + + + + + + + + + + + + \ No newline at end of file diff --git a/platform/platform-impl/intellij.platform.ide.impl.iml b/platform/platform-impl/intellij.platform.ide.impl.iml index 5ccfbd270d9e..3926315fb916 100644 --- a/platform/platform-impl/intellij.platform.ide.impl.iml +++ b/platform/platform-impl/intellij.platform.ide.impl.iml @@ -176,5 +176,6 @@ + \ No newline at end of file diff --git a/platform/platform-impl/resources/messages/DiagnosticBundle.properties b/platform/platform-impl/resources/messages/DiagnosticBundle.properties index d4509d142108..083517e99b5e 100644 --- a/platform/platform-impl/resources/messages/DiagnosticBundle.properties +++ b/platform/platform-impl/resources/messages/DiagnosticBundle.properties @@ -23,28 +23,28 @@ error.list.message.submitted=Submitted error.list.message.submitted.as.link=Submitted as {1} error.list.message.submission.failed=Submission failed error.list.message.submission.failed.details=Submission failed ({0}) -error.dialog.foreign.plugin.warning=This plugin is not a production of JetBrains. Please report the problem to {0}. -error.dialog.foreign.plugin.warning.unnamed=This plugin is not a production of JetBrains. Please report the problem to the plugin vendor. -error.dialog.foreign.plugin.warning.unknown=This plugin is not a production of JetBrains. Please report the problem to the plugin vendor. +error.dialog.foreign.plugin.warning=This plugin is not a production of OpenIDE. Please report the problem to {0}. +error.dialog.foreign.plugin.warning.unnamed=This plugin is not a production of OpenIDE. Please report the problem to the plugin vendor. +error.dialog.foreign.plugin.warning.unknown=This plugin is not a production of OpenIDE. Please report the problem to the plugin vendor. error.dialog.comment.prompt=Comment on what you were doing when the exception occurred error.dialog.notice.label=By submitting this report, you agree to the terms and conditions of the privacy policy error.dialog.notice.label.expanded=By submitting this report, you agree to the terms and conditions of the privacy policy error.dialog.submit.anonymous=Submit anonymously or Log In error.dialog.submit.named=Submit as {0} or change account -error.report.to.jetbrains.action=&Report to JetBrains +error.report.to.jetbrains.action=&Report to OpenIDE error.report.all.action=Report All error.report.and.clear.all.action=Report and Clear All error.report.impossible.action=Report Exception error.report.impossible.tooltip=The plugin vendor didn't implement the exception reporting component. error.dialog.clear.all.action=&Clear all error.dialog.notice.anonymous=I agree to my hardware configuration, software configuration, product information, and the error details shown above \ - being used by JetBrains s.r.o. ("JetBrains") to let JetBrains improve its products and to provide me with support service \ - in accordance with the end-user agreement of JetBrains Exception Analyzer. + being used by OpenIDE to let OpenIDE improve its products and to provide me with support service \ + in accordance with the end-user agreement of OpenIDE Exception Analyzer. error.dialog.notice.third-party.plugin.exception=I agree that my hardware configuration, software configuration, product information, and the error details shown above\ - \ might be used by Jetbrains s.r.o. (\u201CJetBrains\u201D) and the author of the plugin to improve their products and provide me with support service by JetBrains\ - \ in accordance with the relevant Agreement.\ - \ If you submit the report containing any personal data, please note that such data will be shared with JetBrains and the plugin author.\ - \ JetBrains is not responsible for any behaviour of any third-party plugins and their vendors, including processing of your personal data. + \ might be used by OpenIDE and the author of the plugin to improve their products and provide me with support service by OpenIDE\ + \ in accordance with the relevant Agreement.\ + \ If you submit the report containing any personal data, please note that such data will be shared with OpenIDE and the plugin author.\ + \ OpenIDE is not responsible for any behaviour of any third-party plugins and their vendors, including processing of your personal data. error.dialog.notice.third-party.plugin.send=Report to the Third-Party Plugin title.submitting.error.report=Submitting error report error.report.submitted=Report submitted diff --git a/platform/platform-impl/src/com/intellij/diagnostic/ITNReporter.kt b/platform/platform-impl/src/com/intellij/diagnostic/ITNReporter.kt index b9fe3225f11a..018c4c7cd2fb 100644 --- a/platform/platform-impl/src/com/intellij/diagnostic/ITNReporter.kt +++ b/platform/platform-impl/src/com/intellij/diagnostic/ITNReporter.kt @@ -119,9 +119,6 @@ open class ITNReporter internal constructor(private val postUrl: URI?) : ErrorRe val content = DiagnosticBundle.message("error.report.gratitude") val title = DiagnosticBundle.message("error.report.submitted") val notification = Notification("Error Report", title, content, NotificationType.INFORMATION).setImportant(false) - if (reportUrl != null) { - notification.addAction(NotificationAction.createSimpleExpiring(DiagnosticBundle.message("error.report.view.action")) { BrowserUtil.browse(reportUrl) }) - } notification.notify(project) } diff --git a/platform/platform-impl/src/com/intellij/diagnostic/ITNUtils.kt b/platform/platform-impl/src/com/intellij/diagnostic/ITNUtils.kt new file mode 100644 index 000000000000..25b721082c17 --- /dev/null +++ b/platform/platform-impl/src/com/intellij/diagnostic/ITNUtils.kt @@ -0,0 +1,118 @@ +// Copyright (c) Haulmont 2024. All Rights Reserved. +// Use is subject to license terms. +package com.intellij.diagnostic + +import com.intellij.ide.ApplicationActivity +import com.intellij.idea.IdeaLogger +import com.intellij.openapi.application.ApplicationInfo +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.diagnostic.Logger +import com.intellij.openapi.util.SystemInfo +import com.intellij.openapi.util.io.FileUtilRt +import com.intellij.openapi.util.text.StringUtil +import com.intellij.util.ObjectUtils +import io.sentry.Sentry +import io.sentry.SentryEvent +import io.sentry.SentryLevel +import io.sentry.SentryOptions +import io.sentry.protocol.SentryRuntime +import io.sentry.protocol.User +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.launch +import java.util.concurrent.atomic.AtomicBoolean + +object ITNUtils { + val log = Logger.getInstance(ITNUtils::class.java) + + internal fun sendError(error: ITNProxy.ErrorBean) { + val event = SentryEvent() + event.level = SentryLevel.ERROR + event.throwable = (error.event as IdeaReportingEvent).data.throwable + event.release = ApplicationInfo.getInstance().build.asString() + + event.user = User().apply { + id = uniqueComputerId + } + + // set server name to empty to avoid tracking personal data + event.serverName = SystemInfo.getOsNameAndVersion() + event.setExtra("ide.is_eap", ApplicationManager.getApplication().isEAP) + event.setExtra("ide.is_internal", ApplicationManager.getApplication().isInternal) + + val sentryRuntime = SentryRuntime() + val javaVendor = System.getProperty("java.vendor", "") + val javaVersion = System.getProperty("java.version", "") + sentryRuntime.name = "$javaVendor $javaVersion" + event.contexts.setRuntime(sentryRuntime) + + val lastActionId = IdeaLogger.ourLastActionId + if (StringUtil.isNotEmpty(lastActionId)) { + event.setExtra("Last action", lastActionId) + } + + if (StringUtil.isNotEmpty(error.pluginId)) { + event.setExtra("plugin.id", error.pluginId!!) + } + + if (StringUtil.isNotEmpty(error.pluginVersion)) { + event.setExtra("plugin.version", error.pluginVersion!!) + } + + if (StringUtil.isNotEmpty(error.pluginName)) { + event.setExtra("plugin.name", error.pluginName!!) + } + + event.setExtra("Memory", (Runtime.getRuntime().maxMemory() / FileUtilRt.MEGABYTE).toString() + "M") + + if (SystemInfo.isUnix && !SystemInfo.isMac) { + event.setExtra("Current Desktop", ObjectUtils.notNull(System.getenv("XDG_CURRENT_DESKTOP"), "Undefined")) + } + + Sentry.captureEvent(event) + } + + val uniqueComputerId: String get() = UniqueComputerId.value + + fun getEnvironment() = when { + isSnapshot() -> "snapshot" + else -> "production" + } + + private fun isSnapshot() = ApplicationInfo.getInstance().build.asString() + .contains("snapshot", ignoreCase = true) +} + +/** + * Makes Sentry initialization. + */ +class SentryInitializer : ApplicationActivity { + + private val isHeadless by lazy { + ApplicationManager.getApplication().run { isHeadlessEnvironment || isUnitTestMode } + } + + private val isInitialized = AtomicBoolean(false) + + override suspend fun execute() { + if (isHeadless) return + + val isInitializedBefore = isInitialized.getAndSet(true) + if (isInitializedBefore) return + + initComputerId() + + Sentry.init { options: SentryOptions -> + options.dsn = "https://73728607f7e2a336e7cade498caaae0c@sentry-amp.haulmont.com/4" + options.isEnableUncaughtExceptionHandler = false + options.setModulesLoader { emptyMap() } + options.environment = ITNUtils.getEnvironment() + } + } + + private suspend fun initComputerId() = coroutineScope { + launch(Dispatchers.IO) { + UniqueComputerId.precompute() + } + } +} \ No newline at end of file diff --git a/platform/platform-impl/src/com/intellij/diagnostic/UniqueComputerId.kt b/platform/platform-impl/src/com/intellij/diagnostic/UniqueComputerId.kt new file mode 100644 index 000000000000..af7458a8d8cf --- /dev/null +++ b/platform/platform-impl/src/com/intellij/diagnostic/UniqueComputerId.kt @@ -0,0 +1,71 @@ +// Copyright (c) Haulmont 2024. All Rights Reserved. +// Use is subject to license terms. +package com.intellij.diagnostic + +import com.intellij.internal.statistic.eventLog.fus.MachineIdManager +import com.intellij.openapi.diagnostic.logger +import com.intellij.openapi.util.io.FileUtil +import com.intellij.util.SystemProperties +import com.intellij.util.concurrency.annotations.RequiresBackgroundThread +import com.intellij.util.io.DigestUtil +import com.intellij.util.io.bytesToHex +import java.io.File +import java.io.IOException +import java.nio.charset.StandardCharsets +import java.util.* + +object UniqueComputerId { + + private val log = logger() + + @RequiresBackgroundThread + fun precompute() { + // call our lazy value + value + } + + val value by lazy { + val configFile = resolveUserIdFile() + loadIdFromFile(configFile) ?: generateIdAndWrite(configFile) + } + + private fun loadIdFromFile(configFile: File): String? { + if (!configFile.exists()) { + return null + } + return try { + val fileSource = FileUtil.loadFile(configFile, StandardCharsets.UTF_8) + fileSource.trim().ifEmpty { null } + } catch (e: IOException) { + log.warn("Exception during read user id", e) + null + } + } + + private fun generateIdAndWrite(configFile: File): String { + val id = getAnonymizedMachineId() + try { + FileUtil.writeToFile(configFile, id, StandardCharsets.UTF_8) + } catch (e: IOException) { + log.warn("Exception during write user id", e) + } + return id + } + + private fun getAnonymizedMachineId(): String { + var machineId = MachineIdManager.getAnonymizedMachineId("openide.unique.computer.id") + if (machineId != null) { + return machineId + } + log.warn("Getting machine id was failed") + machineId = UUID.randomUUID().toString() + val result = "${System.getProperty("user.name")}-$machineId" + val md = DigestUtil.sha256() + md.update(result.toByteArray()) + return bytesToHex(md.digest()) + } + + private fun resolveUserIdFile(): File { + return File(SystemProperties.getUserHome(), ".openide/user-id.txt") + } +} \ No newline at end of file diff --git a/platform/platform-resources/src/META-INF/PlatformExtensions.xml b/platform/platform-resources/src/META-INF/PlatformExtensions.xml index a0fc423cf6b8..62b7b7c79a47 100644 --- a/platform/platform-resources/src/META-INF/PlatformExtensions.xml +++ b/platform/platform-resources/src/META-INF/PlatformExtensions.xml @@ -2081,6 +2081,7 @@ The refresh itself is toggled by (Preferences | Appearance & Behavior | Syst +