diff --git a/misc/custom_logging/README.md b/misc/custom_logging/README.md new file mode 100644 index 00000000..e9d291a9 --- /dev/null +++ b/misc/custom_logging/README.md @@ -0,0 +1,17 @@ +# Custom Logging + +This demo showcases a custom logger implementation, which runs in parallel +with the built-in logging facilities (including file logging). The custom logger +displays all messages printed by the engine in an in-game console. + +See [Logging](https://docs.godotengine.org/en/latest/tutorials/scripting/logging.html) +in the documentation for more information about configuring the engine's logging +and writing custom loggers. + +Language: GDScript + +Renderer: Compatibility + +## Screenshots + +![Screenshot](screenshots/custom_logging.webp) diff --git a/misc/custom_logging/custom_logger_ui.gd b/misc/custom_logging/custom_logger_ui.gd new file mode 100644 index 00000000..178511cf --- /dev/null +++ b/misc/custom_logging/custom_logger_ui.gd @@ -0,0 +1,72 @@ +extends RichTextLabel + +var logger := CustomLogger.new() + + +# Custom loggers must be thread-safe, as they may be called from non-main threads. +# We use `call_deferred()` to call methods on nodes to ensure they are modified +# from the main thread, as thread guards could prevent the methods from being +# called successfully otherwise. +class CustomLogger extends Logger: + func _log_message(message: String, _error: bool) -> void: + CustomLoggerUI.get_node("Panel/RichTextLabel").call_deferred(&"append_text", message) + + + func _log_error( + function: String, + file: String, + line: int, + code: String, + rationale: String, + _editor_notify: bool, + error_type: int, + script_backtraces: Array[ScriptBacktrace] + ) -> void: + var prefix := "" + # The column at which to print the trace. Should match the length of the + # unformatted text above it. + var trace_indent := 0 + + match error_type: + ERROR_TYPE_ERROR: + prefix = "[color=#f54][b]ERROR:[/b]" + trace_indent = 6 + ERROR_TYPE_WARNING: + prefix = "[color=#fd4][b]WARNING:[/b]" + trace_indent = 8 + ERROR_TYPE_SCRIPT: + prefix = "[color=#f4f][b]SCRIPT ERROR:[/b]" + trace_indent = 13 + ERROR_TYPE_SHADER: + prefix = "[color=#4bf][b]SHADER ERROR:[/b]" + trace_indent = 13 + + var trace := "%*s %s (%s:%s)" % [trace_indent, "at:", function, file, line] + var script_backtraces_text := "" + for backtrace in script_backtraces: + script_backtraces_text += backtrace.format(trace_indent - 3) + "\n" + + CustomLoggerUI.get_node("Panel/RichTextLabel").call_deferred( + &"append_text", + "%s %s %s[/color]\n[color=#999]%s[/color]\n[color=#999]%s[/color]" % [ + prefix, + code, + rationale, + trace, + script_backtraces_text, + ] + ) + + +# Use `_init()` to register the logger as early as possible, which ensures that messages +# printed early are taken into account. However, even when using `_init()`, the engine's own +# initialization messages are not accessible. +func _init() -> void: + OS.add_logger(logger) + + +# Removing the logger happens automatically when the project exits by default. +# In case you need to remove a custom logger earlier, you can use `OS.remove_logger()`. +# Doing so can also avoid object leak warnings that may be printed on exit. +func _exit_tree() -> void: + OS.remove_logger(logger) diff --git a/misc/custom_logging/custom_logger_ui.gd.uid b/misc/custom_logging/custom_logger_ui.gd.uid new file mode 100644 index 00000000..b46be4de --- /dev/null +++ b/misc/custom_logging/custom_logger_ui.gd.uid @@ -0,0 +1 @@ +uid://cgdbfnmbujg61 diff --git a/misc/custom_logging/custom_logger_ui.tscn b/misc/custom_logging/custom_logger_ui.tscn new file mode 100644 index 00000000..9e1d94d0 --- /dev/null +++ b/misc/custom_logging/custom_logger_ui.tscn @@ -0,0 +1,38 @@ +[gd_scene load_steps=5 format=3 uid="uid://cpdcq55mdqx5o"] + +[ext_resource type="FontFile" uid="uid://jrduhl6723o1" path="res://jetbrains_mono_regular.woff2" id="1_c73ru"] +[ext_resource type="FontFile" uid="uid://g800tr1mba1m" path="res://jetbrains_mono_bold.woff2" id="2_nsaj1"] +[ext_resource type="Script" uid="uid://cgdbfnmbujg61" path="res://custom_logger_ui.gd" id="3_5eal2"] + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_c73ru"] +content_margin_left = 20.0 +content_margin_top = 0.0 +content_margin_right = 0.0 +content_margin_bottom = 10.0 +bg_color = Color(0.1, 0.1, 0.1, 0.6) +corner_radius_top_left = 3 +corner_radius_top_right = 3 +corner_radius_bottom_right = 3 +corner_radius_bottom_left = 3 +corner_detail = 5 + +[node name="CanvasLayer" type="CanvasLayer"] +layer = 1024 + +[node name="Panel" type="PanelContainer" parent="."] +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +offset_bottom = -194.0 +grow_horizontal = 2 +grow_vertical = 2 +theme_override_styles/panel = SubResource("StyleBoxFlat_c73ru") + +[node name="RichTextLabel" type="RichTextLabel" parent="Panel"] +layout_mode = 2 +theme_override_fonts/normal_font = ExtResource("1_c73ru") +theme_override_fonts/bold_font = ExtResource("2_nsaj1") +theme_override_fonts/mono_font = ExtResource("1_c73ru") +bbcode_enabled = true +scroll_following = true +script = ExtResource("3_5eal2") diff --git a/misc/custom_logging/jetbrains_mono_bold.woff2 b/misc/custom_logging/jetbrains_mono_bold.woff2 new file mode 100644 index 00000000..4917f434 Binary files /dev/null and b/misc/custom_logging/jetbrains_mono_bold.woff2 differ diff --git a/misc/custom_logging/jetbrains_mono_bold.woff2.import b/misc/custom_logging/jetbrains_mono_bold.woff2.import new file mode 100644 index 00000000..89edd804 --- /dev/null +++ b/misc/custom_logging/jetbrains_mono_bold.woff2.import @@ -0,0 +1,36 @@ +[remap] + +importer="font_data_dynamic" +type="FontFile" +uid="uid://g800tr1mba1m" +path="res://.godot/imported/jetbrains_mono_bold.woff2-81dbd630c21ec1c82f93481b15f2ad52.fontdata" + +[deps] + +source_file="res://jetbrains_mono_bold.woff2" +dest_files=["res://.godot/imported/jetbrains_mono_bold.woff2-81dbd630c21ec1c82f93481b15f2ad52.fontdata"] + +[params] + +Rendering=null +antialiasing=1 +generate_mipmaps=false +disable_embedded_bitmaps=true +multichannel_signed_distance_field=false +msdf_pixel_range=8 +msdf_size=48 +allow_system_fallback=true +force_autohinter=false +modulate_color_glyphs=false +hinting=1 +subpixel_positioning=4 +keep_rounding_remainders=true +oversampling=0.0 +Fallbacks=null +fallbacks=[] +Compress=null +compress=true +preload=[] +language_support={} +script_support={} +opentype_features={} diff --git a/misc/custom_logging/jetbrains_mono_regular.woff2 b/misc/custom_logging/jetbrains_mono_regular.woff2 new file mode 100644 index 00000000..40da4276 Binary files /dev/null and b/misc/custom_logging/jetbrains_mono_regular.woff2 differ diff --git a/misc/custom_logging/jetbrains_mono_regular.woff2.import b/misc/custom_logging/jetbrains_mono_regular.woff2.import new file mode 100644 index 00000000..822be20a --- /dev/null +++ b/misc/custom_logging/jetbrains_mono_regular.woff2.import @@ -0,0 +1,36 @@ +[remap] + +importer="font_data_dynamic" +type="FontFile" +uid="uid://jrduhl6723o1" +path="res://.godot/imported/jetbrains_mono_regular.woff2-a5a5f65e14a39c1ed0a365506c5126e5.fontdata" + +[deps] + +source_file="res://jetbrains_mono_regular.woff2" +dest_files=["res://.godot/imported/jetbrains_mono_regular.woff2-a5a5f65e14a39c1ed0a365506c5126e5.fontdata"] + +[params] + +Rendering=null +antialiasing=1 +generate_mipmaps=false +disable_embedded_bitmaps=true +multichannel_signed_distance_field=false +msdf_pixel_range=8 +msdf_size=48 +allow_system_fallback=true +force_autohinter=false +modulate_color_glyphs=false +hinting=1 +subpixel_positioning=4 +keep_rounding_remainders=true +oversampling=0.0 +Fallbacks=null +fallbacks=[] +Compress=null +compress=true +preload=[] +language_support={} +script_support={} +opentype_features={} diff --git a/misc/custom_logging/main.gd b/misc/custom_logging/main.gd new file mode 100644 index 00000000..4adafea3 --- /dev/null +++ b/misc/custom_logging/main.gd @@ -0,0 +1,58 @@ +extends Control + +var message_counter := 0 +var message_raw_counter := 0 +var message_stderr_counter := 0 +var warning_counter := 0 +var error_counter := 0 + + +func _ready() -> void: + print("Normal message 1.") + push_error("Error 1.") + push_warning("Warning 1.") + push_error("Error 2.") + push_warning("Warning 2.") + print("Normal message 2.") + printerr("Normal message 1 (stderr).") + printerr("Normal message 2 (stderr).") + printraw("Normal message 1 (raw). ") + printraw("Normal message 2 (raw).\n--------\n") + + if bool(ProjectSettings.get_setting_with_override("application/run/flush_stdout_on_print")): + $FlushStdoutOnPrint.text = "Flush stdout on print: Yes (?)" + else: + $FlushStdoutOnPrint.text = "Flush stdout on print: No (?)" + + +func _on_print_message_pressed() -> void: + message_counter += 1 + print("Printing message #%d." % message_counter) + + +func _on_print_message_raw_pressed() -> void: + message_raw_counter += 1 + printraw("Printing message #%d (raw). " % message_raw_counter) + + +func _on_print_message_stderr_pressed() -> void: + message_stderr_counter += 1 + printerr("Printing message #%d (stderr)." % message_stderr_counter) + + +func _on_print_warning_pressed() -> void: + warning_counter += 1 + push_warning("Printing warning #%d." % warning_counter) + + +func _on_print_error_pressed() -> void: + error_counter += 1 + push_error("Printing error #%d." % error_counter) + + +func _on_open_logs_folder_pressed() -> void: + OS.shell_open(ProjectSettings.globalize_path(String(ProjectSettings.get_setting_with_override("debug/file_logging/log_path")).get_base_dir())) + + +func _on_crash_engine_pressed() -> void: + OS.crash("Crashing the engine on user request (the Crash Engine button was pressed). Do not report this as a bug.") diff --git a/misc/custom_logging/main.gd.uid b/misc/custom_logging/main.gd.uid new file mode 100644 index 00000000..0304c6c4 --- /dev/null +++ b/misc/custom_logging/main.gd.uid @@ -0,0 +1 @@ +uid://715k1racyrfh diff --git a/misc/custom_logging/main.tscn b/misc/custom_logging/main.tscn new file mode 100644 index 00000000..fc06437c --- /dev/null +++ b/misc/custom_logging/main.tscn @@ -0,0 +1,120 @@ +[gd_scene load_steps=4 format=3 uid="uid://bukh5v13yl2og"] + +[ext_resource type="Script" uid="uid://715k1racyrfh" path="res://main.gd" id="1_ig7tw"] + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_ig7tw"] +content_margin_left = 8.0 +content_margin_top = 8.0 +content_margin_right = 8.0 +content_margin_bottom = 8.0 +bg_color = Color(0.12591082, 0.12591085, 0.12591076, 1) +corner_radius_top_left = 3 +corner_radius_top_right = 3 +corner_radius_bottom_right = 3 +corner_radius_bottom_left = 3 +corner_detail = 3 + +[sub_resource type="Theme" id="Theme_0xm2m"] +TooltipPanel/styles/panel = SubResource("StyleBoxFlat_ig7tw") + +[node name="Control" type="Control"] +layout_mode = 3 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +theme = SubResource("Theme_0xm2m") +script = ExtResource("1_ig7tw") + +[node name="Label" type="Label" parent="."] +layout_mode = 1 +anchors_preset = 2 +anchor_top = 1.0 +anchor_bottom = 1.0 +offset_left = 24.0 +offset_top = -103.0 +offset_right = 413.0 +offset_bottom = -80.0 +grow_vertical = 0 +theme_override_colors/font_color = Color(1, 1, 1, 0.7529412) +text = "This project has an autoload with a custom logger that displays messages above. +The custom logger replicates the output seen in the terminal, which may differ +from the one displayed in the editor Output panel. +Try running Godot from a terminal to see the difference." + +[node name="FlushStdoutOnPrint" type="Label" parent="."] +layout_mode = 1 +anchors_preset = 2 +anchor_top = 1.0 +anchor_bottom = 1.0 +offset_left = 784.0 +offset_top = -103.0 +offset_right = 981.0 +offset_bottom = -80.0 +grow_vertical = 0 +tooltip_text = "Flushing standard output on print allows prints to be visible immediately +in log files, even if the engine crashes or is killed by the user. +This has a performance impact when printing frequently. + +This can be configured in the Project Settings (Application > Run > Flush stdout on Print). +By default, this is enabled in debug builds (+ editor) and disabled in release builds." +mouse_filter = 1 +theme_override_colors/font_color = Color(1, 1, 1, 0.7529412) +text = "Flush stdout on print: Yes (?)" + +[node name="Actions" type="HBoxContainer" parent="."] +layout_mode = 1 +anchors_preset = 2 +anchor_top = 1.0 +anchor_bottom = 1.0 +offset_left = 24.0 +offset_top = -63.0 +offset_right = 1014.0 +offset_bottom = -28.0 +grow_vertical = 0 +theme_override_constants/separation = 8 + +[node name="PrintMessage" type="Button" parent="Actions"] +layout_mode = 2 +text = "Print Message" + +[node name="PrintMessageRaw" type="Button" parent="Actions"] +layout_mode = 2 +text = "Print Message (raw)" + +[node name="PrintMessageStderr" type="Button" parent="Actions"] +layout_mode = 2 +text = "Print Message (stderr)" + +[node name="PrintWarning" type="Button" parent="Actions"] +layout_mode = 2 +text = "Print Warning" + +[node name="PrintError" type="Button" parent="Actions"] +layout_mode = 2 +text = "Print Error" + +[node name="VSeparator" type="VSeparator" parent="Actions"] +layout_mode = 2 +theme_override_constants/separation = 20 + +[node name="OpenLogsFolder" type="Button" parent="Actions"] +layout_mode = 2 +text = "Open Logs Folder" + +[node name="CrashEngine" type="Button" parent="Actions"] +layout_mode = 2 +tooltip_text = "Crashing the engine produces a stack trace written in the log file, +as well as terminal output. The crash backtrace is not available from scripting +in the current session, but when the next session starts, you could read existing +log files in the logs folder and check for the presence of crash backtraces there." +text = "Crash Engine" + +[connection signal="pressed" from="Actions/PrintMessage" to="." method="_on_print_message_pressed"] +[connection signal="pressed" from="Actions/PrintMessageRaw" to="." method="_on_print_message_raw_pressed"] +[connection signal="pressed" from="Actions/PrintMessageStderr" to="." method="_on_print_message_stderr_pressed"] +[connection signal="pressed" from="Actions/PrintWarning" to="." method="_on_print_warning_pressed"] +[connection signal="pressed" from="Actions/PrintError" to="." method="_on_print_error_pressed"] +[connection signal="pressed" from="Actions/OpenLogsFolder" to="." method="_on_open_logs_folder_pressed"] +[connection signal="pressed" from="Actions/CrashEngine" to="." method="_on_crash_engine_pressed"] diff --git a/misc/custom_logging/project.godot b/misc/custom_logging/project.godot new file mode 100644 index 00000000..9119437f --- /dev/null +++ b/misc/custom_logging/project.godot @@ -0,0 +1,38 @@ +; Engine configuration file. +; It's best edited using the editor UI and not directly, +; since the parameters that go here are not all obvious. +; +; Format: +; [section] ; section goes between [] +; param=value ; assign values to parameters + +config_version=5 + +[application] + +config/name="Custom Logging" +config/description="This demo showcases a custom logger implementation, which runs in parallel +to the built-in logging facilities (including file logging). The custom logger +displays all messages printed by the engine in an in-game console." +config/tags=PackedStringArray("demo", "official") +run/main_scene="uid://bukh5v13yl2og" +config/features=PackedStringArray("4.5") +run/low_processor_mode=true + +[autoload] + +CustomLoggerUI="*res://custom_logger_ui.tscn" + +[debug] + +gdscript/warnings/untyped_declaration=1 + +[display] + +window/stretch/mode="canvas_items" +window/stretch/aspect="expand" + +[rendering] + +renderer/rendering_method="gl_compatibility" +renderer/rendering_method.mobile="gl_compatibility" diff --git a/misc/custom_logging/screenshots/.gdignore b/misc/custom_logging/screenshots/.gdignore new file mode 100644 index 00000000..e69de29b diff --git a/misc/custom_logging/screenshots/custom_logging.webp b/misc/custom_logging/screenshots/custom_logging.webp new file mode 100644 index 00000000..5de891e8 Binary files /dev/null and b/misc/custom_logging/screenshots/custom_logging.webp differ