mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
[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:
@@ -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);
|
||||
|
||||
+28
-25
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+21
-16
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user