Skip to content

Commit

Permalink
Ability to print and log script backtraces
Browse files Browse the repository at this point in the history
* Changes how stack information is stored in GDScript to a reverse linked list.
* This makes it fast enough to leave it enabled all time time on debug.
* Added a new script function script_backtrace() to get a string with the current script backtrace.
* Added the script backtrace to the logger.
* Added script backtrace to console logs (Windows, MacOS, Linux) using blue color.
* Added a custom Logger class to help log errors from script.
  • Loading branch information
reduz committed Apr 30, 2024
1 parent 4a01602 commit bb26553
Show file tree
Hide file tree
Showing 25 changed files with 343 additions and 104 deletions.
74 changes: 74 additions & 0 deletions core/core_bind.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -193,8 +193,55 @@ void ResourceSaver::_bind_methods() {
BIND_BITFIELD_FLAG(FLAG_REPLACE_SUBRESOURCE_PATHS);
}

////// LOGGER ///////

void Logger::_bind_methods() {
GDVIRTUAL_BIND(_log_error, "function", "file", "line", "code", "rationale", "editor_notify", "error_type", "script_backtrace");
GDVIRTUAL_BIND(_log_message, "message", "error");
BIND_ENUM_CONSTANT(ERROR_TYPE_ERROR);
BIND_ENUM_CONSTANT(ERROR_TYPE_WARNING);
BIND_ENUM_CONSTANT(ERROR_TYPE_SCRIPT);
BIND_ENUM_CONSTANT(ERROR_TYPE_SHADER);
}

void Logger::log_error(const char *p_function, const char *p_file, int p_line, const char *p_code, const char *p_rationale, bool p_editor_notify, ErrorType p_type, const char *p_script_backtrace) {
GDVIRTUAL_CALL(_log_error, p_function, p_file, p_line, p_code, p_rationale, p_editor_notify, p_type, p_script_backtrace);
}

void Logger::log_message(const String &p_text, bool p_error) {
GDVIRTUAL_CALL(_log_message, p_text, p_error);
}

////// OS //////

void OS::LoggerBind::logv(const char *p_format, va_list p_list, bool p_err) {
if (!should_log(p_err)) {
return;
}

const int static_buf_size = 1024;
char static_buf[static_buf_size];
char *buf = static_buf;
va_list list_copy;
va_copy(list_copy, p_list);
int len = vsnprintf(buf, static_buf_size, p_format, p_list);
if (len >= static_buf_size) {
buf = (char *)Memory::alloc_static(len + 1);
vsnprintf(buf, len + 1, p_format, list_copy);
}
va_end(list_copy);

String str;
str.parse_utf8(buf, len);
for (uint32_t i = 0; i < loggers.size(); i++) {
loggers[i]->log_message(str, p_err);
}

if (len >= static_buf_size) {
Memory::free_static(buf);
}
}

PackedStringArray OS::get_connected_midi_inputs() {
return ::OS::get_singleton()->get_connected_midi_inputs();
}
Expand Down Expand Up @@ -571,6 +618,20 @@ String OS::get_unique_id() const {
return ::OS::get_singleton()->get_unique_id();
}

void OS::add_logger(Ref<Logger> p_logger) {
if (!logger_bind) {
logger_bind = memnew(LoggerBind);
::OS::get_singleton()->add_logger(logger_bind);
}

ERR_FAIL_COND(logger_bind->loggers.find(p_logger) != -1);
logger_bind->loggers.push_back(p_logger);
}

void OS::remove_logger(Ref<Logger> p_logger) {
logger_bind->loggers.erase(p_logger);
}

OS *OS::singleton = nullptr;

void OS::_bind_methods() {
Expand Down Expand Up @@ -667,6 +728,9 @@ void OS::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_granted_permissions"), &OS::get_granted_permissions);
ClassDB::bind_method(D_METHOD("revoke_granted_permissions"), &OS::revoke_granted_permissions);

ClassDB::bind_method(D_METHOD("add_logger", "logger"), &OS::add_logger);
ClassDB::bind_method(D_METHOD("remove_logger", "logger"), &OS::remove_logger);

ADD_PROPERTY(PropertyInfo(Variant::BOOL, "low_processor_usage_mode"), "set_low_processor_usage_mode", "is_in_low_processor_usage_mode");
ADD_PROPERTY(PropertyInfo(Variant::INT, "low_processor_usage_mode_sleep_usec"), "set_low_processor_usage_mode_sleep_usec", "get_low_processor_usage_mode_sleep_usec");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "delta_smoothing"), "set_delta_smoothing", "is_delta_smoothing_enabled");
Expand All @@ -690,6 +754,16 @@ void OS::_bind_methods() {
BIND_ENUM_CONSTANT(SYSTEM_DIR_RINGTONES);
}

