mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
IJPL-234642 [platform]: reporting startup errors to Diogen instead of YT
GitOrigin-RevId: 7fe6432287a38782dc993ed15f4ce3130deae7c8
This commit is contained in:
committed by
intellij-monorepo-bot
parent
5f6738063e
commit
d2a7820f41
@@ -1,12 +1,16 @@
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.diagnostic
|
||||
|
||||
import com.fasterxml.jackson.core.JsonFactory
|
||||
import com.fasterxml.jackson.core.JsonToken
|
||||
import com.intellij.errorreport.error.InternalEAPException
|
||||
import com.intellij.errorreport.error.UpdateAvailableException
|
||||
import com.intellij.ide.plugins.PluginManagerCore
|
||||
import com.intellij.idea.AppMode
|
||||
import com.intellij.internal.statistic.DeviceIdManager
|
||||
import com.intellij.internal.statistic.utils.getPluginInfoById
|
||||
import com.intellij.openapi.application.ApplicationNamesInfo
|
||||
import com.intellij.openapi.application.PathManager
|
||||
import com.intellij.openapi.application.ex.ApplicationInfoEx
|
||||
import com.intellij.openapi.components.Service
|
||||
import com.intellij.openapi.diagnostic.IdeaLoggingEvent
|
||||
@@ -28,6 +32,7 @@ import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.currentCoroutineContext
|
||||
import kotlinx.coroutines.ensureActive
|
||||
import org.jetbrains.annotations.ApiStatus
|
||||
import java.io.OutputStreamWriter
|
||||
import java.net.HttpURLConnection
|
||||
import java.net.URI
|
||||
@@ -38,6 +43,8 @@ import java.net.http.HttpResponse
|
||||
import java.nio.charset.StandardCharsets
|
||||
import java.time.Duration
|
||||
import java.util.zip.GZIPOutputStream
|
||||
import kotlin.io.path.Path
|
||||
import kotlin.io.path.bufferedReader
|
||||
|
||||
@Service
|
||||
internal class ITNProxyCoroutineScopeHolder(coroutineScope: CoroutineScope) {
|
||||
@@ -49,48 +56,101 @@ internal class ITNProxyCoroutineScopeHolder(coroutineScope: CoroutineScope) {
|
||||
internal val coroutineScope: CoroutineScope = coroutineScope.childScope("ITNProxy call", dispatcher)
|
||||
}
|
||||
|
||||
internal object ITNProxy {
|
||||
private const val DEFAULT_USER = "idea_anonymous"
|
||||
private const val DEFAULT_PASS = "guest"
|
||||
@ApiStatus.Internal
|
||||
object ITNProxy {
|
||||
private const val DEFAULT_ENDPOINT = "https://ea-report.jetbrains.com/trackerRpc/idea/createScr"
|
||||
private const val DIOGEN_VIEW_URL = "https://diogen.labs.jb.gg/report/"
|
||||
|
||||
internal val DEVICE_ID: String = DeviceIdManager.getOrGenerateId(object : DeviceIdManager.DeviceIdToken {}, "EA")
|
||||
internal val DEVICE_ID: String by lazy {
|
||||
DeviceIdManager.getOrGenerateId(object : DeviceIdManager.DeviceIdToken {}, "EA")
|
||||
}
|
||||
|
||||
private val LOG = logger<ITNProxy>()
|
||||
|
||||
private val TEMPLATE: Map<String, String?> by lazy {
|
||||
private val TEMPLATE_SAFE: Map<String, String?> by lazy {
|
||||
val template = LinkedHashMap<String, String?>()
|
||||
template["protocol.version"] = "1.1"
|
||||
template["user.login"] = "idea_anonymous"
|
||||
template["user.password"] = "guest"
|
||||
template["os.cpu.arch"] = if (CpuArch.isEmulated()) "${CpuArch.CURRENT}(emulated)" else "${CpuArch.CURRENT}"
|
||||
template["os.name"] = OS.CURRENT.name
|
||||
template["os.version"] = OS.CURRENT.version()
|
||||
template["host.id"] = DEVICE_ID
|
||||
template["java.version"] = SystemInfo.JAVA_RUNTIME_VERSION
|
||||
template["java.vm.vendor"] = SystemInfo.JAVA_VENDOR
|
||||
template
|
||||
}
|
||||
|
||||
private val TEMPLATE_APP: Map<String, String?> by lazy {
|
||||
val appInfo = ApplicationInfoEx.getInstanceEx()
|
||||
val namesInfo = ApplicationNamesInfo.getInstance()
|
||||
val build = appInfo.build
|
||||
var buildNumberWithAllDetails = build.asString()
|
||||
if (buildNumberWithAllDetails.startsWith(build.productCode + '-')) {
|
||||
buildNumberWithAllDetails = buildNumberWithAllDetails.substring(build.productCode.length + 1)
|
||||
}
|
||||
|
||||
val template = LinkedHashMap<String, String?>()
|
||||
template["host.id"] = DEVICE_ID
|
||||
template["app.name"] = namesInfo.productName
|
||||
template["app.name.full"] = namesInfo.fullProductName
|
||||
template["app.name.version"] = appInfo.versionName
|
||||
template["app.eap"] = java.lang.Boolean.toString(appInfo.isEAP)
|
||||
template["app.eap"] = appInfo.isEAP.toString()
|
||||
template["app.build"] = appInfo.apiVersion
|
||||
template["app.version.major"] = appInfo.majorVersion
|
||||
template["app.version.minor"] = appInfo.minorVersion
|
||||
template["app.build.date"] = (appInfo.buildTime.toInstant().toEpochMilli()).toString()
|
||||
template["app.build.date"] = appInfo.buildTime.toInstant().toEpochMilli().toString()
|
||||
template["app.build.date.release"] = appInfo.majorReleaseBuildDate.time.time.toString()
|
||||
template["app.product.code"] = build.productCode
|
||||
template["app.build.number"] = buildNumberWithAllDetails
|
||||
template["app.build.number"] = build.asStringWithoutProductCode()
|
||||
IdeProductInfo.getInstance().currentProductInfo.customProperties
|
||||
.find { it.key == CustomPropertyNames.GIT_REVISION }
|
||||
?.let { template["app.source.revision"] = it.value }
|
||||
template
|
||||
}
|
||||
|
||||
private fun appendEarlyAppData(builder: StringBuilder) {
|
||||
@Suppress("TestOnlyProblems")
|
||||
val homeDir = System.getProperty("idea.home.path")?.let { Path(it) }
|
||||
?: PathManager.getHomeDirFor(ITNProxy::class.java)
|
||||
?: throw RuntimeException("Cannot detect the IDE home directory")
|
||||
val appDataFile = homeDir.resolve(when {
|
||||
AppMode.isRunningFromDevBuild() -> "bin/product-info.json"
|
||||
OS.CURRENT == OS.macOS -> "Resources/product-info.json"
|
||||
else -> "product-info.json"
|
||||
})
|
||||
|
||||
try {
|
||||
var appName = null as String?
|
||||
var version = null as String?
|
||||
var buildNumber = null as String?
|
||||
var productCode = null as String?
|
||||
JsonFactory().createParser(appDataFile.bufferedReader()).use { parser ->
|
||||
if (parser.nextToken() == JsonToken.START_OBJECT) {
|
||||
while (true) {
|
||||
if (parser.nextToken() != JsonToken.FIELD_NAME) break
|
||||
val name = parser.currentName()
|
||||
if (parser.nextToken() != JsonToken.VALUE_STRING) break
|
||||
val value = parser.text
|
||||
when (name) {
|
||||
"name" -> appName = value
|
||||
"version" -> version = value
|
||||
"buildNumber" -> buildNumber = value
|
||||
"productCode" -> productCode = value
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (appName == null || version == null || buildNumber == null || productCode == null) throw RuntimeException("Malformed app data file")
|
||||
|
||||
val shortName = appName.splitToSequence(' ').last()
|
||||
val versionParts = version.split('.')
|
||||
append(builder, "app.name", shortName)
|
||||
append(builder, "app.name.full", appName)
|
||||
append(builder, "app.build", "${productCode}-${buildNumber}")
|
||||
append(builder, "app.version.major", versionParts[0])
|
||||
append(builder, "app.version.minor", versionParts.getOrNull(1) ?: "0")
|
||||
append(builder, "app.product.code", productCode)
|
||||
append(builder, "app.build.number", buildNumber)
|
||||
}
|
||||
catch (e: Exception) {
|
||||
throw RuntimeException("Cannot read application data", e)
|
||||
}
|
||||
}
|
||||
|
||||
@JvmRecord
|
||||
internal data class ErrorBean(
|
||||
val event: IdeaLoggingEvent,
|
||||
@@ -102,7 +162,7 @@ internal object ITNProxy {
|
||||
val isAutoReportedByPlatform: Boolean,
|
||||
)
|
||||
|
||||
fun getBrowseUrl(threadId: Long): String? = when {
|
||||
internal fun getBrowseUrl(threadId: Long): String? = when {
|
||||
isInternalUser() -> DIOGEN_VIEW_URL + threadId
|
||||
else -> null
|
||||
}
|
||||
@@ -121,31 +181,38 @@ internal object ITNProxy {
|
||||
}
|
||||
|
||||
@Throws(Exception::class)
|
||||
suspend fun sendError(error: ErrorBean, newThreadPostUrl: String): Long {
|
||||
internal suspend fun sendError(error: ErrorBean, newThreadPostUrl: String?): Long {
|
||||
val context = currentCoroutineContext()
|
||||
val request = createRequest(error.event, error)
|
||||
val response = post(newThreadPostUrl ?: DEFAULT_ENDPOINT, request)
|
||||
context.ensureActive()
|
||||
val reportId = handleResponse(response)
|
||||
logger<ITNProxy>().info("report ID: ${reportId}")
|
||||
return reportId
|
||||
}
|
||||
|
||||
val response = post(newThreadPostUrl, createRequest(error))
|
||||
@JvmStatic
|
||||
@Throws(Exception::class)
|
||||
fun sendError(event: IdeaLoggingEvent): Long {
|
||||
val request = createRequest(event, errorBean = null)
|
||||
val response = post(DEFAULT_ENDPOINT, request)
|
||||
return handleResponse(response)
|
||||
}
|
||||
|
||||
private fun handleResponse(response: HttpResponse<String>): Long {
|
||||
val responseCode = response.statusCode()
|
||||
if (responseCode != HttpURLConnection.HTTP_OK) {
|
||||
throw InternalEAPException(DiagnosticBundle.message("error.http.result.code", responseCode))
|
||||
}
|
||||
|
||||
context.ensureActive()
|
||||
|
||||
val responseText = response.body()
|
||||
if (responseText == "unauthorized") {
|
||||
throw InternalEAPException("Authorization failed")
|
||||
}
|
||||
if (responseText.startsWith("update ")) {
|
||||
throw UpdateAvailableException(responseText.substring(7))
|
||||
}
|
||||
if (responseText.startsWith("message ")) {
|
||||
throw InternalEAPException(responseText.substring(8))
|
||||
when {
|
||||
responseText == "unauthorized" -> throw InternalEAPException("Authorization failed")
|
||||
responseText.startsWith("update ") -> throw UpdateAvailableException(responseText.substring(7))
|
||||
responseText.startsWith("message ") -> throw InternalEAPException(responseText.substring(8))
|
||||
}
|
||||
try {
|
||||
val reportId = responseText.trim()
|
||||
LOG.info("report ID: ${reportId}")
|
||||
return reportId.toLong()
|
||||
return responseText.trim().toLong()
|
||||
}
|
||||
catch (_: NumberFormatException) {
|
||||
throw InternalEAPException(DiagnosticBundle.message("error.itn.returns.wrong.data"))
|
||||
@@ -153,72 +220,76 @@ internal object ITNProxy {
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
val appInfoString: String
|
||||
get() {
|
||||
val builder = StringBuilder()
|
||||
appendAppInfo(builder)
|
||||
return builder.toString()
|
||||
}
|
||||
internal val appInfoString: String
|
||||
get() = StringBuilder().apply { appendAppInfo(this) }.toString()
|
||||
|
||||
private fun appendAppInfo(builder: StringBuilder) {
|
||||
for ((key, value) in TEMPLATE) {
|
||||
append(builder, key, value)
|
||||
}
|
||||
TEMPLATE_SAFE.forEach { (key, value) -> append(builder, key, value) }
|
||||
TEMPLATE_APP.forEach { (key, value) -> append(builder, key, value) }
|
||||
}
|
||||
|
||||
private fun createRequest(error: ErrorBean): StringBuilder {
|
||||
private fun createRequest(event: IdeaLoggingEvent, errorBean: ErrorBean?): StringBuilder {
|
||||
val builder = StringBuilder(8192)
|
||||
val eventData = error.event.data
|
||||
val appInfo = if (eventData is AbstractMessage) eventData.appInfo else null
|
||||
if (appInfo != null) {
|
||||
builder.append(appInfo)
|
||||
|
||||
if (errorBean != null) {
|
||||
val appInfo = (event.data as? AbstractMessage)?.appInfo
|
||||
if (appInfo != null) {
|
||||
builder.append(appInfo)
|
||||
}
|
||||
else {
|
||||
appendAppInfo(builder)
|
||||
append(builder, "report.startup.error", "true")
|
||||
}
|
||||
}
|
||||
else {
|
||||
appendAppInfo(builder)
|
||||
TEMPLATE_SAFE.forEach { (key, value) -> append(builder, key, value) }
|
||||
appendEarlyAppData(builder)
|
||||
}
|
||||
|
||||
append(builder, "user.login", DEFAULT_USER)
|
||||
append(builder, "user.password", DEFAULT_PASS)
|
||||
JBAccountInfoService.getInstance()?.userData?.email?.takeIf { it.endsWith("@jetbrains.com", ignoreCase = true) }?.let {
|
||||
append(builder, "user.email", it)
|
||||
}
|
||||
|
||||
val updateSettings = UpdateSettings.getInstance()
|
||||
append(builder, "update.channel.status", updateSettings.selectedChannelStatus.code)
|
||||
append(builder, "update.ignored.builds", java.lang.String.join(",", updateSettings.ignoredBuildNumbers))
|
||||
append(builder, "plugin.id", error.pluginId)
|
||||
append(builder, "plugin.name", error.pluginName)
|
||||
append(builder, "plugin.version", error.pluginVersion)
|
||||
append(builder, "last.action", error.lastActionId)
|
||||
|
||||
val nonBundledPlugins = PluginManagerCore.loadedPlugins
|
||||
.filter { !it.isBundled }
|
||||
.map { it.pluginId }
|
||||
.filter { getPluginInfoById(it).isSafeToReport() }
|
||||
|
||||
if (nonBundledPlugins.isNotEmpty()) {
|
||||
append(builder, "plugins.nonbundled", nonBundledPlugins.joinToString(",") { it.idString })
|
||||
}
|
||||
|
||||
append(builder, "error.message", error.event.message?.trim { it <= ' ' } ?: "")
|
||||
append(builder, "error.stacktrace", error.event.throwableText)
|
||||
(error.event.throwable as? UnhandledException)?.let {
|
||||
append(builder, "error.message", event.message?.trim { it <= ' ' } ?: "")
|
||||
append(builder, "error.stacktrace", event.throwableText)
|
||||
(event.throwable as? UnhandledException)?.let {
|
||||
append(builder, "error.unhandled.interactive", it.isInteractive.toString())
|
||||
}
|
||||
|
||||
append(builder, "error.description", error.comment)
|
||||
if (error.event.throwable is RecoveredThrowable) {
|
||||
if (event.throwable is RecoveredThrowable) {
|
||||
append(builder, "error.redacted", "true")
|
||||
}
|
||||
|
||||
for (attachment in error.event.attachments) {
|
||||
for (attachment in event.attachments) {
|
||||
append(builder, "attachment.name", attachment.name)
|
||||
append(builder, "attachment.value", attachment.encodedBytes)
|
||||
}
|
||||
|
||||
if (error.isAutoReportedByPlatform) {
|
||||
append(builder, "report.automatic", "true")
|
||||
// optional fields; added only when the app is loaded
|
||||
if (errorBean != null) {
|
||||
JBAccountInfoService.getInstance()?.userData?.email?.takeIf { it.endsWith("@jetbrains.com", ignoreCase = true) }?.let {
|
||||
append(builder, "user.email", it)
|
||||
}
|
||||
|
||||
val updateSettings = UpdateSettings.getInstance()
|
||||
append(builder, "update.channel.status", updateSettings.selectedChannelStatus.code)
|
||||
append(builder, "update.ignored.builds", updateSettings.ignoredBuildNumbers.joinToString(","))
|
||||
|
||||
append(builder, "plugin.id", errorBean.pluginId)
|
||||
append(builder, "plugin.name", errorBean.pluginName)
|
||||
append(builder, "plugin.version", errorBean.pluginVersion)
|
||||
append(builder, "last.action", errorBean.lastActionId)
|
||||
|
||||
append(builder, "error.description", errorBean.comment)
|
||||
|
||||
PluginManagerCore.loadedPlugins
|
||||
.filter { !it.isBundled }
|
||||
.map { it.pluginId }
|
||||
.filter { getPluginInfoById(it).isSafeToReport() }
|
||||
.takeIf { it.isNotEmpty() }
|
||||
?.joinToString(",") { it.idString }
|
||||
?.let { append(builder, "plugins.nonbundled", it) }
|
||||
|
||||
if (errorBean.isAutoReportedByPlatform) {
|
||||
append(builder, "report.automatic", "true")
|
||||
}
|
||||
}
|
||||
|
||||
return builder
|
||||
}
|
||||
|
||||
|
||||
@@ -40,9 +40,9 @@ internal val NOTIFY_SUCCESS_EACH_REPORT = AtomicBoolean(true) // dirty hack, rep
|
||||
* Third-party plugins need to provide their own implementations of [ErrorReportSubmitter].
|
||||
*/
|
||||
@InternalIgnoreDependencyViolation
|
||||
open class ITNReporter internal constructor(private val postUrl: String) : ErrorReportSubmitter() {
|
||||
open class ITNReporter internal constructor(private val postUrl: String?) : ErrorReportSubmitter() {
|
||||
@ApiStatus.Internal
|
||||
constructor() : this("https://ea-report.jetbrains.com/trackerRpc/idea/createScr")
|
||||
constructor() : this(postUrl = null)
|
||||
|
||||
override fun getReportActionText(): String = DiagnosticBundle.message("error.report.to.jetbrains.action")
|
||||
|
||||
|
||||
+66
-88
@@ -1,10 +1,10 @@
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.platform.ide.bootstrap;
|
||||
|
||||
import com.intellij.diagnostic.ITNProxy;
|
||||
import com.intellij.diagnostic.ImplementationConflictException;
|
||||
import com.intellij.diagnostic.LoadingState;
|
||||
import com.intellij.diagnostic.PluginException;
|
||||
import com.intellij.ide.BootstrapBundle;
|
||||
import com.intellij.ide.logsUploader.LogUploader;
|
||||
import com.intellij.ide.plugins.EssentialPluginMissingException;
|
||||
import com.intellij.ide.plugins.PluginConflictReporter;
|
||||
@@ -22,17 +22,17 @@ import com.intellij.openapi.application.impl.ApplicationInfoImpl;
|
||||
import com.intellij.openapi.application.impl.ExceptionsKt;
|
||||
import com.intellij.openapi.diagnostic.ControlFlowException;
|
||||
import com.intellij.openapi.diagnostic.ExceptionWithAttachments;
|
||||
import com.intellij.openapi.diagnostic.IdeaLoggingEvent;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.progress.ProcessCanceledException;
|
||||
import com.intellij.openapi.util.NlsSafe;
|
||||
import com.intellij.openapi.util.io.NioFiles;
|
||||
import com.intellij.util.io.Compressor;
|
||||
import com.intellij.util.io.URLUtil;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.Nls;
|
||||
import org.jetbrains.annotations.NonNls;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jspecify.annotations.NullMarked;
|
||||
|
||||
import javax.swing.BorderFactory;
|
||||
import javax.swing.ImageIcon;
|
||||
@@ -44,7 +44,6 @@ import javax.swing.JProgressBar;
|
||||
import javax.swing.JScrollPane;
|
||||
import javax.swing.JTextPane;
|
||||
import javax.swing.ScrollPaneConstants;
|
||||
import javax.swing.SwingWorker;
|
||||
import javax.swing.UIManager;
|
||||
import java.awt.AWTError;
|
||||
import java.awt.BorderLayout;
|
||||
@@ -63,16 +62,16 @@ import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
import static com.intellij.ide.BootstrapBundle.message;
|
||||
import static java.util.Objects.requireNonNullElse;
|
||||
import static org.jetbrains.annotations.Nls.Capitalization.Sentence;
|
||||
import static org.jetbrains.annotations.Nls.Capitalization.Title;
|
||||
|
||||
@ApiStatus.Internal
|
||||
@NullMarked
|
||||
public final class StartupErrorReporter {
|
||||
private static final String SUPPORT_URL_PROPERTY = "ij.startup.error.support.url";
|
||||
private static final String REPORT_URL_PROPERTY = "ij.startup.error.report.url";
|
||||
|
||||
private static boolean hasGraphics = !ApplicationManagerEx.isInIntegrationTest();
|
||||
|
||||
@@ -81,7 +80,8 @@ public final class StartupErrorReporter {
|
||||
"Plugin Installation Problem",
|
||||
"The IDE failed to install or update some plugins.\n" +
|
||||
"Please try again, and if the problem persists, report it to the support.\n\n" +
|
||||
"The cause: " + t.toString());
|
||||
"The cause: " + t
|
||||
);
|
||||
}
|
||||
|
||||
/** <strong>Note:</strong> warnings should be hardcoded because it's too early to try loading localization plugins. */
|
||||
@@ -101,23 +101,23 @@ public final class StartupErrorReporter {
|
||||
}
|
||||
}
|
||||
|
||||
public static void showError(@NotNull @Nls(capitalization = Title) String title, @NotNull Throwable t) {
|
||||
public static void showError(@Nls(capitalization = Title) String title, Throwable t) {
|
||||
var message = new StringWriter();
|
||||
|
||||
var awtError = findCause(t, AWTError.class);
|
||||
if (awtError != null) {
|
||||
message.append(BootstrapBundle.message("bootstrap.error.prefix.graphics"));
|
||||
message.append(message("bootstrap.error.prefix.graphics"));
|
||||
hasGraphics = false;
|
||||
t = awtError;
|
||||
}
|
||||
else {
|
||||
message.append(BootstrapBundle.message("bootstrap.error.prefix.other"));
|
||||
message.append(message("bootstrap.error.prefix.other"));
|
||||
}
|
||||
|
||||
message.append("\n\n");
|
||||
t.printStackTrace(new PrintWriter(message));
|
||||
|
||||
message.append("\n-----\n").append(BootstrapBundle.message("bootstrap.error.appendix.jre", jreDetails()));
|
||||
message.append("\n-----\n").append(message("bootstrap.error.appendix.jre", jreDetails()));
|
||||
|
||||
showError(title, message.toString(), t); //NON-NLS
|
||||
}
|
||||
@@ -158,10 +158,10 @@ public final class StartupErrorReporter {
|
||||
|
||||
try {
|
||||
var messageObj = prepareMessage(message);
|
||||
var close = BootstrapBundle.message("bootstrap.error.option.close");
|
||||
var close = message("bootstrap.error.option.close");
|
||||
var iconUrl = StartupErrorReporter.class.getResource("/images/questionSign.png");
|
||||
var learnMore = iconUrl != null ? new JLabel(new ImageIcon(iconUrl)) : new JLabel("?");
|
||||
learnMore.setToolTipText(BootstrapBundle.message("bootstrap.error.option.support"));
|
||||
learnMore.setToolTipText(message("bootstrap.error.option.support"));
|
||||
learnMore.setCursor(new Cursor(Cursor.HAND_CURSOR));
|
||||
learnMore.addMouseListener(new MouseAdapter() {
|
||||
@Override
|
||||
@@ -170,13 +170,13 @@ public final class StartupErrorReporter {
|
||||
}
|
||||
});
|
||||
if (error != null) {
|
||||
var options = new Object[]{close, BootstrapBundle.message("bootstrap.error.option.reset"), BootstrapBundle.message("bootstrap.error.option.report"), learnMore};
|
||||
var options = new Object[]{close, message("bootstrap.error.option.reset"), message("bootstrap.error.option.report"), learnMore};
|
||||
var choice = JOptionPane.showOptionDialog(
|
||||
JOptionPane.getRootFrame(), messageObj, title, JOptionPane.DEFAULT_OPTION, JOptionPane.ERROR_MESSAGE, null, options, options[0]
|
||||
);
|
||||
switch (choice) {
|
||||
case 1 -> cleanStart();
|
||||
case 2 -> reportProblem(title, message, error);
|
||||
case 2 -> reportProblem(error);
|
||||
}
|
||||
}
|
||||
else {
|
||||
@@ -188,7 +188,7 @@ public final class StartupErrorReporter {
|
||||
}
|
||||
catch (Throwable t) {
|
||||
System.err.println("\n-----");
|
||||
System.err.println(BootstrapBundle.message("bootstrap.error.appendix.graphics"));
|
||||
System.err.println(message("bootstrap.error.appendix.graphics"));
|
||||
t.printStackTrace(System.err);
|
||||
}
|
||||
}
|
||||
@@ -203,78 +203,45 @@ public final class StartupErrorReporter {
|
||||
}
|
||||
}
|
||||
|
||||
private static void reportProblem(String title, String description, @Nullable Throwable error) {
|
||||
if (error != null) {
|
||||
title += " (" + error.getClass().getSimpleName() + ": " + shorten(error.getMessage()) + ')';
|
||||
}
|
||||
|
||||
var uploadId = (String)null;
|
||||
if (error instanceof ExceptionWithAttachments ewa) {
|
||||
var message = prepareMessage(BootstrapBundle.message("bootstrap.error.message.confirm"));
|
||||
var ok = JOptionPane.showConfirmDialog(JOptionPane.getRootFrame(), message, BootstrapBundle.message("bootstrap.error.option.report"), JOptionPane.OK_CANCEL_OPTION, JOptionPane.INFORMATION_MESSAGE);
|
||||
private static void reportProblem(Throwable error) {
|
||||
if (error instanceof ExceptionWithAttachments) {
|
||||
var message = prepareMessage(message("bootstrap.error.message.confirm"));
|
||||
var ok = JOptionPane.showConfirmDialog(JOptionPane.getRootFrame(), message, message("bootstrap.error.option.report"), JOptionPane.OK_CANCEL_OPTION, JOptionPane.INFORMATION_MESSAGE);
|
||||
if (ok != JOptionPane.OK_OPTION) return;
|
||||
|
||||
try {
|
||||
uploadId = uploadLogs(ewa);
|
||||
}
|
||||
catch (Throwable t) {
|
||||
var buf = new StringWriter();
|
||||
t.printStackTrace(new PrintWriter(buf));
|
||||
message = prepareMessage(BootstrapBundle.message("bootstrap.error.message.no.logs", buf));
|
||||
JOptionPane.showMessageDialog(JOptionPane.getRootFrame(), message, BootstrapBundle.message("bootstrap.error.title.no.logs"), JOptionPane.ERROR_MESSAGE);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (uploadId != null) {
|
||||
description += "\n\n-----\n[Upload ID: " + uploadId + ']';
|
||||
}
|
||||
|
||||
try {
|
||||
var url = System.getProperty(REPORT_URL_PROPERTY, "https://youtrack.jetbrains.com/newissue?project=IJPL&clearDraft=true&summary=$TITLE$&description=$DESCR$&c=$SUBSYSTEM$")
|
||||
.replace("$TITLE$", URLUtil.encodeURIComponent(title))
|
||||
.replace("$DESCR$", URLUtil.encodeURIComponent(description))
|
||||
.replace("$SUBSYSTEM$", URLUtil.encodeURIComponent("Subsystem: IDE. Startup"));
|
||||
Desktop.getDesktop().browse(new URI(url));
|
||||
}
|
||||
catch (Throwable t) {
|
||||
showBrowserError(t);
|
||||
}
|
||||
}
|
||||
|
||||
private static String shorten(String message) {
|
||||
if (message.length() <= 200) return message;
|
||||
int p = message.indexOf('\n', 200);
|
||||
if (p < 0 || p >= 250) p = message.indexOf(". ", 200);
|
||||
if (p < 0 || p >= 250) p = message.indexOf(' ', 200);
|
||||
if (p < 0 || p >= 250) p = 200;
|
||||
message = message.substring(0, p);
|
||||
return message + (message.endsWith(".") ? ".." : "...");
|
||||
}
|
||||
|
||||
private static @Nullable String uploadLogs(ExceptionWithAttachments error) throws ExecutionException, InterruptedException {
|
||||
var progressBar = new JProgressBar();
|
||||
progressBar.setIndeterminate(true);
|
||||
var label = new JLabel(BootstrapBundle.message("bootstrap.error.message.logs"));
|
||||
var label = new JLabel(message("bootstrap.error.message.submitting"));
|
||||
var panel = new JPanel(new BorderLayout(5, 5));
|
||||
panel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
|
||||
panel.add(label, BorderLayout.NORTH);
|
||||
panel.add(progressBar, BorderLayout.CENTER);
|
||||
var progressDialog = new JDialog(JOptionPane.getRootFrame(), BootstrapBundle.message("bootstrap.error.title.logs"), true);
|
||||
var progressDialog = new JDialog(JOptionPane.getRootFrame(), message("bootstrap.error.title.submitting"), true);
|
||||
progressDialog.add(panel);
|
||||
progressDialog.setSize(300, 100);
|
||||
progressDialog.setLocationRelativeTo(null);
|
||||
|
||||
@SuppressWarnings("SSBasedInspection")
|
||||
var worker = new SwingWorker<String, Void>() {
|
||||
@SuppressWarnings({"SSBasedInspection", "UnnecessaryFullyQualifiedName"})
|
||||
var worker = new javax.swing.SwingWorker<String, Void>() {
|
||||
@Override
|
||||
protected String doInBackground() throws Exception {
|
||||
var logs = collectLogs(error);
|
||||
try {
|
||||
return LogUploader.uploadFile(logs);
|
||||
}
|
||||
finally {
|
||||
NioFiles.deleteQuietly(logs);
|
||||
var comment = "Startup error";
|
||||
|
||||
if (error instanceof ExceptionWithAttachments ewa) {
|
||||
var logs = collectLogs(ewa);
|
||||
try {
|
||||
var uploadId = LogUploader.uploadFile(logs);
|
||||
comment += "\n\nLogs upload ID: " + uploadId;
|
||||
}
|
||||
finally {
|
||||
NioFiles.deleteQuietly(logs);
|
||||
}
|
||||
}
|
||||
|
||||
var id = ITNProxy.sendError(new IdeaLoggingEvent(comment, error));
|
||||
|
||||
return String.valueOf(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -287,7 +254,17 @@ public final class StartupErrorReporter {
|
||||
worker.execute();
|
||||
progressDialog.setVisible(true);
|
||||
|
||||
return worker.get();
|
||||
try {
|
||||
var reportId = worker.get();
|
||||
var message = message("bootstrap.error.message.submitted", reportId);
|
||||
JOptionPane.showMessageDialog(JOptionPane.getRootFrame(), message, message("bootstrap.error.title.submitted"), JOptionPane.INFORMATION_MESSAGE);
|
||||
}
|
||||
catch (Throwable t) {
|
||||
var buf = new StringWriter();
|
||||
t.printStackTrace(new PrintWriter(buf));
|
||||
var message = prepareMessage(message("bootstrap.error.message.no.report", buf));
|
||||
JOptionPane.showMessageDialog(JOptionPane.getRootFrame(), message, message("bootstrap.error.title.no.report"), JOptionPane.ERROR_MESSAGE);
|
||||
}
|
||||
}
|
||||
|
||||
private static Path collectLogs(ExceptionWithAttachments error) throws IOException {
|
||||
@@ -320,30 +297,30 @@ public final class StartupErrorReporter {
|
||||
try {
|
||||
var backupPath = ConfigBackup.Companion.getNextBackupPath(PathManager.getConfigDir());
|
||||
CustomConfigMigrationOption.StartWithCleanConfig.INSTANCE.writeConfigMarkerFile();
|
||||
var message = BootstrapBundle.message("bootstrap.error.message.reset", backupPath);
|
||||
JOptionPane.showMessageDialog(JOptionPane.getRootFrame(), message, BootstrapBundle.message("bootstrap.error.title.reset"), JOptionPane.INFORMATION_MESSAGE);
|
||||
var message = message("bootstrap.error.message.reset", backupPath);
|
||||
JOptionPane.showMessageDialog(JOptionPane.getRootFrame(), message, message("bootstrap.error.title.reset"), JOptionPane.INFORMATION_MESSAGE);
|
||||
}
|
||||
catch (Throwable t) {
|
||||
var message = BootstrapBundle.message("bootstrap.error.message.reset.failed", t);
|
||||
JOptionPane.showMessageDialog(JOptionPane.getRootFrame(), message, BootstrapBundle.message("bootstrap.error.title.reset"), JOptionPane.ERROR_MESSAGE);
|
||||
var message = message("bootstrap.error.message.reset.failed", t);
|
||||
JOptionPane.showMessageDialog(JOptionPane.getRootFrame(), message, message("bootstrap.error.title.reset"), JOptionPane.ERROR_MESSAGE);
|
||||
}
|
||||
}
|
||||
|
||||
private static void showBrowserError(Throwable t) {
|
||||
var message = prepareMessage(BootstrapBundle.message("bootstrap.error.message.browser", t));
|
||||
JOptionPane.showMessageDialog(JOptionPane.getRootFrame(), message, BootstrapBundle.message("bootstrap.error.title.browser"), JOptionPane.ERROR_MESSAGE);
|
||||
var message = prepareMessage(message("bootstrap.error.message.browser", t));
|
||||
JOptionPane.showMessageDialog(JOptionPane.getRootFrame(), message, message("bootstrap.error.title.browser"), JOptionPane.ERROR_MESSAGE);
|
||||
}
|
||||
|
||||
@SuppressWarnings({"UndesirableClassUsage", "HardCodedStringLiteral"})
|
||||
private static JScrollPane prepareMessage(String message) {
|
||||
var textPane = new JTextPane();
|
||||
textPane.setEditable(false);
|
||||
textPane.setText(message.replaceAll("\t", " "));
|
||||
textPane.setText(message.replace("\t", " "));
|
||||
textPane.setBackground(UIManager.getColor("Panel.background"));
|
||||
textPane.setCaretPosition(0);
|
||||
|
||||
var scrollPane = new JScrollPane(textPane, ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED, ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED);
|
||||
scrollPane.setBorder(null);
|
||||
scrollPane.setBorder(BorderFactory.createEmptyBorder());
|
||||
|
||||
var maxHeight = Toolkit.getDefaultToolkit().getScreenSize().height / 2;
|
||||
var maxWidth = Toolkit.getDefaultToolkit().getScreenSize().width / 2;
|
||||
@@ -354,7 +331,7 @@ public final class StartupErrorReporter {
|
||||
return scrollPane;
|
||||
}
|
||||
|
||||
public static void processException(@NotNull Throwable t) {
|
||||
public static void processException(Throwable t) {
|
||||
if (LoadingState.COMPONENTS_LOADED.isOccurred() && !(t instanceof StartupAbortedException)) {
|
||||
if (!(t instanceof ControlFlowException)) {
|
||||
PluginManagerCore.getLogger().error(t);
|
||||
@@ -367,8 +344,9 @@ public final class StartupErrorReporter {
|
||||
if (essentialPluginMissingException != null) {
|
||||
var pluginIds = essentialPluginMissingException.pluginIds;
|
||||
showError(
|
||||
BootstrapBundle.message("bootstrap.error.title.corrupted"),
|
||||
BootstrapBundle.message("bootstrap.error.essential.plugins", pluginIds.size(), " " + String.join("\n ", pluginIds) + "\n\n"));
|
||||
message("bootstrap.error.title.corrupted"),
|
||||
message("bootstrap.error.essential.plugins", pluginIds.size(), " " + String.join("\n ", pluginIds) + "\n\n")
|
||||
);
|
||||
System.exit(AppExitCodes.INSTALLATION_CORRUPTED);
|
||||
}
|
||||
|
||||
@@ -398,20 +376,20 @@ public final class StartupErrorReporter {
|
||||
PluginManagerCore.disablePlugin(pluginId);
|
||||
|
||||
var message = new StringWriter();
|
||||
message.append(BootstrapBundle.message("bootstrap.error.message.plugin.failed", pluginId.getIdString()));
|
||||
message.append(message("bootstrap.error.message.plugin.failed", pluginId.getIdString()));
|
||||
message.append("\n\n");
|
||||
requireNonNullElse(pluginException.getCause(), pluginException).printStackTrace(new PrintWriter(message));
|
||||
|
||||
showError(BootstrapBundle.message("bootstrap.error.title.plugin.init"), message.toString()); //NON-NLS
|
||||
showError(message("bootstrap.error.title.plugin.init"), message.toString()); //NON-NLS
|
||||
System.exit(AppExitCodes.PLUGIN_ERROR);
|
||||
}
|
||||
else {
|
||||
showError(BootstrapBundle.message("bootstrap.error.title.start.failed"), t);
|
||||
showError(message("bootstrap.error.title.start.failed"), t);
|
||||
System.exit(AppExitCodes.STARTUP_EXCEPTION);
|
||||
}
|
||||
}
|
||||
|
||||
private static <T extends Throwable> T findCause(Throwable t, Class<T> clazz) {
|
||||
private static <T extends Throwable> @Nullable T findCause(Throwable t, Class<T> clazz) {
|
||||
while (t != null) {
|
||||
if (clazz.isInstance(t)) {
|
||||
return clazz.cast(t);
|
||||
|
||||
@@ -13,13 +13,15 @@ bootstrap.error.option.support=Learn More
|
||||
bootstrap.error.option.report=Report Problem
|
||||
bootstrap.error.option.reset=Reset Settings\\&Plugins
|
||||
|
||||
bootstrap.error.title.logs=Collecting Logs
|
||||
bootstrap.error.message.logs=Collecting logs, please wait…
|
||||
bootstrap.error.title.no.logs=Cannot Collect Logs
|
||||
bootstrap.error.message.no.logs=Cannot collect logs.\n\nThe cause: {0}
|
||||
bootstrap.error.message.confirm=\
|
||||
The IDE will upload logs, which may contain sensitive data, to uploads.jetbrains.com.\n\
|
||||
Uploaded logs are only accessible to JetBrains and deleted automatically after 60 days.
|
||||
bootstrap.error.title.submitting=Submitting Error Report
|
||||
bootstrap.error.message.submitting=Submitting the error report, please wait\u2026
|
||||
bootstrap.error.title.no.report=Cannot Submit Report
|
||||
bootstrap.error.message.no.report=Cannot submit the error report.\n\nThe cause: {0}
|
||||
bootstrap.error.title.submitted=Report Submitted
|
||||
bootstrap.error.message.submitted=Report ID: {0}\nYou can use the number when contacting support for checking the status.
|
||||
|
||||
bootstrap.error.title.browser=Cannot Open Browser
|
||||
bootstrap.error.message.browser=Cannot launch the default browser.\n\nThe cause: {0}
|
||||
|
||||
Reference in New Issue
Block a user