New log visualizer in Developer Settings

This commit is contained in:
Adrien Ufferte
2025-10-03 15:59:21 +02:00
parent c0ae8e74ee
commit 440849be7f
10 changed files with 219 additions and 328 deletions
+1
View File
@@ -47,6 +47,7 @@ const SUPPORTED_LOCALES: Array[String] = [
effects_volume = volume
var ind: int = AudioServer.get_bus_index("Effects")
AudioServer.set_bus_volume_db(ind, volume)
@export var log_level: Log.LogLevel = Log.LogLevel.TRACE
func init_os_language() -> void:
+95 -185
View File
@@ -3,6 +3,7 @@ extends Control
#const MAIN_MENU_PATH: String = "res://sources/menus/main/main_menu.tscn"
const LOGIN_MENU_PATH: String = "res://sources/menus/login/login.tscn"
const LOG_LEVEL_ELEMENT: PackedScene = preload("res://sources/menus/settings/log_level_element.tscn")
#const DEVICE_SELECTION_SCENE_PATH: String = "res://sources/menus/device_selection/device_selection.tscn"
#const DEVICE_TAB_SCENE: PackedScene = preload("res://sources/menus/settings/device_tab.tscn")
#
@@ -20,191 +21,100 @@ const LOGIN_MENU_PATH: String = "res://sources/menus/login/login.tscn"
#@onready var add_device_popup: CanvasLayer = %AddDevicePopup
#@onready var add_student_popup: CanvasLayer = %AddStudentPopup
#@onready var delete_student_popup: CanvasLayer = %DeleteStudentPopup
#
#
#func _ready() -> void:
#refresh_devices_tabs()
#
## Internet mandatory to add student because only the server can ensure the student code is not a duplicate
#if await ServerManager.check_internet_access():
#add_device_button.show()
#add_student_button.show()
#label_internet_mandatory.hide()
#else:
#add_device_button.hide()
#add_student_button.hide()
#label_internet_mandatory.show()
#
#OpeningCurtain.open()
#lesson_unlocks.teacher_settings = self
#
#account_type_option_button.select(UserDataManager.teacher_settings.account_type)
#education_method_option_button.select(UserDataManager.teacher_settings.education_method)
#
#UserDataManager.user_database_synchronizer.loading_popup = loading_popup
#UserDataManager.user_database_synchronizer.account_type_option_button = account_type_option_button
#UserDataManager.user_database_synchronizer.education_method_option_button = education_method_option_button
#
#
#func _on_account_type_option_button_item_selected(index: int) -> void:
#if TeacherSettings.AccountType.values().has(index):
#UserDataManager.teacher_settings.account_type = index as TeacherSettings.AccountType
#UserDataManager.teacher_settings.last_modified = Time.get_datetime_string_from_system(true)
#UserDataManager.save_teacher_settings()
#else:
#Log.warn("SettingsTeacherSettings: Cannot assign index %d to AccountType" % index)
#
#
#func _on_education_method_option_button_item_selected(index: int) -> void:
#if TeacherSettings.EducationMethod.values().has(index):
#UserDataManager.teacher_settings.education_method = index as TeacherSettings.EducationMethod
#UserDataManager.teacher_settings.last_modified = Time.get_datetime_string_from_system(true)
#UserDataManager.save_teacher_settings()
#else:
#Log.warn("SettingsTeacherSettings: Cannot assign index %d to EducationMethod" % index)
#
#
#func refresh_devices_tabs() -> void:
#for child: Node in devices_tab_container.get_children(false):
#child.queue_free()
#
#if not UserDataManager.teacher_settings:
#Log.error("SettingsTeacherSettings: Teacher settings not found")
#return
#
#for device: int in UserDataManager.teacher_settings.students.keys():
#var device_tab: DeviceTab
#device_tab = DEVICE_TAB_SCENE.instantiate()
#devices_tab_container.add_child(device_tab)
#await get_tree().process_frame # Not optional or an auto-rename bug will occur on the tabs (especiallly if there are a lot of them)
#device_tab.device_id = device
#device_tab.students = UserDataManager.teacher_settings.students[device]
#device_tab.name = tr("DEVICE_NUMBER").format({"number": device})
#device_tab.student_pressed.connect(_on_student_pressed)
#device_tab.refresh()
#
#last_device_id = device
#
#
var filters: Dictionary[int, bool] = {}
var line_steps: PackedInt32Array = [10, 50, 100, 200, 500, 1000, -1] # -1 = all
var loglevel_regex: RegEx
@onready var slider: HSlider = $VBoxContainer/Controls/LineCountSlider
@onready var slider_label: Label = $VBoxContainer/Controls/SliderLabel
@onready var log_level_dropdown: OptionButton = $VBoxContainer/LogControls/LogLevelDropdown
@onready var filters_container: HBoxContainer = $VBoxContainer/Filters
@onready var log_text: TextEdit = $VBoxContainer/ColorRect/LogText
func _ready() -> void:
slider.min_value = 0
slider.max_value = line_steps.size() - 1
slider.step = 1
slider.value = 5
var levels: Array[String] = []
log_level_dropdown.clear()
for level_name: String in Log.LogLevel.keys():
var value: int = Log.LogLevel[level_name]
log_level_dropdown.add_item(level_name, value)
if level_name != "NONE":
levels.append(level_name)
var element: LogLevelElement = LOG_LEVEL_ELEMENT.instantiate()
filters_container.add_child(element)
element.log_level.text = level_name
element.check_box.button_pressed = Log.current_level <= value
element.check_box.toggled.connect(_on_filters_changed.bind(value))
_on_filters_changed(Log.current_level <= value, value)
var pattern: String = "\\[(" + "|".join(levels) + ")\\]"
loglevel_regex = RegEx.new()
loglevel_regex.compile(pattern)
log_level_dropdown.item_selected.connect(_on_level_changed)
slider.value_changed.connect(_on_slider_changed)
_update_log_text()
func _update_log_text() -> void:
var idx: int = int(slider.value)
var lines_to_show: int = line_steps[idx]
var total: int = Log.all_logs.size()
var subset: PackedStringArray
if lines_to_show == -1:
slider_label.text = "Show: all (%d lines)" % total
subset = Log.all_logs
else:
slider_label.text = "Show: %d last lines" % lines_to_show
var start: int = maxi(total - lines_to_show, 0)
subset = Log.all_logs.slice(start, total)
var filtered: Array[String] = []
for line: String in subset:
var level: int = _extract_log_level(line)
if level == -1:
filtered.append(line)
elif filters.has(level) and filters[level]:
filtered.append(line)
log_text.text = "\n".join(filtered)
log_text.scroll_vertical = log_text.get_line_count() # Scroll down
func _extract_log_level(line: String) -> int:
var result: RegExMatch = loglevel_regex.search(line)
if result:
var log_level_name: String = result.get_string(1) # "DEBUG", "INFO"…
if Log.LogLevel.has(log_level_name):
return Log.LogLevel[log_level_name]
return -1
func _on_level_changed(_index: int) -> void:
var selected_level: int = log_level_dropdown.get_selected_id()
Log.current_level = selected_level as Log.LogLevel
Log.info("Log level changed to %s" % Log.LogLevel.keys()[selected_level])
func _on_filters_changed(checked: bool, index: int) -> void:
filters[index] = checked
Log.trace("DeveloperSettings: Filters changed: %s" % str(filters))
if loglevel_regex != null:
_update_log_text()
func _on_back_button_pressed() -> void:
await OpeningCurtain.close()
get_tree().change_scene_to_file(LOGIN_MENU_PATH)
#
#
#func _on_delete_button_pressed() -> void:
#delete_popup.show()
#
#
#func _on_delete_popup_accepted() -> void:
#var res: Dictionary = await ServerManager.delete_account()
#if res.code == 200:
#UserDataManager.delete_teacher_data()
#UserDataManager.logout()
#get_tree().change_scene_to_file(MAIN_MENU_PATH)
#
#
#func _on_logout_button_pressed() -> void:
#await OpeningCurtain.close()
#UserDataManager.logout()
#get_tree().change_scene_to_file(MAIN_MENU_PATH)
#
#
#func _on_devices_tab_container_tab_changed(tab: int) -> void:
#lesson_unlocks.device = tab + 1
#
#
#func _on_student_pressed(code: int) -> void:
#lesson_unlocks.student = code
#lesson_unlocks.show()
#
#
#func _on_add_student_button_pressed() -> void:
#add_student_popup.show()
#
#
#func _on_add_student_popup_accepted() -> void:
#var current_tab: DeviceTab = devices_tab_container.get_current_tab_control() as DeviceTab
#if not current_tab:
#Log.error("SettingsTeacherSettings: DeviceTab not found")
#return
#var res: Dictionary = await ServerManager.add_student({"device": current_tab.device_id})
#if res.code == 200:
#UserDataManager.update_configuration(res.body as Dictionary)
#current_tab.students = UserDataManager.teacher_settings.students[current_tab.device_id]
#current_tab.refresh()
#else:
#Log.error("SettingsTeacherSettings: Request to add student failed. Error code " + str(res.code))
#
#
#func _on_add_device_button_pressed() -> void:
#add_device_popup.show()
#
#
#func _on_add_device_popup_accepted() -> void:
#var res: Dictionary = await ServerManager.add_student({"device": last_device_id + 1})
#if res.code == 200:
#UserDataManager.update_configuration(res.body as Dictionary)
#refresh_devices_tabs()
#await get_tree().create_timer(1).timeout
#var count: int = devices_tab_container.get_tab_count()
#devices_tab_container.current_tab = count -1
#
#
#func _on_lesson_unlocks_student_deleted(_code: int) -> void:
#delete_student_popup.show()
#
#
#func _on_delete_student_popup_accepted() -> void:
#var current_tab: DeviceTab = devices_tab_container.get_current_tab_control() as DeviceTab
#if not current_tab:
#return
#var res: Dictionary = await ServerManager.remove_student(int(lesson_unlocks.student))
#if res.code == 200:
#lesson_unlocks.hide()
#UserDataManager.update_configuration(res.body as Dictionary)
#await get_tree().create_timer(1).timeout
#if UserDataManager.teacher_settings.students.has(current_tab.device_id):
#current_tab.students = UserDataManager.teacher_settings.students[current_tab.device_id]
#current_tab.refresh()
#else:
#refresh_devices_tabs()
#
#
#func update_student_name(student_code: int, student_name: String) -> void:
#for device: DeviceTab in devices_tab_container.get_children(false):
#for student_panel: StudentPanel in device.students_container.get_children(false):
#if student_panel.student_data.code == student_code:
#student_panel.name_label.text = student_name
#return
#Log.warn("SettingsTeacherSettings: update_student_name: student not found with code " + str(student_code))
#
##region Synchronization
#
#func _on_dashboard_button_pressed() -> void:
#var res: Dictionary = await ServerManager.get_dashboard()
#if res.code == 200:
#if res.has("body") and res.body is Dictionary:
#if (res.body as Dictionary).has("url"):
#OS.shell_open(res.body.url as String)
#return
#Log.error("SettingsTeacherSettings: Request to get Dashboard link has an invalid content")
#else:
#Log.error("SettingsTeacherSettings: Request to get Dashboard link failed. Error code " + str(res.code))
#
#
#func _on_logs_viewer_button_pressed() -> void:
#Log.open_log_popup()
#
#
#func _on_synchronize_button_pressed() -> void:
#UserDataManager.user_database_synchronizer.synchronize()
#
#
#func _on_loading_popup_ok() -> void:
#loading_popup.hide()
#
#
#func _on_loading_popup_cancel() -> void:
#Log.warn("SettingsTeacherSettings: User wanted to cancel synchronization but it is impossible to interrupt.")
#
##endregion
func _on_slider_changed(_value: float) -> void:
_update_log_text()
+53 -15
View File
@@ -1,7 +1,6 @@
[gd_scene load_steps=6 format=3 uid="uid://b8hlfrsgrkmy"]
[gd_scene load_steps=5 format=3 uid="uid://b8hlfrsgrkmy"]
[ext_resource type="Script" uid="uid://dutga8wms2uy4" path="res://sources/menus/settings/developer_settings.gd" id="1_qqqt3"]
[ext_resource type="PackedScene" uid="uid://wdjp1sv55q4f" path="res://sources/menus/components/night_sky/night_sky.tscn" id="2_h4f88"]
[ext_resource type="Texture2D" uid="uid://wtx3wqs0l4si" path="res://assets/minigames/minigame_ui/graphic/button_back_up.png" id="3_vjlw0"]
[ext_resource type="Texture2D" uid="uid://cbhip35yyqneg" path="res://assets/minigames/minigame_ui/graphic/button_back_down.png" id="4_s0ywi"]
[ext_resource type="Texture2D" uid="uid://tcapan0mj64o" path="res://assets/minigames/minigame_ui/graphic/button_back_disabled.png" id="5_4q33k"]
@@ -15,8 +14,14 @@ grow_horizontal = 2
grow_vertical = 2
script = ExtResource("1_qqqt3")
[node name="Background" parent="." instance=ExtResource("2_h4f88")]
[node name="ColorRect" type="ColorRect" parent="."]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
color = Color(0, 0, 0, 1)
[node name="InterfaceLeft" type="MarginContainer" parent="."]
custom_minimum_size = Vector2(450, 0)
@@ -42,12 +47,6 @@ texture_pressed = ExtResource("4_s0ywi")
texture_disabled = ExtResource("5_4q33k")
stretch_mode = 4
[node name="Logs" type="Button" parent="InterfaceLeft/Container"]
layout_mode = 2
theme_type_variation = &"FlatButton"
theme_override_font_sizes/font_size = 40
text = "Logs"
[node name="VBoxContainer" type="VBoxContainer" parent="."]
custom_minimum_size = Vector2(1970, 1800)
layout_mode = 1
@@ -59,19 +58,58 @@ offset_left = -632.0
grow_horizontal = 0
grow_vertical = 2
[node name="LogControls" type="HBoxContainer" parent="VBoxContainer"]
layout_mode = 2
alignment = 1
[node name="Label" type="Label" parent="VBoxContainer/LogControls"]
layout_mode = 2
text = "Change log level:"
[node name="LogLevelDropdown" type="OptionButton" parent="VBoxContainer/LogControls"]
layout_mode = 2
[node name="HSeparator" type="HSeparator" parent="VBoxContainer"]
layout_mode = 2
theme_override_constants/separation = 50
[node name="HSeparator2" type="HSeparator" parent="VBoxContainer"]
[node name="Controls" type="HBoxContainer" parent="VBoxContainer"]
layout_mode = 2
theme_override_constants/separation = 50
alignment = 1
[node name="DevicesTabContainer" type="TabContainer" parent="VBoxContainer"]
unique_name_in_owner = true
[node name="LineCountSlider" type="HSlider" parent="VBoxContainer/Controls"]
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 4
max_value = 20.0
value = 10.0
[node name="SliderLabel" type="Label" parent="VBoxContainer/Controls"]
layout_mode = 2
text = "Show: ?"
[node name="Control" type="Control" parent="VBoxContainer/Controls"]
layout_mode = 2
size_flags_horizontal = 3
[node name="Filters" type="HBoxContainer" parent="VBoxContainer"]
layout_mode = 2
alignment = 1
[node name="ColorRect" type="ColorRect" parent="VBoxContainer"]
layout_mode = 2
size_flags_vertical = 3
color = Color(0.48962224, 0.48962224, 0.48962218, 1)
[node name="LogText" type="TextEdit" parent="VBoxContainer/ColorRect"]
layout_mode = 0
offset_left = 30.0
offset_top = 14.0
offset_right = 1930.0
offset_bottom = 1614.0
size_flags_vertical = 3
placeholder_text = "Placeholder Text
Contains all logs textss"
editable = false
[connection signal="pressed" from="InterfaceLeft/Container/BackButton" to="." method="_on_back_button_pressed"]
[connection signal="pressed" from="InterfaceLeft/Container/Logs" to="." method="_on_logs_viewer_button_pressed"]
[connection signal="tab_changed" from="VBoxContainer/DevicesTabContainer" to="." method="_on_devices_tab_container_tab_changed"]
@@ -0,0 +1,5 @@
extends MarginContainer
class_name LogLevelElement
@onready var check_box: CheckBox = %CheckBox
@onready var log_level: Label = %LogLevel
@@ -0,0 +1 @@
uid://by4sblxaaubdk
@@ -0,0 +1,64 @@
[gd_scene load_steps=4 format=3 uid="uid://6wsos3jta373"]
[ext_resource type="Script" uid="uid://by4sblxaaubdk" path="res://sources/menus/settings/log_level_element.gd" id="1_opayw"]
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_emsyu"]
bg_color = Color(0.254902, 0.329412, 0.572549, 1)
border_width_left = 2
border_width_top = 2
border_width_right = 2
border_width_bottom = 2
border_color = Color(0, 0, 0, 1)
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_t1yui"]
[node name="LogLevelElement" type="MarginContainer"]
anchors_preset = 14
anchor_top = 0.5
anchor_right = 1.0
anchor_bottom = 0.5
offset_top = -900.0
offset_right = -2250.0
offset_bottom = -811.0
grow_horizontal = 2
grow_vertical = 2
size_flags_horizontal = 3
script = ExtResource("1_opayw")
[node name="Panel" type="Panel" parent="."]
layout_mode = 2
theme_override_styles/panel = SubResource("StyleBoxFlat_emsyu")
[node name="TabContainer" type="TabContainer" parent="."]
layout_mode = 2
theme_override_styles/panel = SubResource("StyleBoxEmpty_t1yui")
current_tab = 0
tabs_visible = false
[node name="NormalContainer" type="HBoxContainer" parent="TabContainer"]
layout_mode = 2
theme_override_constants/separation = 0
metadata/_tab_index = 0
[node name="VSeparator" type="VSeparator" parent="TabContainer/NormalContainer"]
layout_mode = 2
theme_override_constants/separation = 25
[node name="CheckBox" type="CheckBox" parent="TabContainer/NormalContainer"]
unique_name_in_owner = true
layout_mode = 2
size_flags_stretch_ratio = 0.5
button_pressed = true
[node name="VSeparator2" type="VSeparator" parent="TabContainer/NormalContainer"]
layout_mode = 2
theme_override_constants/separation = 25
[node name="LogLevel" type="Label" parent="TabContainer/NormalContainer"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
size_flags_stretch_ratio = 0.3
theme_override_font_sizes/font_size = 70
text = "Trace"
clip_text = true
-70
View File
@@ -1,70 +0,0 @@
class_name LogViewer
extends AcceptDialog
var line_steps: PackedInt32Array = [10, 50, 100, 200, 500, 1000, -1] # -1 = all
@onready var log_text: TextEdit = $VBox/LogText
@onready var slider: HSlider = $VBox/Controls/LineCountSlider
@onready var slider_label: Label = $VBox/Controls/SliderLabel
@onready var level_dropdown: OptionButton = $VBox/Controls/LogLevelDropdown
func _ready() -> void:
# Fill the list with the log enum
if level_dropdown.get_item_count() == 0:
for level_name: String in Log.LogLevel.keys():
var value: int = Log.LogLevel[level_name]
level_dropdown.add_item(level_name, value)
level_dropdown.item_selected.connect(_on_level_changed)
func show_logs() -> void:
slider.min_value = 0
slider.max_value = line_steps.size() - 1
slider.step = 1
slider.value = 0
if not slider.value_changed.is_connected(_on_slider_changed):
slider.value_changed.connect(_on_slider_changed)
level_dropdown.select(Log.current_level)
_update_log_text()
popup_centered_ratio(0.9)
func _on_slider_changed(_value: float) -> void:
_update_log_text()
func _update_log_text() -> void:
var idx: int = int(slider.value)
var lines_to_show: int = line_steps[idx]
var total: int = Log.all_logs.size()
var subset: PackedStringArray
if lines_to_show == -1:
slider_label.text = "Show: all (%d lines)" % total
subset = Log.all_logs
else:
slider_label.text = "Show: %d last lines" % lines_to_show
var start: int = maxi(total - lines_to_show, 0)
subset = Log.all_logs.slice(start, total)
log_text.text = "\n".join(subset)
log_text.scroll_vertical = log_text.get_line_count() # Scroll down
func _on_level_changed(_index: int) -> void:
var selected_level: int = level_dropdown.get_selected_id()
Log.current_level = selected_level as Log.LogLevel
Log.info("Log level changed to %s" % Log.LogLevel.keys()[selected_level])
func _on_confirmed() -> void:
queue_free()
func _on_canceled() -> void:
queue_free()
-1
View File
@@ -1 +0,0 @@
uid://b0ymo1jg6thjx
-50
View File
@@ -1,50 +0,0 @@
[gd_scene load_steps=2 format=3 uid="uid://dvc25hgxlvw6u"]
[ext_resource type="Script" uid="uid://b0ymo1jg6thjx" path="res://sources/ui/log_viewer.gd" id="1_r352f"]
[node name="LogViewer" type="AcceptDialog"]
oversampling_override = 1.0
title = "Logs"
size = Vector2i(900, 600)
visible = true
script = ExtResource("1_r352f")
[node name="VBox" type="VBoxContainer" parent="."]
offset_right = 900.0
offset_bottom = 533.0
[node name="Controls" type="HBoxContainer" parent="VBox"]
layout_mode = 2
alignment = 1
[node name="LineCountSlider" type="HSlider" parent="VBox/Controls"]
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 4
max_value = 20.0
value = 10.0
[node name="SliderLabel" type="Label" parent="VBox/Controls"]
layout_mode = 2
text = "Show: ?"
[node name="Control" type="Control" parent="VBox/Controls"]
layout_mode = 2
size_flags_horizontal = 3
[node name="Label" type="Label" parent="VBox/Controls"]
layout_mode = 2
text = "Change log level:"
[node name="LogLevelDropdown" type="OptionButton" parent="VBox/Controls"]
layout_mode = 2
[node name="LogText" type="TextEdit" parent="VBox"]
layout_mode = 2
size_flags_vertical = 3
placeholder_text = "Placeholder Text"
editable = false
context_menu_enabled = false
[connection signal="canceled" from="." to="." method="_on_canceled"]
[connection signal="confirmed" from="." to="." method="_on_confirmed"]
-7
View File
@@ -12,7 +12,6 @@ enum LogLevel {
}
const LOG_PATH: String = "user://Logs/"
const VIEWER_SCENE: PackedScene = preload("res://sources/ui/log_viewer.tscn")
var current_level: LogLevel = LogLevel.NONE
var log_file_path: String
@@ -136,12 +135,6 @@ func _log_to_file(message: String) -> void:
log_file.flush()
func open_log_popup() -> void:
var viewer: LogViewer = VIEWER_SCENE.instantiate()
get_tree().root.add_child(viewer)
viewer.show_logs()
# trace can be used everywhere, in order to have a full log of all that is hapenning
func trace(msg: String) -> void: _log_internal(LogLevel.TRACE, msg)