mirror of
https://github.com/xpipe-io/xpipe.git
synced 2024-11-25 09:00:26 +00:00
Error handler improvements
This commit is contained in:
parent
8abc82971a
commit
c3793ed0c0
14 changed files with 127 additions and 63 deletions
|
@ -6,6 +6,7 @@ import io.xpipe.app.fxcomps.util.PlatformThread;
|
|||
import io.xpipe.app.issue.ErrorEvent;
|
||||
import io.xpipe.app.issue.TrackEvent;
|
||||
import io.xpipe.app.update.XPipeDistributionType;
|
||||
import io.xpipe.app.util.PlatformState;
|
||||
import io.xpipe.core.process.OsType;
|
||||
import javafx.application.Application;
|
||||
import javafx.application.Platform;
|
||||
|
@ -23,10 +24,6 @@ public class App extends Application {
|
|||
private Stage stage;
|
||||
private Image icon;
|
||||
|
||||
public static boolean isPlatformRunning() {
|
||||
return APP != null;
|
||||
}
|
||||
|
||||
public static App getApp() {
|
||||
return APP;
|
||||
}
|
||||
|
@ -35,6 +32,7 @@ public class App extends Application {
|
|||
public void start(Stage primaryStage) {
|
||||
TrackEvent.info("Application launched");
|
||||
APP = this;
|
||||
PlatformState.setCurrent(PlatformState.RUNNING);
|
||||
stage = primaryStage;
|
||||
icon = AppImages.image("logo.png");
|
||||
|
||||
|
|
|
@ -3,10 +3,7 @@ package io.xpipe.app.core.mode;
|
|||
import io.xpipe.app.comp.storage.collection.SourceCollectionViewState;
|
||||
import io.xpipe.app.comp.storage.store.StoreViewState;
|
||||
import io.xpipe.app.core.*;
|
||||
import io.xpipe.app.issue.ErrorAction;
|
||||
import io.xpipe.app.issue.ErrorHandler;
|
||||
import io.xpipe.app.issue.LogErrorHandler;
|
||||
import io.xpipe.app.issue.TrackEvent;
|
||||
import io.xpipe.app.issue.*;
|
||||
import io.xpipe.app.prefs.AppPrefs;
|
||||
import io.xpipe.app.storage.DataStorage;
|
||||
import io.xpipe.app.util.FileBridge;
|
||||
|
@ -59,9 +56,9 @@ public class BaseMode extends OperationMode {
|
|||
@Override
|
||||
public ErrorHandler getErrorHandler() {
|
||||
var log = new LogErrorHandler();
|
||||
return event -> {
|
||||
return new SyncErrorHandler(event -> {
|
||||
log.handle(event);
|
||||
ErrorAction.ignore().handle(event);
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import io.xpipe.app.core.App;
|
|||
import io.xpipe.app.core.AppGreetings;
|
||||
import io.xpipe.app.issue.*;
|
||||
import io.xpipe.app.update.UpdateChangelogAlert;
|
||||
import io.xpipe.app.util.PlatformState;
|
||||
import javafx.application.Platform;
|
||||
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
|
@ -17,7 +18,7 @@ public class GuiMode extends PlatformMode {
|
|||
|
||||
@Override
|
||||
public void onSwitchTo() {
|
||||
if (!App.isPlatformRunning()) {
|
||||
if (PlatformState.getCurrent() == PlatformState.NOT_INITIALIZED) {
|
||||
super.platformSetup();
|
||||
}
|
||||
|
||||
|
@ -44,7 +45,7 @@ public class GuiMode extends PlatformMode {
|
|||
|
||||
@Override
|
||||
public void onSwitchFrom() {
|
||||
if (App.isPlatformRunning()) {
|
||||
if (PlatformState.getCurrent() == PlatformState.RUNNING) {
|
||||
TrackEvent.info("mode", "Closing window");
|
||||
App.getApp().close();
|
||||
waitForPlatform();
|
||||
|
@ -54,9 +55,9 @@ public class GuiMode extends PlatformMode {
|
|||
@Override
|
||||
public ErrorHandler getErrorHandler() {
|
||||
var log = new LogErrorHandler();
|
||||
return event -> {
|
||||
return new SyncErrorHandler(event -> {
|
||||
log.handle(event);
|
||||
ErrorHandlerComp.showAndWait(event);
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,12 +6,11 @@ import io.xpipe.app.core.AppLogs;
|
|||
import io.xpipe.app.core.AppProperties;
|
||||
import io.xpipe.app.issue.ErrorEvent;
|
||||
import io.xpipe.app.issue.ErrorHandler;
|
||||
import io.xpipe.app.issue.SentryErrorHandler;
|
||||
import io.xpipe.app.issue.TrackEvent;
|
||||
import io.xpipe.app.launcher.LauncherCommand;
|
||||
import io.xpipe.app.util.ThreadHelper;
|
||||
import io.xpipe.core.util.XPipeDaemonMode;
|
||||
import io.xpipe.app.util.XPipeSession;
|
||||
import io.xpipe.core.util.XPipeDaemonMode;
|
||||
import org.apache.commons.lang3.function.FailableRunnable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
@ -77,6 +76,11 @@ public abstract class OperationMode {
|
|||
OperationMode.shutdown(true, false);
|
||||
}));
|
||||
|
||||
// Handle uncaught exceptions
|
||||
Thread.setDefaultUncaughtExceptionHandler((thread, ex) -> {
|
||||
ErrorEvent.fromThrowable(ex).build().handle();
|
||||
});
|
||||
|
||||
// if (true) {
|
||||
// throw new OutOfMemoryError();
|
||||
// }
|
||||
|
@ -84,7 +88,6 @@ public abstract class OperationMode {
|
|||
TrackEvent.info("mode", "Initial setup");
|
||||
AppProperties.init();
|
||||
XPipeSession.init(AppProperties.get().getBuildUuid());
|
||||
SentryErrorHandler.init();
|
||||
AppChecks.checkDirectoryPermissions();
|
||||
AppLogs.init();
|
||||
AppProperties.logArguments(args);
|
||||
|
|
|
@ -6,6 +6,7 @@ import io.xpipe.app.core.*;
|
|||
import io.xpipe.app.issue.TrackEvent;
|
||||
import io.xpipe.app.prefs.AppPrefs;
|
||||
import io.xpipe.app.update.UpdateAvailableAlert;
|
||||
import io.xpipe.app.util.PlatformState;
|
||||
import io.xpipe.app.util.ThreadHelper;
|
||||
import javafx.application.Application;
|
||||
import javafx.application.Platform;
|
||||
|
@ -89,7 +90,7 @@ public abstract class PlatformMode extends OperationMode {
|
|||
protected void waitForPlatform() {
|
||||
// The platform thread waits for the shutdown hook to finish in case SIGTERM is sent.
|
||||
// Therefore, we do not wait for the platform when being in a shutdown hook.
|
||||
if (App.isPlatformRunning() && !Platform.isFxApplicationThread() && !OperationMode.isInShutdownHook()) {
|
||||
if (PlatformState.getCurrent() == PlatformState.RUNNING && !Platform.isFxApplicationThread() && !OperationMode.isInShutdownHook()) {
|
||||
TrackEvent.info("mode", "Waiting for platform thread ...");
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
Platform.runLater(latch::countDown);
|
||||
|
@ -116,6 +117,7 @@ public abstract class PlatformMode extends OperationMode {
|
|||
TrackEvent.info("mode", "Shutting down platform components");
|
||||
onSwitchFrom();
|
||||
Platform.exit();
|
||||
PlatformState.setCurrent(PlatformState.EXITED);
|
||||
TrackEvent.info("mode", "Platform shutdown finished");
|
||||
BACKGROUND.finalTeardown();
|
||||
}
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
package io.xpipe.app.core.mode;
|
||||
|
||||
import com.dustinredmond.fxtrayicon.FXTrayIcon;
|
||||
import io.xpipe.app.core.App;
|
||||
import io.xpipe.app.core.AppTray;
|
||||
import io.xpipe.app.issue.*;
|
||||
import io.xpipe.app.util.PlatformState;
|
||||
|
||||
public class TrayMode extends PlatformMode {
|
||||
|
||||
|
@ -19,7 +19,7 @@ public class TrayMode extends PlatformMode {
|
|||
|
||||
@Override
|
||||
public void onSwitchTo() {
|
||||
if (!App.isPlatformRunning()) {
|
||||
if (PlatformState.getCurrent() == PlatformState.NOT_INITIALIZED) {
|
||||
super.platformSetup();
|
||||
}
|
||||
|
||||
|
@ -45,13 +45,13 @@ public class TrayMode extends PlatformMode {
|
|||
@Override
|
||||
public ErrorHandler getErrorHandler() {
|
||||
var log = new LogErrorHandler();
|
||||
return event -> {
|
||||
return new SyncErrorHandler(event -> {
|
||||
// Check if tray initialization is finished
|
||||
if (AppTray.get() != null) {
|
||||
AppTray.get().getErrorHandler().handle(event);
|
||||
}
|
||||
log.handle(event);
|
||||
ErrorAction.ignore().handle(event);
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -39,7 +39,7 @@ public interface ErrorAction {
|
|||
@Override
|
||||
public boolean handle(ErrorEvent event) {
|
||||
event.clearAttachments();
|
||||
SentryErrorHandler.report(event);
|
||||
SentryErrorHandler.getInstance().handle(event);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
|
|
@ -27,6 +27,12 @@ public class ErrorEvent {
|
|||
@Singular
|
||||
private List<Path> attachments;
|
||||
|
||||
private String userReport;
|
||||
|
||||
public void attachUserReport(String text) {
|
||||
userReport = text;
|
||||
}
|
||||
|
||||
public static ErrorEventBuilder fromThrowable(Throwable t) {
|
||||
return builder().throwable(t).description(ExceptionConverter.convertMessage(t));
|
||||
}
|
||||
|
|
|
@ -2,7 +2,6 @@ package io.xpipe.app.issue;
|
|||
|
||||
import io.xpipe.app.comp.base.ButtonComp;
|
||||
import io.xpipe.app.comp.base.TitledPaneComp;
|
||||
import io.xpipe.app.core.App;
|
||||
import io.xpipe.app.core.AppFont;
|
||||
import io.xpipe.app.core.AppI18n;
|
||||
import io.xpipe.app.core.AppWindowHelper;
|
||||
|
@ -10,6 +9,7 @@ import io.xpipe.app.fxcomps.Comp;
|
|||
import io.xpipe.app.fxcomps.SimpleComp;
|
||||
import io.xpipe.app.fxcomps.augment.GrowAugment;
|
||||
import io.xpipe.app.util.JfxHelper;
|
||||
import io.xpipe.app.util.PlatformState;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.property.Property;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
|
@ -44,7 +44,7 @@ public class ErrorHandlerComp extends SimpleComp {
|
|||
}
|
||||
|
||||
public static void showAndWait(ErrorEvent event) {
|
||||
if (!App.isPlatformRunning() || event.isOmitted()) {
|
||||
if (PlatformState.getCurrent() != PlatformState.RUNNING || event.isOmitted()) {
|
||||
ErrorAction.ignore().handle(event);
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -13,32 +13,40 @@ import java.nio.file.Files;
|
|||
import java.util.Date;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class SentryErrorHandler {
|
||||
public class SentryErrorHandler implements ErrorHandler {
|
||||
|
||||
public static void init() {
|
||||
AppProperties.init();
|
||||
if (AppProperties.get().getSentryUrl() != null) {
|
||||
Sentry.init(options -> {
|
||||
options.setDsn(AppProperties.get().getSentryUrl());
|
||||
options.setEnableUncaughtExceptionHandler(false);
|
||||
options.setAttachServerName(false);
|
||||
// options.setDebug(true);
|
||||
options.setRelease(AppProperties.get().getVersion());
|
||||
options.setEnableShutdownHook(false);
|
||||
options.setProguardUuid(AppProperties.get().getBuildUuid().toString());
|
||||
options.setTag("os", System.getProperty("os.name"));
|
||||
options.setTag("osVersion", System.getProperty("os.version"));
|
||||
options.setTag("arch", System.getProperty("os.arch"));
|
||||
});
|
||||
}
|
||||
private static final ErrorHandler INSTANCE = new SyncErrorHandler(new SentryErrorHandler());
|
||||
|
||||
Thread.setDefaultUncaughtExceptionHandler((thread, ex) -> {
|
||||
ErrorEvent.fromThrowable(ex).build().handle();
|
||||
});
|
||||
public static ErrorHandler getInstance() {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
public static void report(ErrorEvent e, String text) {
|
||||
var id = report(e);
|
||||
private boolean init;
|
||||
|
||||
public void handle(ErrorEvent ee) {
|
||||
// Assume that this object is wrapped by a synchronous error handler
|
||||
if (!init) {
|
||||
AppProperties.init();
|
||||
if (AppProperties.get().getSentryUrl() != null) {
|
||||
Sentry.init(options -> {
|
||||
options.setDsn(AppProperties.get().getSentryUrl());
|
||||
options.setEnableUncaughtExceptionHandler(false);
|
||||
options.setAttachServerName(false);
|
||||
// options.setDebug(true);
|
||||
options.setRelease(AppProperties.get().getVersion());
|
||||
options.setEnableShutdownHook(false);
|
||||
options.setProguardUuid(AppProperties.get().getBuildUuid().toString());
|
||||
options.setTag("os", System.getProperty("os.name"));
|
||||
options.setTag("osVersion", System.getProperty("os.version"));
|
||||
options.setTag("arch", System.getProperty("os.arch"));
|
||||
options.setDist(XPipeDistributionType.get().getId());
|
||||
});
|
||||
}
|
||||
init = true;
|
||||
}
|
||||
|
||||
var id = createReport(ee);
|
||||
var text = ee.getUserReport();
|
||||
if (text != null && text.length() > 0) {
|
||||
var fb = new UserFeedback(id);
|
||||
fb.setComments(text);
|
||||
|
@ -46,7 +54,7 @@ public class SentryErrorHandler {
|
|||
}
|
||||
}
|
||||
|
||||
public static SentryId report(ErrorEvent ee) {
|
||||
private static SentryId createReport(ErrorEvent ee) {
|
||||
/*
|
||||
TODO: Ignore breadcrumbs for now
|
||||
*/
|
||||
|
@ -86,7 +94,6 @@ public class SentryErrorHandler {
|
|||
.toList();
|
||||
atts.forEach(attachment -> s.addAttachment(attachment));
|
||||
|
||||
s.setTag("dist", XPipeDistributionType.get().getId());
|
||||
s.setTag("developerMode", AppPrefs.get() != null ? AppPrefs.get().developerMode().getValue().toString() : "false");
|
||||
s.setTag("terminal", Boolean.toString(ee.isTerminal()));
|
||||
s.setTag("omitted", Boolean.toString(ee.isOmitted()));
|
||||
|
@ -97,7 +104,6 @@ public class SentryErrorHandler {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
var user = new User();
|
||||
user.setId(AppCache.getCachedUserId().toString());
|
||||
s.setUser(user);
|
||||
|
|
38
app/src/main/java/io/xpipe/app/issue/SyncErrorHandler.java
Normal file
38
app/src/main/java/io/xpipe/app/issue/SyncErrorHandler.java
Normal file
|
@ -0,0 +1,38 @@
|
|||
package io.xpipe.app.issue;
|
||||
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
public class SyncErrorHandler implements ErrorHandler {
|
||||
|
||||
private final Queue<ErrorEvent> eventQueue = new LinkedBlockingQueue<>();
|
||||
private final ErrorHandler errorHandler;
|
||||
private final AtomicBoolean busy = new AtomicBoolean();
|
||||
|
||||
public SyncErrorHandler(ErrorHandler errorHandler) {
|
||||
this.errorHandler = errorHandler;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle(ErrorEvent event) {
|
||||
synchronized (busy) {
|
||||
if (busy.get()) {
|
||||
synchronized (eventQueue) {
|
||||
eventQueue.add(event);
|
||||
}
|
||||
return;
|
||||
}
|
||||
busy.set(true);
|
||||
}
|
||||
|
||||
errorHandler.handle(event);
|
||||
synchronized (eventQueue) {
|
||||
eventQueue.forEach(errorEvent -> {
|
||||
System.out.println("Event happened during error handling: " + errorEvent.toString());
|
||||
});
|
||||
eventQueue.clear();
|
||||
}
|
||||
busy.set(false);
|
||||
}
|
||||
}
|
|
@ -5,6 +5,7 @@ import io.xpipe.app.core.*;
|
|||
import io.xpipe.app.core.mode.OperationMode;
|
||||
import io.xpipe.app.util.Hyperlinks;
|
||||
import io.xpipe.app.update.XPipeDistributionType;
|
||||
import io.xpipe.app.util.PlatformState;
|
||||
import javafx.application.Platform;
|
||||
import javafx.scene.control.Alert;
|
||||
import javafx.scene.control.ButtonBar;
|
||||
|
@ -23,7 +24,7 @@ public class TerminalErrorHandler implements ErrorHandler {
|
|||
|
||||
if (!OperationMode.GUI.isSupported()) {
|
||||
event.clearAttachments();
|
||||
SentryErrorHandler.report(event, null);
|
||||
SentryErrorHandler.getInstance().handle(event);
|
||||
OperationMode.halt(1);
|
||||
}
|
||||
|
||||
|
@ -31,20 +32,20 @@ public class TerminalErrorHandler implements ErrorHandler {
|
|||
}
|
||||
|
||||
private void handleSentry(ErrorEvent event) {
|
||||
SentryErrorHandler.init();
|
||||
if (OperationMode.isInStartup()) {
|
||||
Sentry.setExtra("initError", "true");
|
||||
}
|
||||
}
|
||||
|
||||
private void handleGui(ErrorEvent event) {
|
||||
if (!App.isPlatformRunning()) {
|
||||
if (PlatformState.getCurrent() == PlatformState.NOT_INITIALIZED) {
|
||||
try {
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
Platform.setImplicitExit(false);
|
||||
Platform.startup(latch::countDown);
|
||||
try {
|
||||
latch.await();
|
||||
PlatformState.setCurrent(PlatformState.RUNNING);
|
||||
} catch (InterruptedException ignored) {
|
||||
}
|
||||
} catch (Throwable r) {
|
||||
|
@ -78,8 +79,8 @@ public class TerminalErrorHandler implements ErrorHandler {
|
|||
}
|
||||
|
||||
private static void handleSecondaryException(ErrorEvent event, Throwable t) {
|
||||
SentryErrorHandler.report(event, null);
|
||||
SentryErrorHandler.report(ErrorEvent.fromThrowable(t).build(), null);
|
||||
SentryErrorHandler.getInstance().handle(event);
|
||||
SentryErrorHandler.getInstance().handle(ErrorEvent.fromThrowable(t).build());
|
||||
Sentry.flush(5000);
|
||||
t.printStackTrace();
|
||||
OperationMode.halt(1);
|
||||
|
@ -106,7 +107,7 @@ public class TerminalErrorHandler implements ErrorHandler {
|
|||
}
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
SentryErrorHandler.report(ErrorEvent.fromThrowable(t).build(), null);
|
||||
SentryErrorHandler.getInstance().handle(ErrorEvent.fromThrowable(t).build());
|
||||
Sentry.flush(5000);
|
||||
t.printStackTrace();
|
||||
OperationMode.halt(1);
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
package io.xpipe.app.issue;
|
||||
|
||||
import io.sentry.Sentry;
|
||||
import io.xpipe.app.comp.base.ButtonComp;
|
||||
import io.xpipe.app.comp.base.ListSelectorComp;
|
||||
import io.xpipe.app.comp.base.TitledPaneComp;
|
||||
|
@ -112,12 +111,10 @@ public class UserReportComp extends SimpleComp {
|
|||
}
|
||||
|
||||
private void send() {
|
||||
Sentry.withScope(scope -> {
|
||||
event.clearAttachments();
|
||||
includedDiagnostics.forEach(event::addAttachment);
|
||||
SentryErrorHandler.report(event, text.get());
|
||||
});
|
||||
|
||||
event.clearAttachments();
|
||||
includedDiagnostics.forEach(event::addAttachment);
|
||||
event.attachUserReport(text.get());
|
||||
SentryErrorHandler.getInstance().handle(event);
|
||||
stage.close();
|
||||
}
|
||||
}
|
||||
|
|
15
app/src/main/java/io/xpipe/app/util/PlatformState.java
Normal file
15
app/src/main/java/io/xpipe/app/util/PlatformState.java
Normal file
|
@ -0,0 +1,15 @@
|
|||
package io.xpipe.app.util;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
public enum PlatformState {
|
||||
|
||||
NOT_INITIALIZED,
|
||||
RUNNING,
|
||||
EXITED;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
private static PlatformState current = PlatformState.NOT_INITIALIZED;
|
||||
}
|
Loading…
Reference in a new issue