# CORE FRAMEWORK SOURCE FILE # Copyright (c) 2024 The StarOpenSource Project & Contributors # Licensed under the GNU Affero General Public License v3 # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as # published by the Free Software Foundation, either version 3 of the # License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . ## Your usual basic logger implementation, with some extra features. ## ## Allows for colored output, better newlines, multiple logger levels and a ## large variety of placeholders usable in [param config_format]. extends CoreBaseModule # Signals signal log_event # Configuration var config_level: CoreTypes.LoggerLevel var config_colored: bool var config_format: String var config_newlines_override: bool var config_newlines_sizelimit: int # Update configuration func _pull_config() -> void: config_level = core.config.logger_level config_colored = core.config.logger_colored config_format = core.config.logger_format config_newlines_override = core.config.logger_newlines_override config_newlines_sizelimit = core.config.logger_newlines_sizelimit # Creates log messages func _log(level: CoreTypes.LoggerLevel, message: String) -> void: var origin: Dictionary = get_origin() if !is_level_allowed(level): emit_signal("log_event", false, level, origin, message, "") return var format: String = config_format format = format.replace("%time_ms%", str(Time.get_ticks_msec())) format = format.replace("%time%", Time.get_time_string_from_system(true)) format = format.replace("%source%", origin["source_clean"]) format = format.replace("%source_raw%", origin["source"]) format = format.replace("%function%", origin["function"]) format = format.replace("%line%", str(origin["line"])) var format_newline: String = format.replace("%color%", "").replace("%message%", "") if !config_colored: format = format.replace("%color%", "") match(level): CoreTypes.LoggerLevel.DIAG: format = format.replace("%level%", "DIAG") format_newline = format_newline.replace("%level%", "DIAG") format = format.replace("%color%", "[color=dark_gray]") CoreTypes.LoggerLevel.VERB: format = format.replace("%level%", "VERB") format_newline = format_newline.replace("%level%", "VERB") format = format.replace("%color%", "[color=gray]") CoreTypes.LoggerLevel.INFO: format = format.replace("%level%", "INFO") format_newline = format_newline.replace("%level%", "INFO") format = format.replace("%color%", "[color=white]") CoreTypes.LoggerLevel.WARN: format = format.replace("%level%", "WARN") format_newline = format_newline.replace("%level%", "WARN") format = format.replace("%color%", "[color=yellow]") CoreTypes.LoggerLevel.ERROR: format = format.replace("%level%", "ERR!") format_newline = format_newline.replace("%level%", "ERR!") format = format.replace("%color%", "[color=red]") CoreTypes.LoggerLevel.NONE: format = format.replace("%level%", "CRSH") format_newline = format_newline.replace("%level%", "CRSH") format = format.replace("%color%", "[b][color=red]") # Replace %message% if config_newlines_override and config_newlines_sizelimit <= -1 or format_newline.length() <= config_newlines_sizelimit: message = message.replace("\n", "\n" + " ".repeat(format_newline.length())) format = format.replace("%message%", message) emit_signal("log_event", true, level, origin, message, format) if config_colored: print_rich(format) else: print(format) # Get function caller func get_origin(n: int = 1) -> Dictionary: var stack: Dictionary = get_stack()[2+n] return { "source": stack["source"], "source_clean": stack["source"].replace("res://", "").replace("user://", ""), "function": stack["function"], "line": stack["line"] } # Check if level is allowed func is_level_allowed(level: CoreTypes.LoggerLevel) -> bool: if level <= config_level: return true else: return false # Self explanitory func diag(message: String) -> void: _log(CoreTypes.LoggerLevel.DIAG, message) func verb(message: String) -> void: _log(CoreTypes.LoggerLevel.VERB, message) func info(message: String) -> void: _log(CoreTypes.LoggerLevel.INFO, message) func warn(message: String) -> void: _log(CoreTypes.LoggerLevel.WARN, message) func error(message: String) -> void: _log(CoreTypes.LoggerLevel.ERROR, message) # Built-in crash handler for CORE and applications using it func crash(_message: String, framework_crash: bool = false) -> void: # Collect information var stack: Array[Dictionary] = get_stack() var origin: Dictionary = get_origin(0) var mem_info: Dictionary = OS.get_memory_info() var crash_message: String = """Generating crash report... ###################################### ### CORE CRASH HANDLER ### ###################################### %causer% inflicted a crash! %script% (func %function%, line %line%) issues: %message% +++ CORE INFORMATION +++ VERSION -> Release %version_release% -> Type %version_type% -> Typerelease %version_typerelease% -> Full %version_full% -> SemVer %version_semantic% MODES -> Development %devmode% -> Headless %headless% +++ SYSTEM INFORMATION +++ OPERATING SYSTEM -> Operating System %os% -> Version %os_version% -> Distribution %os_distribution% LOCALE -> Locale %locale% -> Language %locale_lang% MEMORY -> Free %mem_free% -> Available %mem_avail% -> Used (by engine) %mem_used% -> Total (physical) %mem_total% -> Peak %mem_peak% PROCESSOR -> Name %processor_name% -> Count %processor_count% VIDEO -> Adapter Information %video_adapter% +++ GODOT ENGINE INFORMATION +++ BUILD -> Debug build %godot_debug% -> Sandboxed %godot_sandboxed% USERDATA -> Persistent %godot_persistance% STACKTRACE %godot_stacktrace% ###################################### ### CORE CRASH HANDLER ### ######################################""" # Replace placeholders if framework_crash: crash_message = crash_message.replace("%causer%", "The CORE Framework") else: crash_message = crash_message.replace("%causer%", "The running application") crash_message = crash_message.replace("%script%", origin["source_clean"]) crash_message = crash_message.replace("%function%", origin["function"]) crash_message = crash_message.replace("%line%", str(origin["line"])) crash_message = crash_message.replace("%message%", _message) crash_message = crash_message.replace("%version_release%", str(core.version_release)) crash_message = crash_message.replace("%version_type%", await core.get_formatted_string("%type%")) crash_message = crash_message.replace("%version_typerelease%", str(core.version_typerelease)) crash_message = crash_message.replace("%version_full%", str(core.version_release) + await core.get_formatted_string("-%type_technical%") + str(core.version_typerelease)) crash_message = crash_message.replace("%version_semantic%", await core.get_formatted_string("%release_semantic%")) crash_message = crash_message.replace("%devmode%", str(core.is_devmode())) crash_message = crash_message.replace("%headless%", str(core.config.headless)) crash_message = crash_message.replace("%os%", OS.get_name()) crash_message = crash_message.replace("%os_version%", OS.get_version()) crash_message = crash_message.replace("%os_distribution%", OS.get_distribution_name()) crash_message = crash_message.replace("%locale%", OS.get_locale()) crash_message = crash_message.replace("%locale_lang%", OS.get_locale_language()) crash_message = crash_message.replace("%mem_free%", str(core.misc.mib2gib(core.misc.byte2mib(mem_info["free"], false))) + " GiB") crash_message = crash_message.replace("%mem_avail%", str(core.misc.mib2gib(core.misc.byte2mib(mem_info["available"], false))) + " GiB") crash_message = crash_message.replace("%mem_used%", str(core.misc.byte2mib(OS.get_static_memory_usage())) + " MiB") crash_message = crash_message.replace("%mem_total%", str(core.misc.mib2gib(core.misc.byte2mib(mem_info["physical"], false))) + " GiB") crash_message = crash_message.replace("%mem_peak%", str(core.misc.byte2mib(OS.get_static_memory_peak_usage())) + " MiB") crash_message = crash_message.replace("%processor_name%", OS.get_processor_name()) crash_message = crash_message.replace("%processor_count%", str(OS.get_processor_count())) crash_message = crash_message.replace("%video_adapter%", str(OS.get_video_adapter_driver_info())) crash_message = crash_message.replace("%godot_debug%", str(OS.is_debug_build())) crash_message = crash_message.replace("%godot_sandboxed%", str(OS.is_sandboxed())) crash_message = crash_message.replace("%godot_persistance%", str(OS.is_userfs_persistent())) crash_message = crash_message.replace("%godot_stacktrace%", str(stack)) # Enable newline overrides config_newlines_override = true config_newlines_sizelimit = -1 # Print crash message _log(CoreTypes.LoggerLevel.NONE, crash_message) # Shutdown await core.misc.quit_safely(69)