[platform] refactoring of exception handling/reporting subsystem

- streamlines exception flow through the system
- allows message and stacktrace editing by user (IDEA-188535)
- deprecates methods/classes basically duplicating Logger.error(..)
- annotates and comments methods of AbstractMessage
This commit is contained in:
Roman Shevchenko
2018-05-30 10:44:49 +03:00
parent 463c47286d
commit e10f08b8b9
19 changed files with 733 additions and 779 deletions
@@ -1,24 +1,11 @@
/*
* Copyright 2000-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.diagnostic;
import com.intellij.openapi.diagnostic.Attachment;
import com.intellij.openapi.diagnostic.SubmittedReportInfo;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Calendar;
import java.util.Collections;
@@ -26,45 +13,46 @@ import java.util.Date;
import java.util.List;
public abstract class AbstractMessage {
private boolean myIsRead = false;
private boolean myIsSubmitting = false;
private final Date myDate = Calendar.getInstance().getTime();
private boolean myIsRead;
private Runnable myOnReadCallback;
private boolean myIsSubmitting;
private SubmittedReportInfo mySubmissionInfo;
private String myAdditionalInfo;
private Runnable myOnReadCallback;
private Integer myAssigneeId;
private final Date myDate;
public abstract @NotNull Throwable getThrowable();
public abstract @NotNull String getThrowableText();
public AbstractMessage() {
myDate = Calendar.getInstance().getTime();
/** Returns a user message (see {@link LogMessage#createEvent}), if present. */
public abstract @Nullable String getMessage();
/** Returns a (possibly empty) list of all attachments. */
public @NotNull List<Attachment> getAllAttachments() {
return Collections.emptyList();
}
public abstract String getThrowableText();
public abstract Throwable getThrowable();
public abstract String getMessage();
/** Returns a list of attachments marked by a user to be included into the error report. */
public @NotNull List<Attachment> getIncludedAttachments() {
return ContainerUtil.filter(getAllAttachments(), Attachment::isIncluded);
}
public @NotNull Date getDate() {
return myDate;
}
public boolean isRead() {
return myIsRead;
}
public void setRead(boolean aReadFlag) {
myIsRead = aReadFlag;
if (myOnReadCallback != null && aReadFlag) {
public void setRead(boolean isRead) {
myIsRead = isRead;
if (isRead && myOnReadCallback != null) {
myOnReadCallback.run();
myOnReadCallback = null;
}
}
public void setSubmitted(SubmittedReportInfo info) {
myIsSubmitting = false;
mySubmissionInfo = info;
}
public SubmittedReportInfo getSubmissionInfo() {
return mySubmissionInfo;
}
public void setOnReadCallback(Runnable callback) {
myOnReadCallback = callback;
}
@@ -77,10 +65,19 @@ public abstract class AbstractMessage {
myIsSubmitting = isSubmitting;
}
public SubmittedReportInfo getSubmissionInfo() {
return mySubmissionInfo;
}
public void setSubmitted(SubmittedReportInfo info) {
myIsSubmitting = false;
mySubmissionInfo = info;
}
public boolean isSubmitted() {
return mySubmissionInfo != null &&
(mySubmissionInfo.getStatus() == SubmittedReportInfo.SubmissionStatus.NEW_ISSUE ||
mySubmissionInfo.getStatus() == SubmittedReportInfo.SubmissionStatus.DUPLICATE);
(mySubmissionInfo.getStatus() == SubmittedReportInfo.SubmissionStatus.NEW_ISSUE ||
mySubmissionInfo.getStatus() == SubmittedReportInfo.SubmissionStatus.DUPLICATE);
}
public String getAdditionalInfo() {
@@ -91,34 +88,16 @@ public abstract class AbstractMessage {
myAdditionalInfo = additionalInfo;
}
public Date getDate() {
return myDate;
}
public Integer getAssigneeId() {
public @Nullable Integer getAssigneeId() {
return myAssigneeId;
}
public void setAssigneeId(Integer assigneeId) {
public void setAssigneeId(@Nullable Integer assigneeId) {
myAssigneeId = assigneeId;
}
public List<Attachment> getAllAttachments() {
return Collections.emptyList();
}
/**
* @return list of attachments which are marked by user to be included into the error report
*/
public List<Attachment> getIncludedAttachments() {
return ContainerUtil.filter(getAllAttachments(), Attachment::isIncluded);
}
/**
* @deprecated use {@link #getIncludedAttachments()} instead
*/
@NotNull
/** @deprecated use {@link #getIncludedAttachments()} instead (to be removed in IDEA 2020) */
public List<Attachment> getAttachments() {
return getIncludedAttachments();
}
}
}
@@ -1,3 +1,4 @@
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.diagnostic;
import com.intellij.openapi.diagnostic.Attachment;
@@ -7,10 +8,10 @@ import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.vfs.CharsetToolkit;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.util.PathUtilRt;
import org.jetbrains.annotations.NotNull;
import java.io.*;
import java.text.MessageFormat;
/**
* @author yole
@@ -18,62 +19,54 @@ import java.text.MessageFormat;
public class AttachmentFactory {
private static final Logger LOG = Logger.getInstance(AttachmentFactory.class);
private static final Long BIG_FILE_THRESHOLD_BYTES = 50 * 1024L;
private static final long BIG_FILE_THRESHOLD_BYTES = 50 * 1024;
private static final String ERROR_MESSAGE_PATTERN = "[[[Can't get file contents: {0}]]]";
private static final String BIG_FILE_MESSAGE_PATTERN = "[[[File is too big to display: {0}]]]";
public static Attachment createContext(@NotNull Object start, Object... more) {
StringBuilder builder = new StringBuilder(String.valueOf(start));
for (Object o : more) builder.append(",").append(o);
return new Attachment("current-context.txt", builder.length() > 0 ? builder.toString() : "(unknown)");
}
public static Attachment createAttachment(Document document) {
public static Attachment createAttachment(@NotNull Document document) {
VirtualFile file = FileDocumentManager.getInstance().getFile(document);
return new Attachment(file != null ? file.getPath() : "unknown.txt", document.getText());
}
public static Attachment createAttachment(@NotNull VirtualFile file) {
try {
boolean isBinary = file.getFileType().isBinary();
boolean isBigFile = file.getLength() > BIG_FILE_THRESHOLD_BYTES;
try (InputStream inputStream = file.getInputStream()) {
return createAttachment(file.getPresentableUrl(), inputStream, isBinary, isBigFile);
}
} catch (IOException e) {
return handleException(e, file.getName(), file.getPath());
try (InputStream inputStream = file.getInputStream()) {
return createAttachment(file.getPresentableUrl(), inputStream, file.getLength(), file.getFileType().isBinary());
}
catch (IOException e) {
return handleException(e, file.getPath());
}
}
public static Attachment createAttachment(@NotNull File file, boolean isBinary) {
try {
try (InputStream inputStream = new FileInputStream(file)) {
return createAttachment(file.getPath(), inputStream, isBinary, file.length() > BIG_FILE_THRESHOLD_BYTES);
}
} catch (IOException e) {
return handleException(e, file.getName(), file.getPath());
try (InputStream inputStream = new FileInputStream(file)) {
return createAttachment(file.getPath(), inputStream, file.length(), isBinary);
}
catch (IOException e) {
return handleException(e, file.getPath());
}
}
private static Attachment handleException(Throwable t, String name, String moniker) {
final String errorMessage = MessageFormat.format(ERROR_MESSAGE_PATTERN, t.getMessage());
LOG.warn("Unable to create Attachment from " + moniker + ": " + t.getMessage(), t);
return new Attachment(name, errorMessage);
private static Attachment handleException(Throwable t, String path) {
LOG.warn("failed to create an attachment from " + path, t);
return new Attachment(path, t);
}
private static Attachment createAttachment(@NotNull String path, InputStream contentStream, boolean isBinary, boolean isBigFile) {
if (isBigFile) {
return new Attachment(path, contentStream, MessageFormat.format(BIG_FILE_MESSAGE_PATTERN, path));
} else {
byte[] bytes = getBytes(contentStream);
final String displayText = isBinary ? "[File is binary]" : new String(bytes, CharsetToolkit.UTF8_CHARSET);
private static Attachment createAttachment(String path, InputStream content, long contentLength, boolean isBinary) throws IOException {
if (contentLength >= BIG_FILE_THRESHOLD_BYTES) {
File tempFile = FileUtil.createTempFile("ij-attachment-" + PathUtilRt.getFileName(path) + ".", isBinary ? ".bin" : ".txt", true);
try (OutputStream outputStream = new FileOutputStream(tempFile)) {
FileUtil.copy(content, contentLength, outputStream);
}
return new Attachment(path, tempFile, "[File is too big to display]");
}
else {
byte[] bytes = FileUtil.loadBytes(content);
String displayText = isBinary ? "[File is binary]" : new String(bytes, CharsetToolkit.UTF8_CHARSET);
return new Attachment(path, bytes, displayText);
}
}
private static byte[] getBytes(InputStream inputStream) {
try {
return FileUtil.loadBytes(inputStream);
}
catch (IOException e) {
return MessageFormat.format(ERROR_MESSAGE_PATTERN, e.getMessage()).getBytes(CharsetToolkit.UTF8_CHARSET);
}
}
}
}
@@ -1,18 +1,4 @@
/*
* Copyright 2000-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.diagnostic;
import com.intellij.idea.IdeaApplication;
@@ -22,6 +8,7 @@ import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.ErrorLogger;
import com.intellij.openapi.diagnostic.ExceptionWithAttachments;
import com.intellij.openapi.diagnostic.IdeaLoggingEvent;
import com.intellij.openapi.diagnostic.RuntimeExceptionWithAttachments;
import com.intellij.util.ExceptionUtil;
import org.apache.log4j.AppenderSkeleton;
import org.apache.log4j.Level;
@@ -37,14 +24,14 @@ import java.util.concurrent.atomic.AtomicInteger;
* @author Mike
*/
public class DialogAppender extends AppenderSkeleton {
private static final ErrorLogger DEFAULT_LOGGER = new DefaultIdeaErrorLogger();
private static final ErrorLogger[] LOGGERS = {new DefaultIdeaErrorLogger()};
private static final int MAX_ASYNC_LOGGING_EVENTS = 5;
private volatile Runnable myDialogRunnable;
private final AtomicInteger myPendingAppendCounts = new AtomicInteger();
private volatile Runnable myDialogRunnable;
@Override
protected synchronized void append(@NotNull final LoggingEvent event) {
protected synchronized void append(@NotNull LoggingEvent event) {
if (!event.getLevel().isGreaterOrEqual(Level.ERROR) ||
Main.isCommandLine() ||
!IdeaApplication.isLoaded()) {
@@ -60,7 +47,7 @@ public class DialogAppender extends AppenderSkeleton {
//noinspection SSBasedInspection
SwingUtilities.invokeLater(() -> {
try {
appendToLoggers(event, new ErrorLogger[]{ DEFAULT_LOGGER });
appendToLoggers(event, LOGGERS);
}
finally {
myPendingAppendCounts.decrementAndGet();
@@ -74,23 +61,23 @@ public class DialogAppender extends AppenderSkeleton {
return;
}
final IdeaLoggingEvent ideaEvent;
final Object message = event.getMessage();
if (message instanceof IdeaLoggingEvent) {
ideaEvent = (IdeaLoggingEvent)message;
IdeaLoggingEvent ideaEvent;
Object messageObject = event.getMessage();
if (messageObject instanceof IdeaLoggingEvent) {
ideaEvent = (IdeaLoggingEvent)messageObject;
}
else {
ThrowableInformation info = event.getThrowableInformation();
if (info == null) {
return;
}
ideaEvent = extractLoggingEvent(message, info.getThrowable());
if (info == null || info.getThrowable() == null) return;
ideaEvent = extractLoggingEvent(messageObject, info.getThrowable());
}
for (int i = errorLoggers.length - 1; i >= 0; i--) {
final ErrorLogger logger = errorLoggers[i];
ErrorLogger logger = errorLoggers[i];
if (!logger.canHandle(ideaEvent)) {
continue;
}
//noinspection NonAtomicOperationOnVolatileField
myDialogRunnable = () -> {
try {
logger.handle(ideaEvent);
@@ -99,8 +86,7 @@ public class DialogAppender extends AppenderSkeleton {
myDialogRunnable = null;
}
};
final Application app = ApplicationManager.getApplication();
Application app = ApplicationManager.getApplication();
if (app == null) {
new Thread(myDialogRunnable, "dialog appender logger").start();
}
@@ -111,20 +97,27 @@ public class DialogAppender extends AppenderSkeleton {
}
}
private static IdeaLoggingEvent extractLoggingEvent(Object message, Throwable throwable) {
//noinspection ThrowableResultOfMethodCallIgnored
@SuppressWarnings("deprecation")
private static IdeaLoggingEvent extractLoggingEvent(Object messageObject, Throwable throwable) {
Throwable rootCause = ExceptionUtil.getRootCause(throwable);
if (rootCause instanceof LogEventException) {
return ((LogEventException)rootCause).getLogMessage();
}
String strMessage = message == null ? "<null> " : message.toString();
String message = null;
ExceptionWithAttachments withAttachments = ExceptionUtil.findCause(throwable, ExceptionWithAttachments.class);
if (withAttachments instanceof RuntimeExceptionWithAttachments) {
message = ((RuntimeExceptionWithAttachments)withAttachments).getUserMessage();
}
if (message == null && messageObject != null) {
message = messageObject.toString();
}
if (withAttachments != null) {
return LogMessageEx.createEvent(strMessage, ExceptionUtil.getThrowableText(throwable), withAttachments.getAttachments());
return LogMessage.createEvent(throwable, message, withAttachments.getAttachments());
}
else {
return new IdeaLoggingEvent(message, throwable);
}
return new IdeaLoggingEvent(strMessage, throwable);
}
@TestOnly
@@ -138,7 +131,5 @@ public class DialogAppender extends AppenderSkeleton {
}
@Override
public void close() {
}
}
public void close() { }
}
@@ -1,30 +1,14 @@
/*
* Copyright 2000-2009 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.diagnostic;
import com.intellij.openapi.diagnostic.Attachment;
import com.intellij.util.Function;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
import java.util.List;
public class GroupedLogMessage extends AbstractMessage {
private final List<AbstractMessage> myMessages;
public GroupedLogMessage(List<AbstractMessage> messages) {
@@ -35,41 +19,44 @@ public class GroupedLogMessage extends AbstractMessage {
return myMessages;
}
public String getThrowableText() {
@Override
public @NotNull Throwable getThrowable() {
return myMessages.get(0).getThrowable();
}
@Override
public @NotNull String getThrowableText() {
StringBuilder result = new StringBuilder();
for (AbstractMessage each : myMessages) {
result.append(each.getThrowableText()).append("\n\n\n");
if (result.length() > 0) result.append("\n\n\n");
result.append(each.getThrowableText());
}
return result.toString();
}
@Override
public void setRead(boolean aReadFlag) {
for (AbstractMessage message : myMessages) {
message.setRead(aReadFlag);
}
super.setRead(aReadFlag);
}
public Throwable getThrowable() {
return myMessages.get(0).getThrowable();
}
public String getMessage() {
public @Nullable String getMessage() {
return myMessages.get(0).getMessage();
}
@Override
public void setAssigneeId(Integer assigneeId) {
public @NotNull List<Attachment> getAllAttachments() {
return ContainerUtil.concat(getMessages(), message -> message.getAllAttachments());
}
@Override
public void setRead(boolean isRead) {
super.setRead(isRead);
for (AbstractMessage message : myMessages) {
message.setRead(isRead);
}
}
@Override
public void setAssigneeId(@Nullable Integer assigneeId) {
super.setAssigneeId(assigneeId);
for (AbstractMessage message : myMessages) {
message.setAssigneeId(assigneeId);
}
super.setAssigneeId(assigneeId);
}
@NotNull
@Override
public List<Attachment> getAllAttachments() {
return ContainerUtil.concat(getMessages(), message -> message.getAllAttachments());
}
}
}
@@ -1,26 +1,26 @@
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.diagnostic;
import com.intellij.errorreport.bean.ErrorBean;
import com.intellij.errorreport.error.InternalEAPException;
import com.intellij.errorreport.error.NoSuchEAPUserException;
import com.intellij.errorreport.error.UpdateAvailableException;
import com.intellij.idea.IdeaLogger;
import com.intellij.openapi.application.Application;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ApplicationNamesInfo;
import com.intellij.openapi.application.ex.ApplicationInfoEx;
import com.intellij.openapi.diagnostic.Attachment;
import com.intellij.openapi.diagnostic.IdeaLoggingEvent;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.Task;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.updateSettings.impl.UpdateSettings;
import com.intellij.openapi.util.AtomicNotNullLazyValue;
import com.intellij.openapi.util.BuildNumber;
import com.intellij.openapi.util.NotNullLazyValue;
import com.intellij.openapi.util.SystemInfo;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.security.CompositeX509TrustManager;
import com.intellij.util.Consumer;
import com.intellij.util.io.HttpRequests;
import com.intellij.util.net.NetUtils;
import com.intellij.util.net.ssl.CertificateUtil;
@@ -44,9 +44,8 @@ import java.security.cert.Certificate;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import java.util.*;
import java.util.function.Consumer;
import java.util.function.IntConsumer;
/**
@@ -60,6 +59,43 @@ class ITNProxy {
private static final String NEW_THREAD_POST_URL = "https://ea-report.jetbrains.com/trackerRpc/idea/createScr";
private static final String NEW_THREAD_VIEW_URL = "https://ea.jetbrains.com/browser/ea_reports/";
private static final NotNullLazyValue<Map<String, String>> TEMPLATE = AtomicNotNullLazyValue.createValue(() -> {
Map<String, String> template = new LinkedHashMap<>();
template.put("protocol.version", "1");
template.put("os.name", SystemInfo.OS_NAME);
template.put("java.version", SystemInfo.JAVA_VERSION);
template.put("java.vm.vendor", SystemInfo.JAVA_VENDOR);
ApplicationInfoEx appInfo = ApplicationInfoEx.getInstanceEx();
ApplicationNamesInfo namesInfo = ApplicationNamesInfo.getInstance();
BuildNumber build = appInfo.getBuild();
String buildNumberWithAllDetails = build.asString();
if (StringUtil.startsWith(buildNumberWithAllDetails, build.getProductCode() + "-")) {
buildNumberWithAllDetails = buildNumberWithAllDetails.substring(build.getProductCode().length() + 1);
}
template.put("app.name", namesInfo.getProductName());
template.put("app.name.full", namesInfo.getFullProductName());
template.put("app.name.version", appInfo.getVersionName());
template.put("app.eap", Boolean.toString(appInfo.isEAP()));
template.put("app.internal", Boolean.toString(ApplicationManager.getApplication().isInternal()));
template.put("app.build", appInfo.getApiVersion());
template.put("app.version.major", appInfo.getMajorVersion());
template.put("app.version.minor", appInfo.getMinorVersion());
template.put("app.build.date", format(appInfo.getBuildDate()));
template.put("app.build.date.release", format(appInfo.getMajorReleaseBuildDate()));
template.put("app.compilation.timestamp", IdeaLogger.getOurCompilationTimestamp());
template.put("app.product.code", build.getProductCode());
template.put("app.build.number", buildNumberWithAllDetails);
return template;
});
private static @Nullable String format(@Nullable Calendar calendar) {
return calendar == null ? null : Long.toString(calendar.getTime().getTime());
}
static @NotNull List<Developer> fetchDevelopers(@NotNull ProgressIndicator indicator) throws IOException {
return HttpRequests.request(DEVELOPERS_LIST_URL).connect(request -> {
List<Developer> developers = new ArrayList<>();
@@ -79,6 +115,24 @@ class ITNProxy {
});
}
static class ErrorBean {
final IdeaLoggingEvent event;
final String comment;
final String pluginName;
final String pluginVersion;
final String lastActionId;
final int previousException;
ErrorBean(IdeaLoggingEvent event, String comment, String pluginName, String pluginVersion, String lastActionId, int previousException) {
this.event = event;
this.comment = comment;
this.pluginName = pluginName;
this.pluginVersion = pluginVersion;
this.lastActionId = lastActionId;
this.previousException = previousException;
}
}
static void sendError(@Nullable Project project,
@Nullable String login,
@Nullable String password,
@@ -101,7 +155,7 @@ class ITNProxy {
onSuccess.accept(postNewThread(_login, _password, error));
}
catch (Exception ex) {
onError.consume(ex);
onError.accept(ex);
}
}
}.queue();
@@ -147,57 +201,57 @@ class ITNProxy {
private static byte[] createRequest(String login, String password, ErrorBean error) throws UnsupportedEncodingException {
StringBuilder builder = new StringBuilder(8192);
append(builder, "protocol.version", "1");
for (Map.Entry<String, String> entry : TEMPLATE.getValue().entrySet()) {
append(builder, entry.getKey(), entry.getValue());
}
append(builder, "user.login", login);
append(builder, "user.password", password);
append(builder, "os.name", SystemInfo.OS_NAME);
append(builder, "java.version", SystemInfo.JAVA_VERSION);
append(builder, "java.vm.vendor", SystemInfo.JAVA_VENDOR);
ApplicationInfoEx appInfo = ApplicationInfoEx.getInstanceEx();
ApplicationNamesInfo namesInfo = ApplicationNamesInfo.getInstance();
Application application = ApplicationManager.getApplication();
append(builder, "app.name", namesInfo.getProductName());
append(builder, "app.name.full", namesInfo.getFullProductName());
append(builder, "app.name.version", appInfo.getVersionName());
append(builder, "app.eap", Boolean.toString(appInfo.isEAP()));
append(builder, "app.internal", Boolean.toString(application.isInternal()));
append(builder, "app.build", appInfo.getApiVersion());
append(builder, "app.version.major", appInfo.getMajorVersion());
append(builder, "app.version.minor", appInfo.getMinorVersion());
append(builder, "app.build.date", format(appInfo.getBuildDate()));
append(builder, "app.build.date.release", format(appInfo.getMajorReleaseBuildDate()));
append(builder, "app.compilation.timestamp", IdeaLogger.getOurCompilationTimestamp());
BuildNumber build = appInfo.getBuild();
String buildNumberWithAllDetails = build.asString();
append(builder, "app.product.code", build.getProductCode());
if (StringUtil.startsWith(buildNumberWithAllDetails, build.getProductCode() + "-")) {
buildNumberWithAllDetails = buildNumberWithAllDetails.substring(build.getProductCode().length() + 1);
}
append(builder, "app.build.number", buildNumberWithAllDetails);
UpdateSettings updateSettings = UpdateSettings.getInstance();
append(builder, "update.channel.status", updateSettings.getSelectedChannelStatus().getCode());
append(builder, "update.ignored.builds", StringUtil.join(updateSettings.getIgnoredBuildNumbers(), ","));
append(builder, "plugin.name", error.getPluginName());
append(builder, "plugin.version", error.getPluginVersion());
append(builder, "plugin.name", error.pluginName);
append(builder, "plugin.version", error.pluginVersion);
append(builder, "last.action", error.lastActionId);
if (error.previousException > 0) {
append(builder, "previous.exception", Integer.toString(error.previousException));
}
append(builder, "last.action", error.getLastAction());
append(builder, "previous.exception", error.getPreviousException() == null ? null : Integer.toString(error.getPreviousException()));
String message = error.event.getMessage();
String stacktrace = error.event.getThrowableText();
boolean redacted = false;
if (error.event instanceof IdeaReportingEvent) {
String originalMessage = ((IdeaReportingEvent)error.event).getOriginalMessage();
String originalStacktrace = ((IdeaReportingEvent)error.event).getOriginalThrowableText();
boolean messagesDiffer = !Objects.equals(message, originalMessage);
boolean tracesDiffer = !Objects.equals(stacktrace, originalStacktrace);
if (messagesDiffer || tracesDiffer) {
String summary = "";
if (messagesDiffer) summary += "*** message was redacted (" + diff(originalMessage, message) + ")\n";
if (tracesDiffer) summary += "*** stacktrace was redacted (" + diff(originalStacktrace, stacktrace) + ")\n";
message = message != null ? summary + '\n' + message : summary.trim();
redacted = true;
}
}
append(builder, "error.message", message);
append(builder, "error.stacktrace", stacktrace);
append(builder, "error.description", error.comment);
if (redacted) {
append(builder, "error.redacted", Boolean.toString(true));
}
append(builder, "error.message", error.getMessage());
append(builder, "error.stacktrace", error.getStackTrace());
append(builder, "error.description", error.getDescription());
append(builder, "assignee.id", error.getAssigneeId() == null ? null : Integer.toString(error.getAssigneeId()));
for (Attachment attachment : error.getAttachments()) {
append(builder, "attachment.name", attachment.getName());
append(builder, "attachment.value", attachment.getEncodedBytes());
Object eventData = error.event.getData();
if (eventData instanceof AbstractMessage) {
AbstractMessage messageObj = (AbstractMessage)eventData;
for (Attachment attachment : messageObj.getIncludedAttachments()) {
append(builder, "attachment.name", attachment.getName());
append(builder, "attachment.value", attachment.getEncodedBytes());
}
if (messageObj.getAssigneeId() != null) {
append(builder, "assignee.id", Integer.toString(messageObj.getAssigneeId()));
}
}
return builder.toString().getBytes(StandardCharsets.UTF_8);
@@ -209,8 +263,12 @@ class ITNProxy {
builder.append(key).append('=').append(URLEncoder.encode(value, StandardCharsets.UTF_8.name()));
}
private static @Nullable String format(@Nullable Calendar calendar) {
return calendar == null ? null : Long.toString(calendar.getTime().getTime());
private static String diff(String original, String redacted) {
return "original:" + wc(original) + " submitted:" + wc(redacted);
}
private static String wc(String s) {
return s == null ? "-" : StringUtil.splitByLines(s).length + "/" + s.split("[^\\w']+").length + "/" + s.length();
}
private static HttpURLConnection post(URL url, byte[] bytes) throws IOException {
@@ -4,12 +4,11 @@ package com.intellij.diagnostic
import com.intellij.CommonBundle
import com.intellij.credentialStore.hasOnlyUserName
import com.intellij.credentialStore.isFulfilled
import com.intellij.errorreport.bean.ErrorBean
import com.intellij.diagnostic.ITNProxy.ErrorBean
import com.intellij.errorreport.error.InternalEAPException
import com.intellij.errorreport.error.NoSuchEAPUserException
import com.intellij.errorreport.error.UpdateAvailableException
import com.intellij.ide.DataManager
import com.intellij.ide.plugins.PluginManager
import com.intellij.idea.IdeaLogger
import com.intellij.notification.NotificationListener
import com.intellij.notification.NotificationType
@@ -28,7 +27,8 @@ import java.awt.Component
import java.lang.Exception
import javax.swing.Icon
private var previousExceptionThreadId = 0
private const val INTERVAL = 10 * 60 * 1000L // an interval between exceptions to form a chain, ms
@Volatile private var previousReport: Pair<Long, Int>? = null // (timestamp, threadID) of last reported exception
open class ITNReporter : ErrorReportSubmitter() {
override fun getReportActionText(): String = DiagnosticBundle.message("error.report.to.jetbrains.action")
@@ -43,21 +43,18 @@ open class ITNReporter : ErrorReportSubmitter() {
consumer: Consumer<SubmittedReportInfo>): Boolean {
val event = events[0]
val errorBean = ErrorBean(event.throwable, IdeaLogger.ourLastActionId)
errorBean.message = event.message
errorBean.description = additionalInfo
val pluginInfo = IdeErrorsDialog.getPluginInfo(event)
setPluginInfo(event, errorBean)
val lastActionId = IdeaLogger.ourLastActionId
val data = event.data
if (data is AbstractMessage) {
errorBean.assigneeId = data.assigneeId
errorBean.attachments = data.includedAttachments
var previousReportId = -1
val previousException = previousReport
val eventData = event.data
if (previousException != null && eventData is AbstractMessage && eventData.date.time - previousException.first in 0..INTERVAL) {
previousReportId = previousException.second
}
if (previousExceptionThreadId != 0) {
errorBean.previousException = previousExceptionThreadId
}
val errorBean = ErrorBean(event, additionalInfo, pluginInfo?.first, pluginInfo?.second, lastActionId, previousReportId)
val project = CommonDataKeys.PROJECT.getData(DataManager.getInstance().getDataContext(parentComponent))
@@ -70,17 +67,13 @@ open class ITNReporter : ErrorReportSubmitter() {
open fun showErrorInRelease(event: IdeaLoggingEvent): Boolean = false
}
fun setPluginInfo(event: IdeaLoggingEvent, errorBean: ErrorBean) {
val t = event.throwable
if (t != null) {
val pluginId = IdeErrorsDialog.findPluginId(t)
if (pluginId != null) {
val ideaPluginDescriptor = PluginManager.getPlugin(pluginId)
if (ideaPluginDescriptor != null && (!ideaPluginDescriptor.isBundled || ideaPluginDescriptor.allowBundledUpdate())) {
errorBean.pluginName = ideaPluginDescriptor.name
errorBean.pluginVersion = ideaPluginDescriptor.version
}
}
/** @deprecated use [IdeErrorsDialog.getPluginInfo] (to be removed in IDEA 2019) */
@Suppress("unused", "DEPRECATION")
fun setPluginInfo(event: IdeaLoggingEvent, errorBean: com.intellij.errorreport.bean.ErrorBean) {
val pluginInfo = IdeErrorsDialog.getPluginInfo(event)
if (pluginInfo != null) {
errorBean.pluginName = pluginInfo.first
errorBean.pluginVersion = pluginInfo.second
}
}
@@ -104,13 +97,14 @@ private fun submit(errorBean: ErrorBean, parentComponent: Component, callback: C
}
ITNProxy.sendError(project, credentials?.userName, credentials?.getPasswordAsString(), errorBean,
{ threadId -> onSuccess(threadId, callback, project) },
{ threadId -> onSuccess(threadId, errorBean.event.data, callback, project) },
{ e -> onError(e, errorBean, parentComponent, callback, project) })
return true
}
private fun onSuccess(threadId: Int, callback: Consumer<SubmittedReportInfo>, project: Project?) {
previousExceptionThreadId = threadId
private fun onSuccess(threadId: Int, eventData: Any?, callback: Consumer<SubmittedReportInfo>, project: Project?) {
previousReport = if (eventData is AbstractMessage) eventData.date.time to threadId else null
val linkText = threadId.toString()
val reportInfo = SubmittedReportInfo(ITNProxy.getBrowseUrl(threadId), linkText, SubmittedReportInfo.SubmissionStatus.NEW_ISSUE)
callback.consume(reportInfo)
@@ -32,6 +32,7 @@ import com.intellij.openapi.ui.DialogWrapper;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.Condition;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.wm.IdeFrame;
@@ -39,6 +40,7 @@ import com.intellij.openapi.wm.WindowManager;
import com.intellij.ui.*;
import com.intellij.ui.components.JBLabel;
import com.intellij.ui.components.JBScrollPane;
import com.intellij.util.ExceptionUtil;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.text.DateFormatUtil;
import com.intellij.util.ui.JBUI;
@@ -55,10 +57,13 @@ import java.awt.event.ActionEvent;
import java.awt.event.ItemEvent;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.List;
import java.util.zip.CRC32;
import static com.intellij.openapi.util.Pair.pair;
import static java.awt.GridBagConstraints.*;
public class IdeErrorsDialog extends DialogWrapper implements MessagePoolListener, DataProvider {
@@ -66,6 +71,9 @@ public class IdeErrorsDialog extends DialogWrapper implements MessagePoolListene
public static final DataKey<String> CURRENT_TRACE_KEY = DataKey.create("current_stack_trace_key");
private static final String STACKTRACE_ATTACHMENT = "stacktrace.txt";
private static final String INDUCED_STACKTRACES_ATTACHMENT = "induced.txt";
private static final DateFormat TIMESTAMP_FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS");
private static final String ACCEPTED_NOTICES_KEY = "exception.accepted.notices";
private static final String ACCEPTED_NOTICES_SEPARATOR = ":";
private static List<Developer> ourDevelopersList = Collections.emptyList();
@@ -74,8 +82,7 @@ public class IdeErrorsDialog extends DialogWrapper implements MessagePoolListene
private final Project myProject;
private final boolean myInternalMode;
private final Set<String> myAcceptedNotices;
private final List<AbstractMessage> myRawMessages = new ArrayList<>();
private final List<List<AbstractMessage>> myMergedMessages = new ArrayList<>();
private final List<MessageCluster> myMessageClusters = new ArrayList<>(); // exceptions with the same stacktrace
private int myIndex;
private JLabel myCountLabel;
@@ -144,11 +151,12 @@ public class IdeErrorsDialog extends DialogWrapper implements MessagePoolListene
}
private void selectMessage(@Nullable LogMessage defaultMessage) {
for (int i = 0; i < myMergedMessages.size(); i++) {
AbstractMessage message = myMergedMessages.get(i).get(0);
if (defaultMessage != null && message == defaultMessage || defaultMessage == null && !message.isRead()) {
myIndex = i;
return;
for (int i = 0; i < myMessageClusters.size(); i++) {
for (AbstractMessage message : myMessageClusters.get(i).messages) {
if (defaultMessage != null && message == defaultMessage || defaultMessage == null && !message.isRead()) {
myIndex = i;
return;
}
}
}
}
@@ -210,12 +218,17 @@ public class IdeErrorsDialog extends DialogWrapper implements MessagePoolListene
int index = myAttachmentsList.getSelectedIndex();
if (index < 0) {
myAttachmentArea.setText("");
myAttachmentArea.setEditable(false);
}
else if (index == 0) {
myAttachmentArea.setText(getDetailsText(selectedMessage()));
MessageCluster cluster = selectedCluster();
AbstractMessage message = cluster.first;
myAttachmentArea.setText(cluster.detailsText);
myAttachmentArea.setEditable(!(message.isSubmitted() || message.isSubmitting()));
}
else {
myAttachmentArea.setText(selectedMessage().getAllAttachments().get(index - 1).getDisplayText());
myAttachmentArea.setEditable(false);
}
myAttachmentArea.setCaretPosition(0);
});
@@ -227,7 +240,16 @@ public class IdeErrorsDialog extends DialogWrapper implements MessagePoolListene
myAttachmentArea = new JTextArea();
myAttachmentArea.setMargin(JBUI.insets(2));
myAttachmentArea.setEditable(false);
myAttachmentArea.getDocument().addDocumentListener(new DocumentAdapter() {
@Override
protected void textChanged(DocumentEvent e) {
if (myAttachmentsList.getSelectedIndex() == 0) {
String detailsText = myAttachmentArea.getText();
selectedCluster().detailsText = detailsText;
setOKActionEnabled(isOKActionEnabled() && !StringUtil.isEmptyOrSpaces(detailsText));
}
}
});
if (myInternalMode) {
myAssigneeCombo = new ComboBox<>();
@@ -341,8 +363,8 @@ public class IdeErrorsDialog extends DialogWrapper implements MessagePoolListene
@Override
public void doOKAction() {
if (getOKAction().isEnabled()) {
boolean closeDialog = myMergedMessages.size() == 1;
boolean reportingStarted = reportMessage(selectedMessage(), closeDialog);
boolean closeDialog = myMessageClusters.size() == 1;
boolean reportingStarted = reportMessage(selectedCluster(), closeDialog);
if (!closeDialog) {
updateControls();
}
@@ -358,32 +380,58 @@ public class IdeErrorsDialog extends DialogWrapper implements MessagePoolListene
super.dispose();
}
private MessageCluster selectedCluster() {
return myMessageClusters.get(myIndex);
}
private AbstractMessage selectedMessage() {
return myMergedMessages.get(myIndex).get(0);
return selectedCluster().first;
}
private void updateMessages() {
myRawMessages.clear();
myRawMessages.addAll(myMessagePool.getFatalErrors(true, true));
Map<Long, List<AbstractMessage>> messageGroups = new LinkedHashMap<>();
for (AbstractMessage message : myRawMessages) {
List<AbstractMessage> rawMessages = myMessagePool.getFatalErrors(true, true);
Map<Long, MessageCluster> clusters = new LinkedHashMap<>();
for (AbstractMessage raw : rawMessages) {
AbstractMessage message = raw instanceof GroupedLogMessage ? pack((GroupedLogMessage)raw) : raw;
CRC32 digest = new CRC32();
digest.update(StringUtil.getThrowableText(message.getThrowable()).getBytes(StandardCharsets.UTF_8));
messageGroups.computeIfAbsent(digest.getValue(), k -> new ArrayList<>()).add(message);
digest.update(ExceptionUtil.getThrowableText(message.getThrowable()).getBytes(StandardCharsets.UTF_8));
clusters.computeIfAbsent(digest.getValue(), k -> new MessageCluster(message)).messages.add(message);
}
myMergedMessages.clear();
myMergedMessages.addAll(messageGroups.values());
myMessageClusters.clear();
myMessageClusters.addAll(clusters.values());
}
private static AbstractMessage pack(GroupedLogMessage message) {
AbstractMessage mainCause = message.getMessages().get(0);
List<Attachment> attachments = new ArrayList<>();
attachments.add(null);
StringBuilder stacktraces = new StringBuilder();
for (AbstractMessage each : message.getMessages()) {
attachments.addAll(each.getAllAttachments());
if (each != mainCause) {
if (stacktraces.length() > 0) stacktraces.append("\n\n\n");
stacktraces.append(TIMESTAMP_FORMAT.format(each.getDate())).append('\n');
if (!StringUtil.isEmptyOrSpaces(each.getMessage())) stacktraces.append(each.getMessage()).append('\n');
stacktraces.append(each.getThrowableText());
}
}
attachments.set(0, new Attachment(INDUCED_STACKTRACES_ATTACHMENT, stacktraces.toString()));
return new LogMessage(mainCause.getThrowable(), mainCause.getMessage(), attachments);
}
private void updateControls() {
myMergedMessages.get(myIndex).forEach(m -> m.setRead(true));
AbstractMessage message = selectedMessage();
ErrorReportSubmitter submitter = getSubmitter(message.getThrowable());
MessageCluster cluster = selectedCluster();
AbstractMessage message = cluster.first;
ErrorReportSubmitter submitter = cluster.submitter;
cluster.messages.forEach(m -> m.setRead(true));
boolean unsent = !(message.isSubmitted() || message.isSubmitting());
boolean canReport = unsent && submitter != null;
updateLabels(message, submitter);
updateLabels(cluster);
updateDetails(message, canReport);
@@ -398,19 +446,22 @@ public class IdeErrorsDialog extends DialogWrapper implements MessagePoolListene
setOKButtonTooltip(submitter != null ? null : DiagnosticBundle.message("error.report.impossible.tooltip"));
}
private void updateLabels(AbstractMessage message, @Nullable ErrorReportSubmitter submitter) {
myCountLabel.setText(DiagnosticBundle.message("error.list.message.index.count", myIndex + 1, myMergedMessages.size()));
private void updateLabels(MessageCluster cluster) {
AbstractMessage message = cluster.first;
myCountLabel.setText(DiagnosticBundle.message("error.list.message.index.count", myIndex + 1, myMessageClusters.size()));
Throwable t = message.getThrowable();
if (t instanceof MessagePool.TooManyErrorsException) {
myInfoLabel.setText(message.getMessage());
myInfoLabel.setText(t.getMessage());
myDisableLink.setVisible(false);
myForeignPluginWarningLabel.setVisible(false);
myNoticePanel.setVisible(false);
return;
}
PluginId pluginId = findPluginId(t);
IdeaPluginDescriptor plugin = PluginManager.getPlugin(pluginId);
PluginId pluginId = cluster.pluginId;
IdeaPluginDescriptor plugin = cluster.plugin;
StringBuilder info = new StringBuilder();
String url = null;
@@ -426,7 +477,7 @@ public class IdeErrorsDialog extends DialogWrapper implements MessagePoolListene
}
String date = DateFormatUtil.formatPrettyDateTime(message.getDate());
int count = myMergedMessages.get(myIndex).size();
int count = cluster.messages.size();
info.append(' ').append(DiagnosticBundle.message("error.list.message.info", date, count));
if (message.isSubmitted()) {
@@ -445,6 +496,7 @@ public class IdeErrorsDialog extends DialogWrapper implements MessagePoolListene
myDisableLink.setVisible(pluginId != null && !ApplicationInfoEx.getInstanceEx().isEssentialPlugin(pluginId.getIdString()));
ErrorReportSubmitter submitter = cluster.submitter;
if (submitter == null && plugin != null && !PluginManagerMain.isDevelopedByJetBrains(plugin)) {
myForeignPluginWarningLabel.setVisible(true);
String vendor = plugin.getVendor();
@@ -491,7 +543,7 @@ public class IdeErrorsDialog extends DialogWrapper implements MessagePoolListene
myCommentArea.setEditable(canReport);
myAttachmentsList.clear();
myAttachmentsList.addItem("stacktrace.txt", true);
myAttachmentsList.addItem(STACKTRACE_ATTACHMENT, true);
for (Attachment attachment : message.getAllAttachments()) {
myAttachmentsList.addItem(attachment.getName(), myInternalMode || attachment.isIncluded());
}
@@ -533,9 +585,10 @@ public class IdeErrorsDialog extends DialogWrapper implements MessagePoolListene
}
}
private boolean reportMessage(AbstractMessage message, boolean dialogClosed) {
ErrorReportSubmitter submitter = getSubmitter(message.getThrowable());
private boolean reportMessage(MessageCluster cluster, boolean dialogClosed) {
ErrorReportSubmitter submitter = cluster.submitter;
if (submitter == null) return false;
AbstractMessage message = cluster.first;
message.setSubmitting(true);
@@ -547,13 +600,8 @@ public class IdeErrorsDialog extends DialogWrapper implements MessagePoolListene
}
}
IdeaLoggingEvent[] events;
if (message instanceof GroupedLogMessage) {
events = ((GroupedLogMessage)message).getMessages().stream().map(IdeErrorsDialog::getEvent).toArray(IdeaLoggingEvent[]::new);
}
else {
events = new IdeaLoggingEvent[]{getEvent(message)};
}
Pair<String, String> pair = cluster.decouple();
IdeaLoggingEvent[] events = {new IdeaReportingEvent(message, pair.first, pair.second, cluster.plugin)};
Container parentComponent = getRootPane();
if (dialogClosed) {
@@ -564,64 +612,52 @@ public class IdeErrorsDialog extends DialogWrapper implements MessagePoolListene
return submitter.submit(events, message.getAdditionalInfo(), parentComponent, reportInfo -> {
message.setSubmitting(false);
message.setSubmitted(reportInfo);
ApplicationManager.getApplication().invokeLater(() -> updateOnSubmit());
UIUtil.invokeLaterIfNeeded(() -> updateOnSubmit());
});
}
private static IdeaLoggingEvent getEvent(AbstractMessage message) {
if (message instanceof LogMessageEx) {
return ((LogMessageEx)message).toEvent();
}
else {
return new IdeaLoggingEvent(message.getMessage(), message.getThrowable(), message);
}
}
private void disablePlugin() {
PluginId pluginId = findPluginId(selectedMessage().getThrowable());
if (pluginId != null) {
IdeaPluginDescriptor plugin = PluginManager.getPlugin(pluginId);
if (plugin != null) {
Ref<Boolean> hasDependants = new Ref<>(false);
PluginManagerCore.checkDependants(plugin, PluginManager::getPlugin, dependantId -> {
if (PluginManagerCore.CORE_PLUGIN_ID.equals(dependantId.getIdString())) {
return true;
}
else {
hasDependants.set(true);
return false;
}
});
boolean canRestart = ApplicationManager.getApplication().isRestartCapable();
String message =
"<html>" +
DiagnosticBundle.message("error.dialog.disable.prompt", plugin.getName()) + "<br/>" +
DiagnosticBundle.message(hasDependants.get() ? "error.dialog.disable.prompt.deps" : "error.dialog.disable.prompt.lone") + "<br/><br/>" +
DiagnosticBundle.message(canRestart ? "error.dialog.disable.plugin.can.restart" : "error.dialog.disable.plugin.no.restart") +
"</html>";
String title = DiagnosticBundle.message("error.dialog.disable.plugin.title");
String disable = DiagnosticBundle.message("error.dialog.disable.plugin.action.disable");
String cancel = IdeBundle.message("button.cancel");
boolean doDisable, doRestart;
if (canRestart) {
String restart = DiagnosticBundle.message("error.dialog.disable.plugin.action.disableAndRestart");
int result = Messages.showYesNoCancelDialog(myProject, message, title, disable, restart, cancel, Messages.getQuestionIcon());
doDisable = result == Messages.YES || result == Messages.NO;
doRestart = result == Messages.NO;
IdeaPluginDescriptor plugin = selectedCluster().plugin;
if (plugin != null) {
Ref<Boolean> hasDependants = new Ref<>(false);
PluginManagerCore.checkDependants(plugin, PluginManager::getPlugin, dependantId -> {
if (PluginManagerCore.CORE_PLUGIN_ID.equals(dependantId.getIdString())) {
return true;
}
else {
int result = Messages.showYesNoDialog(myProject, message, title, disable, cancel, Messages.getQuestionIcon());
doDisable = result == Messages.YES;
doRestart = false;
hasDependants.set(true);
return false;
}
});
boolean canRestart = ApplicationManager.getApplication().isRestartCapable();
if (doDisable) {
PluginManagerCore.disablePlugin(pluginId.getIdString());
if (doRestart) {
ApplicationManager.getApplication().restart();
}
String message =
"<html>" +
DiagnosticBundle.message("error.dialog.disable.prompt", plugin.getName()) + "<br/>" +
DiagnosticBundle.message(hasDependants.get() ? "error.dialog.disable.prompt.deps" : "error.dialog.disable.prompt.lone") + "<br/><br/>" +
DiagnosticBundle.message(canRestart ? "error.dialog.disable.plugin.can.restart" : "error.dialog.disable.plugin.no.restart") +
"</html>";
String title = DiagnosticBundle.message("error.dialog.disable.plugin.title");
String disable = DiagnosticBundle.message("error.dialog.disable.plugin.action.disable");
String cancel = IdeBundle.message("button.cancel");
boolean doDisable, doRestart;
if (canRestart) {
String restart = DiagnosticBundle.message("error.dialog.disable.plugin.action.disableAndRestart");
int result = Messages.showYesNoCancelDialog(myProject, message, title, disable, restart, cancel, Messages.getQuestionIcon());
doDisable = result == Messages.YES || result == Messages.NO;
doRestart = result == Messages.NO;
}
else {
int result = Messages.showYesNoDialog(myProject, message, title, disable, cancel, Messages.getQuestionIcon());
doDisable = result == Messages.YES;
doRestart = false;
}
if (doDisable) {
PluginManagerCore.disablePlugin(plugin.getPluginId().getIdString());
if (doRestart) {
ApplicationManager.getApplication().restart();
}
}
}
@@ -667,7 +703,7 @@ public class IdeErrorsDialog extends DialogWrapper implements MessagePoolListene
@Override
public void update(AnActionEvent e) {
e.getPresentation().setEnabled(myIndex < myMergedMessages.size() - 1);
e.getPresentation().setEnabled(myIndex < myMessageClusters.size() - 1);
}
@Override
@@ -762,20 +798,72 @@ public class IdeErrorsDialog extends DialogWrapper implements MessagePoolListene
@Override
public Object getData(String dataId) {
return CURRENT_TRACE_KEY.is(dataId) ? getDetailsText(selectedMessage()) : null;
return CURRENT_TRACE_KEY.is(dataId) ? selectedMessage().getThrowableText() : null;
}
/* helpers */
private static String getDetailsText(AbstractMessage message) {
Throwable t = message.getThrowable();
return t instanceof MessagePool.TooManyErrorsException ? message.getMessage() :
t instanceof NullPointerException ? message.getThrowableText() :
message.getMessage() + "\n" + message.getThrowableText();
private static class MessageCluster {
private final AbstractMessage first;
private final @Nullable PluginId pluginId;
private final @Nullable IdeaPluginDescriptor plugin;
private final @Nullable ErrorReportSubmitter submitter;
private String detailsText;
private final List<AbstractMessage> messages = new ArrayList<>();
private MessageCluster(AbstractMessage message) {
first = message;
pluginId = findPluginId(message.getThrowable());
plugin = PluginManager.getPlugin(pluginId);
submitter = getSubmitter(message.getThrowable(), pluginId, plugin);
detailsText = detailsText();
}
private String detailsText() {
AbstractMessage message = first;
if (message instanceof GroupedLogMessage) {
message = ((GroupedLogMessage)message).getMessages().get(0);
}
Throwable t = message.getThrowable();
if (t instanceof MessagePool.TooManyErrorsException) {
return t.getMessage();
}
String userMessage = message.getMessage(), stacktrace = message.getThrowableText();
return StringUtil.isEmptyOrSpaces(userMessage) ? stacktrace : userMessage + "\n\n" + stacktrace;
}
private Pair<String, String> decouple() {
@SuppressWarnings("ThrowableNotThrown") String className = first.getThrowable().getClass().getName();
int p = detailsText.indexOf(className);
if (p == 0) {
return pair(null, detailsText);
}
else if (p > 0 && detailsText.charAt(p - 1) == '\n') {
return pair(detailsText.substring(0, p).trim(), detailsText.substring(p));
}
else {
return pair("*** exception class was changed or removed", detailsText);
}
}
}
@Nullable
public static PluginId findPluginId(@NotNull Throwable t) {
public static @Nullable Pair<String, String> getPluginInfo(@NotNull IdeaLoggingEvent event) {
IdeaPluginDescriptor plugin = null;
if (event instanceof IdeaReportingEvent) {
plugin = ((IdeaReportingEvent)event).getPlugin();
}
else {
Throwable t = event.getThrowable();
if (t != null) {
plugin = PluginManager.getPlugin(findPluginId(t));
}
}
return plugin != null && (!plugin.isBundled() || plugin.allowBundledUpdate()) ? pair(plugin.getName(), plugin.getVersion()) : null;
}
public static @Nullable PluginId findPluginId(@NotNull Throwable t) {
if (t instanceof PluginException) {
return ((PluginException)t).getPluginId();
}
@@ -859,8 +947,13 @@ public class IdeErrorsDialog extends DialogWrapper implements MessagePoolListene
}
}
@Nullable
static ErrorReportSubmitter getSubmitter(@NotNull Throwable t) {
static @Nullable ErrorReportSubmitter getSubmitter(@NotNull Throwable t) {
PluginId pluginId = findPluginId(t);
IdeaPluginDescriptor plugin = PluginManager.getPlugin(pluginId);
return getSubmitter(t, pluginId, plugin);
}
private static ErrorReportSubmitter getSubmitter(Throwable t, PluginId pluginId, IdeaPluginDescriptor plugin) {
if (t instanceof MessagePool.TooManyErrorsException || t instanceof AbstractMethodError) {
return null;
}
@@ -873,9 +966,6 @@ public class IdeErrorsDialog extends DialogWrapper implements MessagePoolListene
return null;
}
PluginId pluginId = findPluginId(t);
IdeaPluginDescriptor plugin = PluginManager.getPlugin(pluginId);
if (plugin != null) {
for (ErrorReportSubmitter reporter : reporters) {
PluginDescriptor descriptor = reporter.getPluginDescriptor();
@@ -182,10 +182,7 @@ public class IdeMessagePanel extends JPanel implements MessagePoolListener, Icon
else if (state == IdeErrorsIcon.State.UnreadErrors && !myNotificationPopupAlreadyShown) {
Project project = myFrame == null ? null : myFrame.getProject();
if (project != null) {
ApplicationManager.getApplication().invokeLater(() -> {
String notificationText = getNotificationText(myMessagePool.getFatalErrors(false, false));
showErrorNotification(notificationText, project);
}, project.getDisposed());
ApplicationManager.getApplication().invokeLater(() -> showErrorNotification(project), project.getDisposed());
myNotificationPopupAlreadyShown = true;
}
}
@@ -194,20 +191,15 @@ public class IdeMessagePanel extends JPanel implements MessagePoolListener, Icon
private static final String ERROR_TITLE = DiagnosticBundle.message("error.new.notification.title");
private static final String ERROR_LINK = DiagnosticBundle.message("error.new.notification.link");
private void showErrorNotification(@Nullable String notificationText, @NotNull Project project) {
String title = notificationText == null ? ERROR_TITLE : "";
String content = notificationText == null ? "" : notificationText;
Notification notification = new Notification("", AllIcons.Ide.FatalError, title, null, content, NotificationType.ERROR, null);
if (notificationText == null) {
notification.addAction(new NotificationAction(ERROR_LINK) {
@Override
public void actionPerformed(@NotNull AnActionEvent e, @NotNull Notification notification) {
notification.expire();
doOpenErrorsDialog(null);
}
});
}
private void showErrorNotification(@NotNull Project project) {
Notification notification = new Notification("", AllIcons.Ide.FatalError, ERROR_TITLE, null, null, NotificationType.ERROR, null);
notification.addAction(new NotificationAction(ERROR_LINK) {
@Override
public void actionPerformed(@NotNull AnActionEvent e, @NotNull Notification notification) {
notification.expire();
doOpenErrorsDialog(null);
}
});
BalloonLayout layout = myFrame.getBalloonLayout();
assert layout != null;
@@ -222,27 +214,4 @@ public class IdeMessagePanel extends JPanel implements MessagePoolListener, Icon
Disposer.register(myBalloon, () -> myBalloon = null);
layout.add(myBalloon);
}
private static String getNotificationText(List<AbstractMessage> messages) {
String result = null;
for (AbstractMessage message : messages) {
String s;
if (message instanceof LogMessageEx) {
s = ((LogMessageEx)message).getNotificationText();
}
else if (message instanceof GroupedLogMessage) {
s = getNotificationText(((GroupedLogMessage)message).getMessages());
}
else {
return null;
}
if (result == null) {
result = s;
}
else if (!result.equals(s)) {
return null; // if texts are different, show default
}
}
return result;
}
}
@@ -0,0 +1,69 @@
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.diagnostic;
import com.intellij.ide.plugins.IdeaPluginDescriptor;
import com.intellij.openapi.diagnostic.IdeaLoggingEvent;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.PrintStream;
import java.io.PrintWriter;
public class IdeaReportingEvent extends IdeaLoggingEvent {
private final String myStacktrace;
private final Throwable myThrowable;
private final IdeaPluginDescriptor myPlugin;
public IdeaReportingEvent(@NotNull AbstractMessage messageObject, String message, @NotNull String stacktrace, IdeaPluginDescriptor plugin) {
super(message, null, messageObject);
myStacktrace = stacktrace;
myThrowable = new TextBasedThrowable(stacktrace);
myPlugin = plugin;
}
public @Nullable String getOriginalMessage() {
return getData().getMessage();
}
public @NotNull String getOriginalThrowableText() {
return getData().getThrowableText();
}
public @Nullable IdeaPluginDescriptor getPlugin() {
return myPlugin;
}
@Override
public Throwable getThrowable() {
return myThrowable;
}
@Override
public String getThrowableText() {
return myStacktrace;
}
@Override
@SuppressWarnings("ConstantConditions")
public @NotNull AbstractMessage getData() {
return (AbstractMessage)super.getData();
}
static class TextBasedThrowable extends Throwable {
private final String myStacktrace;
TextBasedThrowable(String stacktrace) {
myStacktrace = stacktrace;
}
@Override
public void printStackTrace(PrintWriter s) {
s.print(myStacktrace);
}
@Override
public void printStackTrace(PrintStream s) {
s.print(myStacktrace);
}
}
}
@@ -1,35 +1,20 @@
/*
* Copyright 2000-2013 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.diagnostic;
import com.intellij.openapi.diagnostic.Attachment;
import com.intellij.openapi.diagnostic.ExceptionWithAttachments;
import com.intellij.openapi.diagnostic.IdeaLoggingEvent;
import com.intellij.psi.impl.DebugUtil;
import com.intellij.openapi.diagnostic.RuntimeExceptionWithAttachments;
import com.intellij.util.ArrayUtil;
import org.jetbrains.annotations.NotNull;
/**
* @author peter
*/
/** @deprecated use {@link RuntimeExceptionWithAttachments#RuntimeExceptionWithAttachments(String, String, Attachment...)} (to be removed in IDEA 2020) */
@SuppressWarnings({"DeprecatedIsStillUsed", "unused"})
public class LogEventException extends RuntimeException implements ExceptionWithAttachments {
private final IdeaLoggingEvent myLogMessage;
public LogEventException(String userMessage, final String details, final Attachment... attachments) {
this(LogMessageEx.createEvent(userMessage, details + "\n\n" + DebugUtil.currentStackTrace(), attachments));
public LogEventException(String userMessage, String details, Attachment... attachments) {
this(LogMessage.createEvent(new Throwable(details), userMessage, attachments));
}
public LogEventException(IdeaLoggingEvent logMessage) {
@@ -52,4 +37,4 @@ public class LogEventException extends RuntimeException implements ExceptionWith
}
return Attachment.EMPTY_ARRAY;
}
}
}
@@ -1,18 +1,4 @@
/*
* Copyright 2000-2009 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.diagnostic;
import com.intellij.idea.IdeaLogger;
@@ -22,83 +8,70 @@ import com.intellij.openapi.util.objectTree.ThrowableInterner;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.util.containers.ContainerUtil;
import org.apache.log4j.Layout;
import org.apache.log4j.spi.LoggingEvent;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
public class LogMessage extends AbstractMessage {
private final Throwable myThrowable;
private final String myHeader;
private List<Attachment> myAttachments;
private final String myMessage;
private final List<Attachment> myAttachments;
LogMessage(@NotNull LoggingEvent event) {
Throwable throwable = event.getThrowableInformation() == null ? null : event.getThrowableInformation().getThrowable();
myThrowable = throwable == null ? null : ThrowableInterner.intern(throwable);
LogMessage(Throwable throwable, String message, List<Attachment> attachments) {
myThrowable = ThrowableInterner.intern(throwable);
myHeader =
event.getMessage() == null || event.getMessage().toString().isEmpty() ?
getThrowable().toString() : event.getMessage().toString();
}
LogMessage(@NotNull IdeaLoggingEvent event) {
Throwable throwable = event.getThrowable();
myThrowable = throwable == null ? null : ThrowableInterner.intern(throwable);
String header = null;
if (!StringUtil.isEmptyOrSpaces(event.getMessage())) {
header = event.getMessage();
}
if (myThrowable != null) {
String message = myThrowable.getMessage();
if (StringUtil.isNotEmpty(message) && (header == null || !header.startsWith(message))) {
if (header != null) {
if (header.endsWith(":")) header += " ";
else if (!header.endsWith(": ")) header += ": ";
header += message;
}
else {
header = message;
}
String str = message;
if (str != null && throwable.getMessage() != null) {
str = StringUtil.trimStart(str, throwable.getMessage());
if (str != message) {
str = StringUtil.trimStart(str, ": ");
}
}
if (header == null) {
header = "No message";
if ("null".equals(str)) {
str = null;
}
myMessage = StringUtil.nullize(str, true);
myHeader = header;
myAttachments = new ArrayList<>(ContainerUtil.filter(attachments, attachment -> attachment != null));
}
@Override
public Throwable getThrowable() {
public @NotNull Throwable getThrowable() {
return myThrowable;
}
@Override
public String getMessage() {
return myHeader;
public @NotNull String getThrowableText() {
return StringUtil.join(IdeaLogger.getThrowableRenderer().doRender(myThrowable), Layout.LINE_SEP);
}
@Override
public String getThrowableText() {
return StringUtil.join(IdeaLogger.getThrowableRenderer().doRender(getThrowable()), Layout.LINE_SEP);
public @NotNull String getMessage() {
return myMessage != null ? myMessage : "";
}
@SuppressWarnings("WeakerAccess")
public void addAttachment(@NotNull Attachment attachment) {
if (myAttachments == null) {
myAttachments = ContainerUtil.createLockFreeCopyOnWriteList();
}
@Override
public @NotNull List<Attachment> getAllAttachments() {
return Collections.unmodifiableList(myAttachments);
}
/** @deprecated pass all attachments to {@link #createEvent(Throwable, String, Attachment...)} (to be removed in IDEA 2019) */
public synchronized void addAttachment(@NotNull Attachment attachment) {
myAttachments.add(attachment);
}
@NotNull
@Override
public List<Attachment> getAllAttachments() {
return myAttachments != null ? myAttachments : Collections.emptyList();
// factory methods
/**
* @param userMessage user-friendly message description (short, single line if possible)
* @param attachments attachments that will be suggested to include to the report
*/
public static IdeaLoggingEvent createEvent(@NotNull Throwable throwable, @Nullable String userMessage, @NotNull Attachment... attachments) {
@SuppressWarnings("deprecation") AbstractMessage message = new LogMessageEx(throwable, userMessage, Arrays.asList(attachments), null);
return new IdeaLoggingEvent(userMessage, throwable, message);
}
}
}
@@ -1,125 +1,57 @@
/*
* Copyright 2000-2011 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.diagnostic;
import com.intellij.diagnostic.IdeaReportingEvent.TextBasedThrowable;
import com.intellij.openapi.diagnostic.Attachment;
import com.intellij.openapi.diagnostic.IdeaLoggingEvent;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.util.ExceptionUtil;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
/**
* @author ksafonov
*/
/** @deprecated use {@link LogMessage} (to be removed in IDEA 2020) */
@SuppressWarnings({"DeprecatedIsStillUsed", "unused"})
public class LogMessageEx extends LogMessage {
private final IdeaLoggingEvent myEvent;
private final String myTitle;
private final String myNotificationText;
/**
* @param title text to show in Event Log tool window entry (it comes before 'more')
* @param notificationText text to show in the error balloon that is popped up automatically
*/
public LogMessageEx(IdeaLoggingEvent aEvent, String title, String notificationText) {
super(aEvent);
myEvent = aEvent;
myTitle = title;
myNotificationText = notificationText;
/** @deprecated use {@link Logger#error(String, Throwable, Attachment...)} or {@link LogMessage} (to be removed in IDEA 2020) */
public LogMessageEx(IdeaLoggingEvent event, String title, String notificationText) {
super(event.getThrowable(), event.getMessage(), attachments(event));
}
/**
* @return text to show in the error balloon that is popped up automatically
*/
public String getNotificationText() {
return myNotificationText;
// needed for compatibility - some reporters expect/check for instances of this class
LogMessageEx(Throwable throwable, String message, List<Attachment> attachments, String notificationText) {
super(throwable, message, attachments);
}
/**
* @return text to show in Event Log tool window entry (it comes before 'more')
*/
public String getTitle() {
return myTitle;
private static List<Attachment> attachments(IdeaLoggingEvent event) {
Object data = event.getData();
return data instanceof AbstractMessage ? ((AbstractMessage)data).getAllAttachments() : Collections.emptyList();
}
public IdeaLoggingEvent toEvent() {
return myEvent;
/** @deprecated use {@link Logger#error(String, Throwable, Attachment...)} (to be removed in IDEA 2020) */
public static IdeaLoggingEvent createEvent(String message, String details, Attachment... attachments) {
return createEvent(new TextBasedThrowable(details), message, attachments);
}
/**
* @param userMessage user-friendly message description (short, single line if possible)
* @param details technical details (exception stack trace etc.)
* @param attachments attachments that will be suggested to include to the report
*/
public static IdeaLoggingEvent createEvent(String userMessage, final String details, final Attachment... attachments) {
return createEvent(userMessage, details, userMessage, null, Arrays.asList(attachments));
/** @deprecated use {@link Logger#error(String, Throwable, Attachment...)} (to be removed in IDEA 2020) */
public static IdeaLoggingEvent createEvent(String message, String details, String title, String notificationText, Attachment attachment) {
return createEvent(new TextBasedThrowable(details), message, attachment);
}
/**
* @param userMessage user-friendly message description (short, single line if possible)
* @param details technical details (exception stack trace etc.)
* @param title text to show in Event Log tool window entry (it comes before 'more'), use {@code null} to reuse {@code userMessage}
* @param notificationText text to show in the error balloon that is popped up automatically. Default is {@code com.intellij.diagnostic.IdeMessagePanel#INTERNAL_ERROR_NOTICE}
* @param attachments attachments that will be suggested to include to the report
*/
public static IdeaLoggingEvent createEvent(final String userMessage,
final String details,
@Nullable final String title,
@Nullable final String notificationText,
final Collection<Attachment> attachments) {
final Throwable throwable = new Throwable() {
@Override
public void printStackTrace(PrintWriter s) {
s.print(details);
}
@Override
public void printStackTrace(PrintStream s) {
s.print(details);
}
};
return new IdeaLoggingEvent(userMessage, throwable) {
@Override
public Object getData() {
final LogMessageEx logMessageEx = new LogMessageEx(this, title != null ? title : userMessage, notificationText);
for (Attachment attachment : attachments) {
logMessageEx.addAttachment(attachment);
}
return logMessageEx;
}
};
/** @deprecated use {@link Logger#error(String, Throwable, Attachment...)} (to be removed in IDEA 2020) */
public static IdeaLoggingEvent createEvent(String message, String details, String title, String notificationText, Collection<Attachment> attachments) {
Attachment[] array = attachments != null ? attachments.toArray(Attachment.EMPTY_ARRAY) : Attachment.EMPTY_ARRAY;
return createEvent(new TextBasedThrowable(details), message, array);
}
public static void error(@NotNull Logger logger, @NonNls @NotNull String message, @NotNull String... attachmentText) {
/** @deprecated use {@link Logger#error(String, Throwable, Attachment...)} and {@link AttachmentFactory#createContext} (to be removed in IDEA 2020) */
public static void error(@NotNull Logger logger, @NotNull String message, @NotNull String... attachmentText) {
error(logger, message, new Throwable(), attachmentText);
}
public static void error(@NotNull Logger logger,
@NotNull String message,
@NotNull Throwable cause,
@NotNull String... attachmentText) {
/** @deprecated use {@link Logger#error(String, Throwable, Attachment...)} and {@link AttachmentFactory#createContext} (to be removed in IDEA 2020) */
public static void error(Logger logger, String message, Throwable cause, String... attachmentText) {
StringBuilder detailsBuffer = new StringBuilder();
for (String detail : attachmentText) {
detailsBuffer.append(detail).append(",");
@@ -127,23 +59,11 @@ public class LogMessageEx extends LogMessage {
if (attachmentText.length > 0 && detailsBuffer.length() > 0) {
detailsBuffer.setLength(detailsBuffer.length() - 1);
}
Attachment attachment = detailsBuffer.length() > 0 ? new Attachment("current-context.txt", detailsBuffer.toString()) : null;
logger.error(createEvent(message, ExceptionUtil.getThrowableText(cause), null, null, attachment));
if (detailsBuffer.length() > 0) {
logger.error(message, cause, AttachmentFactory.createContext(detailsBuffer));
}
else {
logger.error(message, cause);
}
}
/**
* @param userMessage user-friendly message description (short, single line if possible)
* @param details technical details (exception stack trace etc.)
* @param title text to show in Event Log tool window entry (it comes before 'more'), use {@code null} to reuse {@code userMessage}
* @param notificationText text to show in the error balloon that is popped up automatically. Default is {@code com.intellij.diagnostic.IdeMessagePanel#INTERNAL_ERROR_NOTICE}
* @param attachment attachment that will be suggested to include to the report
*/
public static IdeaLoggingEvent createEvent(String userMessage,
final String details,
@Nullable final String title,
@Nullable final String notificationText,
@Nullable Attachment attachment) {
return createEvent(userMessage, details, title, notificationText,
attachment != null ? Collections.singletonList(attachment) : Collections.emptyList());
}
}
}
@@ -4,12 +4,10 @@ package com.intellij.diagnostic;
import com.intellij.openapi.diagnostic.IdeaLoggingEvent;
import com.intellij.util.concurrency.AppExecutorUtil;
import com.intellij.util.containers.ContainerUtil;
import org.apache.log4j.Level;
import org.apache.log4j.LogManager;
import org.apache.log4j.spi.LoggingEvent;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Future;
@@ -35,14 +33,21 @@ public class MessagePool {
private MessagePool() { }
public void addIdeFatalMessage(@NotNull IdeaLoggingEvent event) {
Object data = event.getData();
LogMessage message = data instanceof LogMessage ? (LogMessage)data : new LogMessage(event);
if (myErrors.size() < MAX_POOL_SIZE) {
myGrouper.addToGroup(message);
Object data = event.getData();
if (data instanceof GroupedLogMessage) {
myGrouper.addToGroup(new LogMessage(new Throwable(), "illegal reuse a grouped message", Collections.emptyList()));
}
else if (data instanceof AbstractMessage) {
myGrouper.addToGroup((AbstractMessage)data);
}
else {
myGrouper.addToGroup(new LogMessage(event.getThrowable(), event.getMessage(), Collections.emptyList()));
}
}
else if (myErrors.size() == MAX_POOL_SIZE) {
TooManyErrorsException t = new TooManyErrorsException();
myGrouper.addToGroup(new LogMessage(new LoggingEvent(t.getMessage(), LogManager.getRootLogger(), Level.ERROR, null, t)));
TooManyErrorsException e = new TooManyErrorsException();
myGrouper.addToGroup(new LogMessage(e, null, Collections.emptyList()));
}
}
@@ -7,10 +7,8 @@ import com.intellij.util.ExceptionUtil;
import java.util.Collections;
import java.util.List;
/**
* @author stathik
* @since May 5, 2003
*/
/** @deprecated trivial and not particularly helpful, could be replaced with {@link java.util.Map} (to be removed in IDEA 2020) */
@SuppressWarnings({"unused", "DeprecatedIsStillUsed"})
public class ErrorBean {
private final String stackTrace;
private final String lastAction;
@@ -70,14 +68,14 @@ public class ErrorBean {
this.pluginVersion = pluginVersion;
}
public void setAttachments(List<Attachment> attachments) {
this.attachments = attachments;
}
public List<Attachment> getAttachments() {
return attachments;
}
public void setAttachments(List<Attachment> attachments) {
this.attachments = attachments;
}
public Integer getAssigneeId() {
return assigneeId;
}
@@ -1,9 +1,7 @@
/*
* Copyright 2000-2017 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
*/
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.idea;
import com.intellij.diagnostic.LogMessageEx;
import com.intellij.diagnostic.LogMessage;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ex.ApplicationInfoEx;
import com.intellij.openapi.application.impl.ApplicationImpl;
@@ -95,8 +93,7 @@ public class IdeaLogger extends Log4jBasedLogger {
@Override
public void error(String message, @Nullable Throwable t, @NotNull Attachment... attachments) {
String trace = ExceptionUtil.getThrowableText(t != null ? t : new Throwable());
myLogger.error(LogMessageEx.createEvent(message, trace, attachments));
myLogger.error(LogMessage.createEvent(t != null ? t : new Throwable(), message, attachments));
}
@Override
@@ -120,11 +117,7 @@ public class IdeaLogger extends Log4jBasedLogger {
}
private void logErrorHeader() {
String info = ourApplicationInfoProvider.getInfo();
if (info != null) {
myLogger.error(info);
}
myLogger.error(ourApplicationInfoProvider.getInfo());
if (ourCompilationTimestamp != null) {
myLogger.error("Internal version. Compiled " + ourCompilationTimestamp);
@@ -1,42 +1,45 @@
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.diagnostic;
import com.intellij.openapi.diagnostic.Attachment;
import com.intellij.openapi.util.io.FileUtil;
import org.junit.Assert;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.testFramework.rules.TempDirectory;
import org.junit.Rule;
import org.junit.Test;
import java.io.*;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import static org.assertj.core.api.Assertions.assertThat;
public class AttachmentFactoryTest {
@Rule public TempDirectory tempDir = new TempDirectory();
@Test
public void testBigFilesStoredOnDisk() throws IOException {
final File testFile = FileUtil.createTempFile("test", ".bin", true);
try {
FileUtil.writeToFile(testFile, new byte[150000]);
Attachment attachment = AttachmentFactory.createAttachment(testFile, true);
File testFile = tempDir.newFile("a big one.txt");
String content = StringUtil.repeat("*", 100 * 1024);
byte[] contentBytes = content.getBytes(StandardCharsets.UTF_8);
FileUtil.writeToFile(testFile, contentBytes);
try (InputStream contentStream = attachment.openContentStream()) {
Assert.assertTrue(contentStream instanceof FileInputStream);
}
} finally {
//noinspection ResultOfMethodCallIgnored
testFile.delete();
}
Attachment attachment = AttachmentFactory.createAttachment(testFile, false);
assertThat(attachment.getDisplayText()).isNotEmpty().isNotEqualTo(content);
assertThat(attachment.getBytes()).isEqualTo(contentBytes);
assertThat(FileUtil.loadBytes(attachment.openContentStream())).isEqualTo(contentBytes);
}
@Test
public void testSmallFilesStoredInMemory() throws IOException {
final File testFile = FileUtil.createTempFile("test", ".bin", true);
try {
FileUtil.writeToFile(testFile, new byte[1500]);
Attachment attachment = AttachmentFactory.createAttachment(testFile, true);
File testFile = tempDir.newFile("a little one.txt");
String content = StringUtil.repeat("*", 1024);
byte[] contentBytes = content.getBytes(StandardCharsets.UTF_8);
FileUtil.writeToFile(testFile, contentBytes);
try (InputStream contentStream = attachment.openContentStream()) {
Assert.assertTrue(contentStream instanceof ByteArrayInputStream);
}
} finally {
//noinspection ResultOfMethodCallIgnored
testFile.delete();
}
Attachment attachment = AttachmentFactory.createAttachment(testFile, false);
assertThat(attachment.getDisplayText()).isEqualTo(content);
assertThat(attachment.getBytes()).isEqualTo(contentBytes);
assertThat(FileUtil.loadBytes(attachment.openContentStream())).isEqualTo(contentBytes);
}
}
}
@@ -1,18 +1,4 @@
/*
* Copyright 2000-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.testGuiFramework.impl
import com.intellij.diagnostic.MessagePool
@@ -319,7 +305,7 @@ object GuiTestUtilKt {
val freshErrorMessages = errorMessages.filter { it.date > afterDate }
val errors = mutableListOf<Error>()
for (errorMessage in freshErrorMessages) {
val messageBuilder = StringBuilder(errorMessage.message)
val messageBuilder = StringBuilder(errorMessage.message ?: "")
val additionalInfo : String? = errorMessage.additionalInfo
if (additionalInfo != null && additionalInfo.isNotEmpty())
messageBuilder.append(System.getProperty("line.separator")).append("Additional Info: ").append(additionalInfo)
@@ -1,18 +1,4 @@
/*
* Copyright 2000-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.openapi.diagnostic;
import com.intellij.openapi.util.io.FileUtil;
@@ -32,66 +18,36 @@ public class Attachment {
public static final Attachment[] EMPTY_ARRAY = new Attachment[0];
private final String myPath;
@Nullable private final File myTemporaryFile;
@Nullable private final byte[] myBytes;
private boolean myIncluded; // opt-out for traces, opt-in otherwise
private final String myDisplayText;
public Attachment(@NotNull String path, @NotNull String content) {
this(path, content.getBytes(CharsetToolkit.UTF8_CHARSET), content);
}
public Attachment(@NotNull String path, @NotNull byte[] bytes, @NotNull String displayText) {
myPath = path;
myDisplayText = displayText;
myBytes = bytes;
myTemporaryFile = null;
}
public Attachment(@NotNull String path, @NotNull InputStream inputStream, @NotNull String displayText) {
myPath = path;
myDisplayText = displayText;
myBytes = null;
File temporaryFile;
try {
temporaryFile = FileUtil.createTempFile("intellij-attachment", ".bin", true);
temporaryFile.deleteOnExit();
} catch (IOException e) {
LOG.error("Unable to create temp file for attachment: " + e.getMessage(), e);
temporaryFile = null;
}
if (temporaryFile != null) {
try {
OutputStream outputStream = new FileOutputStream(temporaryFile);
try {
FileUtil.copy(inputStream, outputStream);
} finally {
outputStream.close();
}
} catch (IOException e) {
LOG.error("Unable to write temp file for attachment at " + temporaryFile + ": " + e.getMessage(), e);
temporaryFile = null;
}
}
myTemporaryFile = temporaryFile;
}
public Attachment(@NotNull String path, @NotNull File existingTemporaryFile, @NotNull String displayText) {
myPath = path;
myDisplayText = displayText;
myTemporaryFile = existingTemporaryFile;
myBytes = null;
}
private final @Nullable byte[] myBytes;
private final @Nullable File myTemporaryFile;
private boolean myIncluded; // opt-out for traces, opt-in otherwise
public Attachment(@NotNull String name, @NotNull Throwable throwable) {
this(name + ".trace", ExceptionUtil.getThrowableText(throwable));
myIncluded = true;
}
public Attachment(@NotNull String path, @NotNull String content) {
this(path, content, content.getBytes(CharsetToolkit.UTF8_CHARSET), null);
}
public Attachment(@NotNull String path, @NotNull byte[] bytes, @NotNull String displayText) {
this(path, displayText, bytes, null);
}
public Attachment(@NotNull String path, @NotNull File temporaryFile, @NotNull String displayText) {
this(path, displayText, null, temporaryFile);
}
private Attachment(String path, String displayText, @Nullable byte[] bytes, @Nullable File temporaryFile) {
assert bytes != null || temporaryFile != null;
myPath = path;
myDisplayText = displayText;
myBytes = bytes;
myTemporaryFile = temporaryFile;
}
@NotNull
public String getDisplayText() {
return myDisplayText;
@@ -118,16 +74,16 @@ public class Attachment {
return myBytes;
}
if (myTemporaryFile == null) {
return ArrayUtil.EMPTY_BYTE_ARRAY;
if (myTemporaryFile != null) {
try {
return FileUtil.loadFileBytes(myTemporaryFile);
}
catch (IOException e) {
LOG.error("Failed to read attachment content from temp. file " + myTemporaryFile, e);
}
}
try {
return FileUtil.loadFileBytes(myTemporaryFile);
} catch (IOException e) {
LOG.error("Unable to read attachment content from temporary file " + myTemporaryFile + ": " + e.getMessage(), e);
return ArrayUtil.EMPTY_BYTE_ARRAY;
}
return ArrayUtil.EMPTY_BYTE_ARRAY;
}
@NotNull
@@ -136,16 +92,16 @@ public class Attachment {
return new ByteArrayInputStream(myBytes);
}
if (myTemporaryFile == null) {
return new ByteArrayInputStream(ArrayUtil.EMPTY_BYTE_ARRAY);
if (myTemporaryFile != null) {
try {
return new FileInputStream(myTemporaryFile);
}
catch (FileNotFoundException e) {
LOG.error("Failed to read attachment content from temp. file " + myTemporaryFile, e);
}
}
try {
return new FileInputStream(myTemporaryFile);
} catch (FileNotFoundException e) {
LOG.warn("Unable to read attachment content from temporary file " + myTemporaryFile + ": " + e.getMessage(), e);
return new ByteArrayInputStream(ArrayUtil.EMPTY_BYTE_ARRAY);
}
return new ByteArrayInputStream(ArrayUtil.EMPTY_BYTE_ARRAY);
}
public boolean isIncluded() {
@@ -155,4 +111,4 @@ public class Attachment {
public void setIncluded(boolean included) {
myIncluded = included;
}
}
}
@@ -1,42 +1,47 @@
/*
* Copyright 2000-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.openapi.diagnostic;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* @author peter
*/
@SuppressWarnings("ExceptionClassNameDoesntEndWithException")
public class RuntimeExceptionWithAttachments extends RuntimeException implements ExceptionWithAttachments {
private final String myUserMessage;
private final Attachment[] myAttachments;
public RuntimeExceptionWithAttachments(String message, Attachment... attachments) {
super(message);
myUserMessage = null;
myAttachments = attachments;
}
public RuntimeExceptionWithAttachments(Throwable cause, Attachment... attachments) {
super(cause);
myUserMessage = null;
myAttachments = attachments;
}
/**
* Corresponds to {@link Logger#error(String, Throwable, Attachment...)}
* ({@code LOG.error(userMessage, new RuntimeException(details), attachments)}).
*/
public RuntimeExceptionWithAttachments(String userMessage, String details, Attachment... attachments) {
super(details);
myUserMessage = userMessage;
myAttachments = attachments;
}
@Nullable
public String getUserMessage() {
return myUserMessage;
}
@NotNull
@Override
public Attachment[] getAttachments() {
return myAttachments;
}
}
}