Add a custom logging demo (#1242)

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.
This commit is contained in:
Hugo Locurcio
2025-09-26 23:11:38 +02:00
committed by GitHub
parent 84eabb3cf7
commit 9fb7d5d211
14 changed files with 417 additions and 0 deletions

View File

@@ -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)

View File

@@ -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)

View File

@@ -0,0 +1 @@
uid://cgdbfnmbujg61

View File

@@ -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")

Binary file not shown.

View File

@@ -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={}

Binary file not shown.

View File

@@ -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={}

View File

@@ -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.")

View File

@@ -0,0 +1 @@
uid://715k1racyrfh

View File

@@ -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"]

View File

@@ -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"

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB