mirror of
https://gitflic.ru/project/openide/openide.git
synced 2025-12-15 11:53:49 +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" scope="RUNTIME" />
|
||||||
<orderEntry type="module" module-name="intellij.platform.resources.en" scope="RUNTIME" />
|
<orderEntry type="module" module-name="intellij.platform.resources.en" scope="RUNTIME" />
|
||||||
<orderEntry type="module" module-name="intellij.platform.ide.ui" />
|
<orderEntry type="module" module-name="intellij.platform.ide.ui" />
|
||||||
|
<orderEntry type="library" name="io.sentry" level="project" />
|
||||||
</component>
|
</component>
|
||||||
</module>
|
</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.submitted.as.link=Submitted as <a href="{0}">{1}</a>
|
||||||
error.list.message.submission.failed=Submission failed
|
error.list.message.submission.failed=Submission failed
|
||||||
error.list.message.submission.failed.details=Submission failed ({0})
|
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=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 JetBrains. Please report the problem to the <a href="{0}">plugin vendor</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 JetBrains. 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.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=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.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.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.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.all.action=Report All
|
||||||
error.report.and.clear.all.action=Report and Clear All
|
error.report.and.clear.all.action=Report and Clear All
|
||||||
error.report.impossible.action=Report Exception
|
error.report.impossible.action=Report Exception
|
||||||
error.report.impossible.tooltip=The plugin vendor didn't implement the exception reporting component.
|
error.report.impossible.tooltip=The plugin vendor didn't implement the exception reporting component.
|
||||||
error.dialog.clear.all.action=&Clear all
|
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 \
|
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 \
|
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://www.jetbrains.com/legal/agreements/exception_analyzer.html">JetBrains Exception Analyzer</a>.
|
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\
|
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\
|
\ 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://www.jetbrains.com/legal/agreements/exception_analyzer.html">Agreement</a>.\
|
\ 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 JetBrains and the plugin author.\
|
\ If you submit the report containing any personal data, please note that such data will be shared with OpenIDE 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.
|
\ 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
|
error.dialog.notice.third-party.plugin.send=Report to the Third-Party Plugin
|
||||||
title.submitting.error.report=Submitting error report
|
title.submitting.error.report=Submitting error report
|
||||||
error.report.submitted=Report submitted
|
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 {
|
private fun submit(project: Project?, errorBean: ErrorBean, parentComponent: Component, callback: (SubmittedReportInfo) -> Unit): Boolean {
|
||||||
service<ITNProxyCoroutineScopeHolder>().coroutineScope.launch {
|
service<ITNProxyCoroutineScopeHolder>().coroutineScope.launch {
|
||||||
try {
|
try {
|
||||||
val reportId = if (project != null) {
|
if (project != null) {
|
||||||
withBackgroundProgress(project, DiagnosticBundle.message("title.submitting.error.report")) {
|
withBackgroundProgress(project, DiagnosticBundle.message("title.submitting.error.report")) {
|
||||||
ITNProxy.sendError(errorBean, postUrl)
|
ITNUtils.sendError(errorBean)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
ITNProxy.sendError(errorBean, postUrl)
|
ITNUtils.sendError(errorBean)
|
||||||
}
|
}
|
||||||
updatePreviousReport(errorBean.event, reportId)
|
onSuccess(project, callback)
|
||||||
onSuccess(project, reportId, callback)
|
|
||||||
}
|
}
|
||||||
catch (e: Exception) {
|
catch (e: Exception) {
|
||||||
onError(project, e, errorBean, parentComponent, callback)
|
onError(project, e, errorBean, parentComponent, callback)
|
||||||
@@ -112,15 +111,11 @@ open class ITNReporter(private val postUrl: String = "") : ErrorReportSubmitter(
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onSuccess(project: Project?, reportId: Int, callback: (SubmittedReportInfo) -> Unit) {
|
private fun onSuccess(project: Project?, callback: (SubmittedReportInfo) -> Unit) {
|
||||||
val reportUrl = ITNProxy.getBrowseUrl(reportId)
|
callback(SubmittedReportInfo(SubmittedReportInfo.SubmissionStatus.NEW_ISSUE))
|
||||||
callback(SubmittedReportInfo(reportUrl, reportId.toString(), SubmittedReportInfo.SubmissionStatus.NEW_ISSUE))
|
|
||||||
val content = DiagnosticBundle.message("error.report.gratitude")
|
val content = DiagnosticBundle.message("error.report.gratitude")
|
||||||
val title = DiagnosticBundle.message("error.report.submitted")
|
val title = DiagnosticBundle.message("error.report.submitted")
|
||||||
val notification = Notification("Error Report", title, content, NotificationType.INFORMATION).setImportant(false)
|
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)
|
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"
|
<applicationService serviceInterface="com.intellij.auth.GenericAuthService"
|
||||||
serviceImplementation="com.intellij.auth.LocalGenericAuthService"
|
serviceImplementation="com.intellij.auth.LocalGenericAuthService"
|
||||||
client="local"/>
|
client="local"/>
|
||||||
|
<applicationActivity implementation="com.intellij.diagnostic.SentryInitializer"/>
|
||||||
<applicationService serviceInterface="com.intellij.platform.ide.provisioner.ProvisionedServiceRegistry"
|
<applicationService serviceInterface="com.intellij.platform.ide.provisioner.ProvisionedServiceRegistry"
|
||||||
serviceImplementation="com.intellij.platform.ide.provisioner.DefaultProvisionedServiceRegistry"/>
|
serviceImplementation="com.intellij.platform.ide.provisioner.DefaultProvisionedServiceRegistry"/>
|
||||||
<applicationService serviceInterface="com.intellij.platform.ide.provisioner.ProvisionerCompanyBrandingProvider"
|
<applicationService serviceInterface="com.intellij.platform.ide.provisioner.ProvisionerCompanyBrandingProvider"
|
||||||
|
|||||||
Reference in New Issue
Block a user