mirror of
https://gitflic.ru/project/openide/openide.git
synced 2025-12-13 06:59:44 +07:00
OPENIDE #49 Implement sending default errors from OpenIDE
This commit is contained in:
18
.idea/libraries/io_sentry.xml
generated
Normal file
18
.idea/libraries/io_sentry.xml
generated
Normal file
@@ -0,0 +1,18 @@
|
||||
<component name="libraryTable">
|
||||
<library name="io.sentry" type="repository">
|
||||
<properties maven-id="io.sentry:sentry:7.8.0">
|
||||
<verification>
|
||||
<artifact url="file://$MAVEN_REPOSITORY$/io/sentry/sentry/7.8.0/sentry-7.8.0.jar">
|
||||
<sha256sum>927f0dfd76c9262f3d755345d72468138ae1bdba5d8f20c44f79ba2e1efcdb91</sha256sum>
|
||||
</artifact>
|
||||
</verification>
|
||||
</properties>
|
||||
<CLASSES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/sentry/sentry/7.8.0/sentry-7.8.0.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/sentry/sentry/7.8.0/sentry-7.8.0-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
</component>
|
||||
@@ -141,5 +141,6 @@
|
||||
<orderEntry type="module" module-name="intellij.platform.resources" scope="RUNTIME" />
|
||||
<orderEntry type="module" module-name="intellij.platform.resources.en" scope="RUNTIME" />
|
||||
<orderEntry type="module" module-name="intellij.platform.ide.ui" />
|
||||
<orderEntry type="library" name="io.sentry" level="project" />
|
||||
</component>
|
||||
</module>
|
||||
@@ -22,28 +22,28 @@ error.list.message.submitted=Submitted
|
||||
error.list.message.submitted.as.link=Submitted as <a href="{0}">{1}</a>
|
||||
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 <a href="{1}">{0}</a>.
|
||||
error.dialog.foreign.plugin.warning.unnamed=This plugin is not a production of JetBrains. Please report the problem to the <a href="{0}">plugin vendor</a>.
|
||||
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 <a href="{1}">{0}</a>.
|
||||
error.dialog.foreign.plugin.warning.unnamed=This plugin is not a production of OpenIDE. Please report the problem to the <a href="{0}">plugin vendor</a>.
|
||||
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 <a href="#">Log In</a>
|
||||
error.dialog.submit.named=Submit as <b>{0}</b> or <a href="#">change account</a>
|
||||
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 <a href="https://www.jetbrains.com/legal/agreements/exception_analyzer.html">JetBrains Exception Analyzer</a>.
|
||||
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 <a href="https://openide.ru/legal/agreements/exception_analyzer.html">OpenIDE Exception Analyzer</a>.
|
||||
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 <a href="https://www.jetbrains.com/legal/agreements/exception_analyzer.html">Agreement</a>.\
|
||||
\ 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 <a href="https://openide.ru/legal/agreements/exception_analyzer.html">Agreement</a>.\
|
||||
\ 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
|
||||
|
||||
@@ -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<ITNProxyCoroutineScopeHolder>().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)
|
||||
}
|
||||
|
||||
|
||||
118
platform/platform-impl/src/com/intellij/diagnostic/ITNUtils.kt
Normal file
118
platform/platform-impl/src/com/intellij/diagnostic/ITNUtils.kt
Normal file
@@ -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", "<unknown vendor>")
|
||||
val javaVersion = System.getProperty("java.version", "<unknown 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()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<UniqueComputerId>()
|
||||
|
||||
@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")
|
||||
}
|
||||
}
|
||||
@@ -1870,6 +1870,7 @@
|
||||
<applicationService serviceInterface="com.intellij.auth.GenericAuthService"
|
||||
serviceImplementation="com.intellij.auth.LocalGenericAuthService"
|
||||
client="local"/>
|
||||
<applicationActivity implementation="com.intellij.diagnostic.SentryInitializer"/>
|
||||
<applicationService serviceInterface="com.intellij.platform.ide.provisioner.ProvisionedServiceRegistry"
|
||||
serviceImplementation="com.intellij.platform.ide.provisioner.DefaultProvisionedServiceRegistry"/>
|
||||
<applicationService serviceInterface="com.intellij.platform.ide.provisioner.ProvisionerCompanyBrandingProvider"
|
||||
|
||||
Reference in New Issue
Block a user