OS::OS() {
singleton = this;
}

OS::~OS() {
if (logger_bind) {
logger_bind->clear();
}
}

////// Geometry2D //////

Geometry2D *Geometry2D::singleton = nullptr;
Expand Down
48 changes: 47 additions & 1 deletion core/core_bind.h
Original file line number Diff line number Diff line change
Expand Up @@ -118,11 +118,52 @@ class ResourceSaver : public Object {
ResourceSaver() { singleton = this; }
};

class Logger : public RefCounted {
GDCLASS(Logger, RefCounted);

public:
enum ErrorType {
ERROR_TYPE_ERROR,
ERROR_TYPE_WARNING,
ERROR_TYPE_SCRIPT,
ERROR_TYPE_SHADER
};

protected:
GDVIRTUAL2(_log_message, String, bool);
GDVIRTUAL8(_log_error, String, String, int, String, String, bool, int, String);
static void _bind_methods();

public:
virtual void log_error(const char *p_function, const char *p_file, int p_line, const char *p_code, const char *p_rationale, bool p_editor_notify = false, ErrorType p_type = ERROR_TYPE_ERROR, const char *p_script_backtrace = nullptr);
virtual void log_message(const String &p_text, bool p_error);
};

class OS : public Object {
GDCLASS(OS, Object);

mutable HashMap<String, bool> feature_cache;

class LoggerBind : public ::Logger {
public:
LocalVector<Ref<core_bind::Logger>> loggers;
virtual void log_error(const char *p_function, const char *p_file, int p_line, const char *p_code, const char *p_rationale, bool p_editor_notify = false, ErrorType p_type = ERR_ERROR, const char *p_script_backtrace = nullptr) override {
if (!should_log(true)) {
return;
}

for (uint32_t i = 0; i < loggers.size(); i++) {
loggers[i]->log_error(p_function, p_file, p_line, p_code, p_rationale, p_editor_notify, core_bind::Logger::ErrorType(p_type), p_script_backtrace);
}
}

virtual void logv(const char *p_format, va_list p_list, bool p_err) override _PRINTF_FORMAT_ATTRIBUTE_2_0;

void clear() { loggers.clear(); }
};

LoggerBind *logger_bind = nullptr;

protected:
static void _bind_methods();
static OS *singleton;
Expand Down Expand Up @@ -246,9 +287,13 @@ class OS : public Object {
Vector<String> get_granted_permissions() const;
void revoke_granted_permissions();

void add_logger(Ref<Logger> p_logger);
void remove_logger(Ref<Logger> p_logger);

static OS *get_singleton() { return singleton; }

OS() { singleton = this; }
OS();
~OS();
};

class Geometry2D : public Object {
Expand Down Expand Up @@ -581,6 +626,7 @@ class EngineDebugger : public Object {

} // namespace core_bind

VARIANT_ENUM_CAST(core_bind::Logger::ErrorType);
VARIANT_ENUM_CAST(core_bind::ResourceLoader::ThreadLoadStatus);
VARIANT_ENUM_CAST(core_bind::ResourceLoader::CacheMode);

Expand Down
4 changes: 3 additions & 1 deletion core/error/error_macros.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
#include "error_macros.h"

#include "core/io/logger.h"
#include "core/object/script_language.h"
#include "core/os/os.h"
#include "core/string/ustring.h"

Expand Down Expand Up @@ -84,7 +85,8 @@ void _err_print_error(const char *p_function, const char *p_file, int p_line, co
// Main error printing function.
void _err_print_error(const char *p_function, const char *p_file, int p_line, const char *p_error, const char *p_message, bool p_editor_notify, ErrorHandlerType p_type) {
if (OS::get_singleton()) {
OS::get_singleton()->print_error(p_function, p_file, p_line, p_error, p_message, p_editor_notify, (Logger::ErrorType)p_type);
String script_backtrace = ScriptServer::get_current_script_backtrace();
OS::get_singleton()->print_error(p_function, p_file, p_line, p_error, p_message, p_editor_notify, (Logger::ErrorType)p_type, script_backtrace.utf8().get_data());
} else {
// Fallback if errors happen before OS init or after it's destroyed.
const char *err_details = (p_message && *p_message) ? p_message : p_error;
Expand Down
12 changes: 8 additions & 4 deletions core/io/logger.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ void Logger::set_flush_stdout_on_print(bool value) {
_flush_stdout_on_print = value;
}

void Logger::log_error(const char *p_function, const char *p_file, int p_line, const char *p_code, const char *p_rationale, bool p_editor_notify, ErrorType p_type) {
void Logger::log_error(const char *p_function, const char *p_file, int p_line, const char *p_code, const char *p_rationale, bool p_editor_notify, ErrorType p_type, const char *p_script_backtrace) {
if (!should_log(true)) {
return;
}
Expand Down Expand Up @@ -87,7 +87,11 @@ void Logger::log_error(const char *p_function, const char *p_file, int p_line, c
} else {
logf_error("USER %s: %s\n", err_type, err_details);
}
logf_error(" at: %s (%s:%i)\n", p_function, p_file, p_line);
if (p_script_backtrace && p_script_backtrace[0] != 0) {
logf_error(" at: %s (%s:%i)\n%s\n", p_function, p_file, p_line, p_script_backtrace);
} else {
logf_error(" at: %s (%s:%i)\n", p_function, p_file, p_line);
}
}

void Logger::logf(const char *p_format, ...) {
Expand Down Expand Up @@ -247,13 +251,13 @@ void CompositeLogger::logv(const char *p_format, va_list p_list, bool p_err) {
}
}

void CompositeLogger::log_error(const char *p_function, const char *p_file, int p_line, const char *p_code, const char *p_rationale, bool p_editor_notify, ErrorType p_type) {
void CompositeLogger::log_error(const char *p_function, const char *p_file, int p_line, const char *p_code, const char *p_rationale, bool p_editor_notify, ErrorType p_type, const char *p_script_backtrace) {
if (!should_log(true)) {
return;
}

for (int i = 0; i < loggers.size(); ++i) {
loggers[i]->log_error(p_function, p_file, p_line, p_code, p_rationale, p_editor_notify, p_type);
loggers[i]->log_error(p_function, p_file, p_line, p_code, p_rationale, p_editor_notify, p_type, p_script_backtrace);
}
}

Expand Down
4 changes: 2 additions & 2 deletions core/io/logger.h
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ class Logger {
static void set_flush_stdout_on_print(bool value);

virtual void logv(const char *p_format, va_list p_list, bool p_err) _PRINTF_FORMAT_ATTRIBUTE_2_0 = 0;
virtual void log_error(const char *p_function, const char *p_file, int p_line, const char *p_code, const char *p_rationale, bool p_editor_notify = false, ErrorType p_type = ERR_ERROR);
virtual void log_error(const char *p_function, const char *p_file, int p_line, const char *p_code, const char *p_rationale, bool p_editor_notify = false, ErrorType p_type = ERR_ERROR, const char *p_script_backtrace = nullptr);

void logf(const char *p_format, ...) _PRINTF_FORMAT_ATTRIBUTE_2_3;
void logf_error(const char *p_format, ...) _PRINTF_FORMAT_ATTRIBUTE_2_3;
Expand Down Expand Up @@ -99,7 +99,7 @@ class CompositeLogger : public Logger {
explicit CompositeLogger(const Vector<Logger *> &p_loggers);

virtual void logv(const char *p_format, va_list p_list, bool p_err) override _PRINTF_FORMAT_ATTRIBUTE_2_0;
virtual void log_error(const char *p_function, const char *p_file, int p_line, const char *p_code, const char *p_rationale, bool p_editor_notify, ErrorType p_type = ERR_ERROR) override;
virtual void log_error(const char *p_function, const char *p_file, int p_line, const char *p_code, const char *p_rationale, bool p_editor_notify, ErrorType p_type = ERR_ERROR, const char *p_script_backtrace = nullptr) override;

void add_logger(Logger *p_logger);

Expand Down
15 changes: 15 additions & 0 deletions core/object/script_language.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -496,6 +496,21 @@ String ScriptServer::get_global_class_cache_file_path() {
return ProjectSettings::get_singleton()->get_global_class_list_path();
}

String ScriptServer::get_current_script_backtrace() {
String trace;
for (int i = 0; i < get_language_count(); i++) {
ScriptLanguage *sl = get_language(i);
Vector<ScriptLanguage::StackInfo> stack = sl->debug_get_current_stack_info();
if (stack.size()) {
trace += "stack_language: " + sl->get_name();
for (int i = 0; i < stack.size(); i++) {

Check failure on line 506 in core/object/script_language.cpp

View workflow job for this annotation

GitHub Actions / 🐧 Linux / Editor w/ Mono (target=editor)

declaration of 'i' shadows a previous local [-Werror=shadow]

Check failure on line 506 in core/object/script_language.cpp

View workflow job for this annotation

GitHub Actions / 🐧 Linux / Editor with doubles and GCC sanitizers (target=editor, tests=yes, dev_build=yes, scu_build=yes, precision=double, use_asan=yes, use_ubsan=yes, linker=gold)

declaration of 'i' shadows a previous local [-Werror=shadow]

Check failure on line 506 in core/object/script_language.cpp

View workflow job for this annotation

GitHub Actions / 🏁 Windows / Editor (target=editor, tests=yes)

the following warning is treated as an error

Check warning on line 506 in core/object/script_language.cpp

View workflow job for this annotation

GitHub Actions / 🏁 Windows / Editor (target=editor, tests=yes)

declaration of 'i' hides previous local declaration

Check failure on line 506 in core/object/script_language.cpp

View workflow job for this annotation

GitHub Actions / 🏁 Windows / Template (target=template_release)

the following warning is treated as an error

Check warning on line 506 in core/object/script_language.cpp

View workflow job for this annotation

GitHub Actions / 🏁 Windows / Template (target=template_release)

declaration of 'i' hides previous local declaration

Check failure on line 506 in core/object/script_language.cpp

View workflow job for this annotation

GitHub Actions / 🐧 Linux / Template w/ Mono (target=template_release)

declaration of 'i' shadows a previous local [-Werror=shadow]

Check failure on line 506 in core/object/script_language.cpp

View workflow job for this annotation

GitHub Actions / 🐧 Linux / Minimal template (target=template_release, everything disabled)

declaration of 'i' shadows a previous local [-Werror=shadow]
trace += "\n" + itos(i) + ": " + stack[i].func + " (" + stack[i].file + " : " + itos(stack[i].line) + ")";
}
}
}
return trace;
}

////////////////////

ScriptCodeCompletionCache *ScriptCodeCompletionCache::singleton = nullptr;
Expand Down
2 changes: 2 additions & 0 deletions core/object/script_language.h
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,8 @@ class ScriptServer {
static void save_global_classes();
static String get_global_class_cache_file_path();

static String get_current_script_backtrace();

static void init_languages();
static void finish_languages();
static bool are_languages_initialized();
Expand Down
4 changes: 2 additions & 2 deletions core/os/os.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -85,13 +85,13 @@ String OS::get_identifier() const {
return get_name().to_lower();
}

void OS::print_error(const char *p_function, const char *p_file, int p_line, const char *p_code, const char *p_rationale, bool p_editor_notify, Logger::ErrorType p_type) {
void OS::print_error(const char *p_function, const char *p_file, int p_line, const char *p_code, const char *p_rationale, bool p_editor_notify, Logger::ErrorType p_type, const char *p_script_backtrace) {
if (!_stderr_enabled) {
return;
}

if (_logger) {
_logger->log_error(p_function, p_file, p_line, p_code, p_rationale, p_editor_notify, p_type);
_logger->log_error(p_function, p_file, p_line, p_code, p_rationale, p_editor_notify, p_type, p_script_backtrace);
}
}

Expand Down
5 changes: 3 additions & 2 deletions core/os/os.h
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,6 @@ class OS {
RenderThreadMode _render_thread_mode = RENDER_THREAD_SAFE;

// Functions used by Main to initialize/deinitialize the OS.
void add_logger(Logger *p_logger);

virtual void initialize() = 0;
virtual void initialize_joypads() = 0;
Expand Down Expand Up @@ -137,7 +136,7 @@ class OS {

virtual Vector<String> get_video_adapter_driver_info() const = 0;

void print_error(const char *p_function, const char *p_file, int p_line, const char *p_code, const char *p_rationale, bool p_editor_notify = false, Logger::ErrorType p_type = Logger::ERR_ERROR);
void print_error(const char *p_function, const char *p_file, int p_line, const char *p_code, const char *p_rationale, bool p_editor_notify = false, Logger::ErrorType p_type = Logger::ERR_ERROR, const char *p_script_backtrace = nullptr);
void print(const char *p_format, ...) _PRINTF_FORMAT_ATTRIBUTE_2_3;
void print_rich(const char *p_format, ...) _PRINTF_FORMAT_ATTRIBUTE_2_3;
void printerr(const char *p_format, ...) _PRINTF_FORMAT_ATTRIBUTE_2_3;
Expand Down Expand Up @@ -324,6 +323,8 @@ class OS {

virtual Error setup_remote_filesystem(const String &p_server_host, int p_port, const String &p_password, String &r_project_path);

void add_logger(Logger *p_logger);

enum PreferredTextureFormat {
PREFERRED_TEXTURE_FORMAT_S3TC_BPTC,
PREFERRED_TEXTURE_FORMAT_ETC2_ASTC
Expand Down
1 change: 1 addition & 0 deletions core/register_core_types.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,7 @@ void register_core_types() {
GDREGISTER_CLASS(core_bind::Thread);
GDREGISTER_CLASS(core_bind::Mutex);
GDREGISTER_CLASS(core_bind::Semaphore);
GDREGISTER_VIRTUAL_CLASS(core_bind::Logger);

GDREGISTER_CLASS(XMLParser);
GDREGISTER_CLASS(JSON);
Expand Down
7 changes: 7 additions & 0 deletions core/variant/variant_utility.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
#include "core/core_string_names.h"
#include "core/io/marshalls.h"
#include "core/object/ref_counted.h"
#include "core/object/script_language.h"
#include "core/os/os.h"
#include "core/templates/oa_hash_map.h"
#include "core/templates/rid.h"
Expand Down Expand Up @@ -1227,6 +1228,10 @@ bool VariantUtilityFunctions::is_same(const Variant &p_a, const Variant &p_b) {
return p_a.identity_compare(p_b);
}

String VariantUtilityFunctions::script_backtrace() {
return ScriptServer::get_current_script_backtrace();
}

#ifdef DEBUG_METHODS_ENABLED
#define VCALLR *ret = p_func(VariantCasterAndValidate<P>::cast(p_args, Is, r_error)...)
#define VCALL p_func(VariantCasterAndValidate<P>::cast(p_args, Is, r_error)...)
Expand Down Expand Up @@ -1826,6 +1831,8 @@ void Variant::_register_variant_utility_functions() {
FUNCBINDR(rid_from_int64, sarray("base"), Variant::UTILITY_FUNC_TYPE_GENERAL);

FUNCBINDR(is_same, sarray("a", "b"), Variant::UTILITY_FUNC_TYPE_GENERAL);

FUNCBINDR(script_backtrace, sarray(), Variant::UTILITY_FUNC_TYPE_GENERAL);
}

void Variant::_unregister_variant_utility_functions() {
Expand Down
1 change: 1 addition & 0 deletions core/variant/variant_utility.h
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@ struct VariantUtilityFunctions {
static uint64_t rid_allocate_id();
static RID rid_from_int64(uint64_t p_base);
static bool is_same(const Variant &p_a, const Variant &p_b);
static String script_backtrace();
};

#endif // VARIANT_UTILITY_H
6 changes: 6 additions & 0 deletions doc/classes/@GlobalScope.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1151,6 +1151,12 @@
A type-safe version of [method round], returning an [int].
</description>
</method>
<method name="script_backtrace">
<return type="String" />
<description>
Returns a multiple line text with the current call stack information of running scripts.
</description>
</method>
<method name="seed">
<param index="0" name="base" type="int" />
<description>
Expand Down
Loading

0 comments on commit bb26553

Please sign in to comment.