# 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 signal log_event ## Keeps track of all logger instances. ## Unused instances will be cleaned periodically. ## [b]Danger: [i]Don't modify this.[/i][/b] var instances: Array[CoreLoggerInstance] = [] ## Used to determine if running in verbose/command line mode.[br] ## Makes diagnostic log messages display correctly (workaround for Godot's ANSI true color limitation). var verbose_mode: bool = false ## The minimum log level you want to be displayed. var config_level: CoreTypes.LoggerLevel ## Determines if the logger's output should be colored. var config_colored: bool ## The template for all log messages[br] ## Available placeholders are: [code]%time%[/code], [code]%time_ms%[/code], [code]%level%[/code], [code]%color%[/code], [code]%message%[/code], [code]%source%[/code], [code]%source_raw%[/code], [code]%function%[/code] and [code]%line%[/code] var config_format: String ## Determines if identation should be provided if the logger encounters a newline. var config_newlines_override: bool ## The maximum amount of characters excluding the message before newlines are no longer idented. Set to [code]-1[/code] to disable this behaviour. var config_newlines_sizelimit: int # +++ module +++ func _cleanup() -> void: for instance in instances: if !is_instance_valid(instance): loggeri.diag("Removing instance '" + instance.name + "'") instance.queue_free() func _schedule() -> void: var instances_remove_enty: Array[CoreLoggerInstance] = [] for instance in instances: if !is_instance_valid(instance): continue if !is_instance_valid(instance.parent): loggeri.diag("Removing instance '" + instance.name + "'") instance.queue_free() instances_remove_enty.append(instance) for instance in instances_remove_enty: instances.remove_at(instances.find(instance)) 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 # +++ logging +++ ## The main logging function that does the heavy lifting.[br] ## [b]Danger: [i]Don't call this.[/i][/b] func _log(level: CoreTypes.LoggerLevel, origin: String, message: String) -> void: 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("%origin%", origin) 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") if verbose_mode: format = format.replace("%color%", "[color=gray]") else: 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) # +++ self explanitory +++ ## Prints a diagnostic log message. func diag(origin: String, message: String) -> void: _log(CoreTypes.LoggerLevel.DIAG, origin, message) ## Prints a verbose log message. func verb(origin: String, message: String) -> void: _log(CoreTypes.LoggerLevel.VERB, origin, message) ## Prints an informational log message. func info(origin: String, message: String) -> void: _log(CoreTypes.LoggerLevel.INFO, origin, message) ## Prints a warning log message. func warn(origin: String, message: String) -> void: _log(CoreTypes.LoggerLevel.WARN, origin, message) ## Prints an error log message. func error(origin: String, message: String) -> void: _log(CoreTypes.LoggerLevel.ERROR, origin, message) ## Handles crashes. Will terminate your game/application immediately.[br] ## [b]Note: [i]Using the [code]await[/code] keyword is required for this function.[/i][/b][br] ## [b]Danger: [i]Don't set [code]framework_crash[/code] to [code]true[/code], thanks![/i][/b] func crash(origin: String, message: String, framework_crash: bool = false) -> void: # Collect information var stack: Array[Dictionary] = get_stack() var mem_info: Dictionary = OS.get_memory_info() var crash_message: String = """Generating crash report... ###################################### ### CORE CRASH HANDLER ### ###################################### %causer% inflicted a crash! %origin% says: %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 +++ ENGINE -> Version %godot_version% 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("%origin%", origin) crash_message = crash_message.replace("%message%", message) crash_message = crash_message.replace("%version_release%", str(core.version_version)) crash_message = crash_message.replace("%version_type%", await core.get_formatted_string("%version_type%")) crash_message = crash_message.replace("%version_typerelease%", str(core.version_typerelease)) crash_message = crash_message.replace("%version_full%", str(core.version_version) + await core.get_formatted_string("-%version_type_technical%") + str(core.version_typerelease)) crash_message = crash_message.replace("%version_semantic%", await core.get_formatted_string("%version_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_version%", str(Engine.get_version_info()["string"])) 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, origin, crash_message) # Shutdown await core.quit_safely(69) # +++ etc +++ ## Checks if the specified log level is allowed by the current configuration. func is_level_allowed(level: CoreTypes.LoggerLevel) -> bool: if level <= config_level: return true else: return false ## Returns a [class CoreLoggerInstance], which is a fancy word meaning you don't need to pass [code]origin[/code] every time you want to log something. func get_instance(origin: String, parent: Object) -> CoreLoggerInstance: var instance: CoreLoggerInstance = CoreLoggerInstance.new(self, origin, parent) instance.name = "CoreLoggerInstance -> " + str(parent) + " (" + origin + ")" instances.append(instance) return instance