IJPL-234642 [platform]: reporting startup errors to Diogen instead of YT

GitOrigin-RevId: 7fe6432287a38782dc993ed15f4ce3130deae7c8
This commit is contained in:
Roman Shevchenko
2026-02-13 14:01:53 +00:00
committed by intellij-monorepo-bot
parent 5f6738063e
commit d2a7820f41
4 changed files with 223 additions and 172 deletions
@@ -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")
@@ -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}