From d7b77269397309deabc6ef5cd543af81c00b9969 Mon Sep 17 00:00:00 2001 From: Nikita Iarychenko Date: Tue, 10 Dec 2024 09:21:11 +0000 Subject: [PATCH] OPENIDE #49 Implement sending default errors from OpenIDE --- .idea/libraries/io_sentry.xml | 18 +++ .../intellij.platform.ide.impl.iml | 1 + .../messages/DiagnosticBundle.properties | 20 +-- .../com/intellij/diagnostic/ITNReporter.kt | 17 +-- .../src/com/intellij/diagnostic/ITNUtils.kt | 118 ++++++++++++++++++ .../intellij/diagnostic/UniqueComputerId.kt | 71 +++++++++++ .../src/META-INF/PlatformExtensions.xml | 1 + 7 files changed, 225 insertions(+), 21 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 3b95af8aba74..a2c372af3653 100644 --- a/platform/platform-impl/intellij.platform.ide.impl.iml +++ b/platform/platform-impl/intellij.platform.ide.impl.iml @@ -141,5 +141,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 b4e7008275ae..f879b82f088a 100644 --- a/platform/platform-impl/resources/messages/DiagnosticBundle.properties +++ b/platform/platform-impl/resources/messages/DiagnosticBundle.properties @@ -22,28 +22,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 37f98fd441d8..4e56cc836237 100644 --- a/platform/platform-impl/src/com/intellij/diagnostic/ITNReporter.kt +++ b/platform/platform-impl/src/com/intellij/diagnostic/ITNReporter.kt @@ -94,16 +94,15 @@ open class ITNReporter(private val postUrl: String = "") : ErrorReportSubmitter( private fun submit(project: Project?, errorBean: ErrorBean, parentComponent: Component, callback: (SubmittedReportInfo) -> Unit): Boolean { service().coroutineScope.launch { try { - val reportId = if (project != null) { + if (project != null) { withBackgroundProgress(project, DiagnosticBundle.message("title.submitting.error.report")) { - ITNProxy.sendError(errorBean, postUrl) + ITNUtils.sendError(errorBean) } } else { - ITNProxy.sendError(errorBean, postUrl) + ITNUtils.sendError(errorBean) } - updatePreviousReport(errorBean.event, reportId) - onSuccess(project, reportId, callback) + onSuccess(project, callback) } catch (e: Exception) { onError(project, e, errorBean, parentComponent, callback) @@ -112,15 +111,11 @@ open class ITNReporter(private val postUrl: String = "") : ErrorReportSubmitter( return true } - private fun onSuccess(project: Project?, reportId: Int, callback: (SubmittedReportInfo) -> Unit) { - val reportUrl = ITNProxy.getBrowseUrl(reportId) - callback(SubmittedReportInfo(reportUrl, reportId.toString(), SubmittedReportInfo.SubmissionStatus.NEW_ISSUE)) + private fun onSuccess(project: Project?, callback: (SubmittedReportInfo) -> Unit) { + callback(SubmittedReportInfo(SubmittedReportInfo.SubmissionStatus.NEW_ISSUE)) 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 c3174a0ad69f..ed2392b33fa5 100644 --- a/platform/platform-resources/src/META-INF/PlatformExtensions.xml +++ b/platform/platform-resources/src/META-INF/PlatformExtensions.xml @@ -1870,6 +1870,7 @@ +