Skip to content

Commit

Permalink
Show a GUI alert when crashing on desktop platforms
Browse files Browse the repository at this point in the history
This makes it possible to know that Godot has crashed without looking
at the console (which may not be visible to the user).

- Change the window title while dumping the backtrace, as this can
  take a while to finish.
- If file logging is enabled, print path to log file to standard error
  and the GUI alert. Clicking OK will open the log file in the default
  program associated with `.log` files. Notepad has native associations
  for `.log` on Windows, so this works on Windows too.

Co-authored-by: bruvzg <7645683+bruvzg@users.noreply.github.com>
  • Loading branch information
Calinou and bruvzg committed Feb 6, 2024
1 parent 9ab5ced commit 3e9cf34
Show file tree
Hide file tree
Showing 2 changed files with 64 additions and 4 deletions.
30 changes: 28 additions & 2 deletions platform/linuxbsd/crash_handler_linuxbsd.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
#include "core/string/print_string.h"
#include "core/version.h"
#include "main/main.h"
#include "servers/display_server.h"

#ifdef DEBUG_ENABLED
#define CRASH_HANDLER_ENABLED 1
Expand Down Expand Up @@ -62,19 +63,26 @@ static void handle_crash(int sig) {
String _execpath = OS::get_singleton()->get_executable_path();

String msg;
String log_path;
const ProjectSettings *proj_settings = ProjectSettings::get_singleton();
if (proj_settings) {
msg = proj_settings->get("debug/settings/crash_handler/message");
msg = proj_settings->get_setting_with_override("debug/settings/crash_handler/message");
if (proj_settings->get_setting_with_override("debug/file_logging/enable_file_logging")) {
log_path = proj_settings->globalize_path(proj_settings->get_setting_with_override("debug/file_logging/log_path"));
}
}

// Tell MainLoop about the crash. This can be handled by users too in Node.
if (OS::get_singleton()->get_main_loop()) {
OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_CRASH);
}

// Set window title while dumping the backtrace, as this can take 10+ seconds to finish.
DisplayServer::get_singleton()->window_set_title("ERROR: Program crashed, dumping backtrace to log...");

// Dump the backtrace to stderr with a message to the user
print_error("\n================================================================");
print_error(vformat("%s: Program crashed with signal %d", __FUNCTION__, sig));
print_error(vformat("%s: Program crashed with signal %d.", __FUNCTION__, sig));

// Print the engine version just before, so that people are reminded to include the version in backtrace reports.
if (String(VERSION_HASH).is_empty()) {
Expand Down Expand Up @@ -141,6 +149,24 @@ static void handle_crash(int sig) {
print_error("-- END OF BACKTRACE --");
print_error("================================================================");

// Notify the user that backtrace dumping is finished.
DisplayServer::get_singleton()->window_set_title("ERROR: Program crashed, dumped backtrace to log");

// Show alert so the user is aware of the crash, even if the engine wasn't started with visible stdout/stderr.
// This must be done after printing the backtrace to prevent the process from being blocked too early (`OS::alert()` is blocking).
if (!log_path.is_empty()) {
// `fprintf()` is used instead of `print_error()`, as this printed line must not be present
// in the log file (it references the log file itself).
fprintf(stderr, "\nFind the log file for this session at:\n%s\n\n", log_path.utf8().get_data());

if (DisplayServer::get_singleton()->get_name() != "headless") {
OS::get_singleton()->alert(vformat("%s: Program crashed with signal %d.\n\nFind the log file for this session at:\n%s\n\nClicking OK will open this log file. Please include the this log file's contents in bug reports.", __FUNCTION__, sig, log_path));
OS::get_singleton()->shell_open(log_path.utf8().get_data());
}
} else if (DisplayServer::get_singleton()->get_name() != "headless") {
OS::get_singleton()->alert(vformat("%s: Program crashed with signal %d.", __FUNCTION__, sig));
}

// Abort to pass the error to the OS
abort();
}
Expand Down
38 changes: 36 additions & 2 deletions platform/macos/crash_handler_macos.mm
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
#include "core/string/print_string.h"
#include "core/version.h"
#include "main/main.h"
#include "servers/display_server.h"

#include <string.h>
#include <unistd.h>
Expand Down Expand Up @@ -85,19 +86,26 @@ static void handle_crash(int sig) {
String _execpath = OS::get_singleton()->get_executable_path();

String msg;
String log_path;
const ProjectSettings *proj_settings = ProjectSettings::get_singleton();
if (proj_settings) {
msg = proj_settings->get("debug/settings/crash_handler/message");
msg = proj_settings->get_setting_with_override("debug/settings/crash_handler/message");
if (proj_settings->get_setting_with_override("debug/file_logging/enable_file_logging")) {
log_path = proj_settings->globalize_path(proj_settings->get_setting_with_override("debug/file_logging/log_path"));
}
}

// Tell MainLoop about the crash. This can be handled by users too in Node.
if (OS::get_singleton()->get_main_loop()) {
OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_CRASH);
}

// Set window title while dumping the backtrace, as this can take 10+ seconds to finish.
DisplayServer::get_singleton()->window_set_title("ERROR: Program crashed, dumping backtrace to log...");

// Dump the backtrace to stderr with a message to the user
print_error("\n================================================================");
print_error(vformat("%s: Program crashed with signal %d", __FUNCTION__, sig));
print_error(vformat("%s: Program crashed with signal %d.", __FUNCTION__, sig));

// Print the engine version just before, so that people are reminded to include the version in backtrace reports.
if (String(VERSION_HASH).is_empty()) {
Expand Down Expand Up @@ -171,6 +179,32 @@ static void handle_crash(int sig) {
print_error("-- END OF BACKTRACE --");
print_error("================================================================");

// Notify the user that backtrace dumping is finished.
DisplayServer::get_singleton()->window_set_title("ERROR: Program crashed, dumped backtrace to log");

// Show alert so the user is aware of the crash, even if the engine wasn't started with visible stdout/stderr.
// This must be done after printing the backtrace to prevent the process from being blocked too early (`OS::alert()` is blocking).
if (!log_path.is_empty()) {
// `fprintf()` is used instead of `print_error()`, as this printed line must not be present
// in the log file (it references the log file itself).
fprintf(stderr, "\nFind the log file for this session at:\n%s\n\n", log_path.utf8().get_data());

if (DisplayServer::get_singleton()->get_name() != "headless") {
List<String> args;
args.push_back("-e");
args.push_back(vformat("display alert \"Crash\" message \"%s: Program crashed with signal %d.\\n\\nFind the log file for this session at:\\n%s\\n\\nClicking OK will open this log file. Please include the this log file's contents in bug reports.\"", __FUNCTION__, sig, log_path.c_escape()));
OS::get_singleton()->execute("osascript", args);
args.clear();
args.push_back(log_path);
OS::get_singleton()->execute("open", args);
}
} else if (DisplayServer::get_singleton()->get_name() != "headless") {
List<String> args;
args.push_back("-e");
args.push_back(vformat("display alert \"Crash\" message \"%s: Program crashed with signal %d.\"", __FUNCTION__, sig));
OS::get_singleton()->execute("osascript", args);
}

// Abort to pass the error to the OS
abort();
}
Expand Down

0 comments on commit 3e9cf34

Please sign in to comment.