269 lines
8.7 KiB
GDScript
269 lines
8.7 KiB
GDScript
# Copyright (c) 2020-2023 Mansur Isaev and contributors - MIT License
|
||
# See `LICENSE.md` included in the source distribution for details.
|
||
|
||
## Класс журнал событий.
|
||
class_name Logger extends Node
|
||
|
||
## Выдается, когда журнал событий создаёт сообщение.
|
||
signal logged(message: Dictionary)
|
||
|
||
|
||
enum {
|
||
INFO = 1, ## Информационный уровень.
|
||
DEBUG = 1<<1, ## Уровень предупреждения.
|
||
WARNING = 1<<2, ## Уровень отладки.
|
||
ERROR = 1<<3, ## Уровень ошибок.
|
||
FATAL = 1<<4, ## Критический уровень.
|
||
MAX = FATAL, ## Побитовый сдвиг влево для пользовательских уровней.
|
||
}
|
||
|
||
|
||
var _level : int
|
||
|
||
var _log_enabled : bool
|
||
var _default_output_enabled : bool
|
||
var _file_write_enabled : bool
|
||
|
||
var _file : FileAccess
|
||
var _file_path : String
|
||
|
||
var _format_default_output : String
|
||
var _format_file : String
|
||
|
||
var _names = {
|
||
INFO: "ИНФО",
|
||
DEBUG: "ОТЛАДКА",
|
||
WARNING: "ВНИМАНИЕ",
|
||
ERROR: "ОШИБКА",
|
||
FATAL: "АВАРИЯ"
|
||
}
|
||
var _msg_array: Array
|
||
var _filters: Array
|
||
|
||
## Преопределение пользовательских значений по умолчанию.
|
||
func _enter_tree() -> void:
|
||
set_log_enabled(ProjectSettings.get_setting("plugins/logger/log_enabled", true))
|
||
set_default_output_enabled(ProjectSettings.get_setting("plugins/logger/default_output", false))
|
||
|
||
_level = ProjectSettings.get_setting("plugins/logger/level", INFO | DEBUG | WARNING | ERROR | FATAL)
|
||
|
||
set_file_path(ProjectSettings.get_setting("plugins/logger/file/file_path", "user://uarep-ctl.log"))
|
||
set_file_write_enabled(ProjectSettings.get_setting("plugins/logger/file/log_file_write", true))
|
||
|
||
_format_default_output = ProjectSettings.get_setting("plugins/logger/default_output_format", "[{year}-{month}-{day} {hour}:{minute}:{second}][{level}]{text}")
|
||
_format_file = ProjectSettings.get_setting("plugins/logger/file_format", "[{year}-{month}-{day} {hour}:{minute}:{second}][{level}]{text}")
|
||
|
||
|
||
func _exit_tree() -> void:
|
||
_close_file()
|
||
|
||
## Возвращает [param true], если у журнала события есть уровень.
|
||
func has_level(level: int) -> bool:
|
||
return _names.has(level)
|
||
|
||
## Установить включенный уровень.
|
||
func set_level(level: int, enabled: bool) -> void:
|
||
assert(has_level(level), "Invalid level.")
|
||
if not has_level(level):
|
||
return
|
||
|
||
if enabled:
|
||
_level |= level
|
||
else:
|
||
_level &= ~level
|
||
|
||
## Добавить пользовательский уровень журнала. Значение уровня должно быть уникальным и превышать [member MAX].
|
||
## Для вызова пользовательского уровня используйте метод - [method message].
|
||
func add_level(level: int, nm: String) -> void:
|
||
assert(not has_level(level), "Has level.")
|
||
assert(level > MAX and not level % 2, "Invalid level.")
|
||
assert(name, "Invalid name.")
|
||
|
||
if not has_level(level) and level > MAX and not level % MAX and not name.is_empty():
|
||
_names[level] = nm
|
||
set_level(level, true)
|
||
|
||
## Возвращает имя уровня.
|
||
func get_level_name(level: int) -> String:
|
||
return _names[level]
|
||
|
||
## Установить включенный журнал. Отключить ВСЕ сообщения.
|
||
func set_log_enabled(enabled: bool) -> void:
|
||
_log_enabled = enabled
|
||
|
||
## Возвращает [param true], если журнал включен.
|
||
func is_log_enabled() -> bool:
|
||
return _log_enabled
|
||
|
||
## Установка выхода по умолчанию включена.
|
||
func set_default_output_enabled(enabled: bool) -> void:
|
||
_default_output_enabled = enabled
|
||
|
||
## Возвращает [param true], если вывод по умолчанию включен.
|
||
func is_default_output_enabled() -> bool:
|
||
return _default_output_enabled
|
||
|
||
## Установить разрешение на запись файла.
|
||
func set_file_write_enabled(enabled: bool) -> void:
|
||
if _file_write_enabled == enabled:
|
||
return
|
||
|
||
_file_write_enabled = enabled
|
||
|
||
if enabled:
|
||
_open_file()
|
||
else:
|
||
_close_file()
|
||
|
||
## Возвращает [param true], если запись в файл разрешена.
|
||
func is_file_write_enabled() -> bool:
|
||
return _file_write_enabled
|
||
|
||
## Задать путь к файлу журнала.
|
||
func set_file_path(path: String) -> void:
|
||
assert(path.is_absolute_path(), "Invalid path.")
|
||
if not path.is_absolute_path() or _file_path == path:
|
||
return
|
||
|
||
_file_path = path
|
||
|
||
if is_file_write_enabled():
|
||
_open_file()
|
||
|
||
## Возвращает путь к файлу журнала.
|
||
func get_file_path() -> String:
|
||
return _file_path
|
||
|
||
## Создать информационное сообщение.
|
||
func info(text: String) -> void:
|
||
message(INFO, text)
|
||
|
||
## Создать отладочное сообщение. Только для отладочной сборки.
|
||
func debug(text: String) -> void:
|
||
if OS.is_debug_build():
|
||
message(DEBUG, text)
|
||
|
||
## Создать предупреждающее сообщение.
|
||
func warning(text: String) -> void:
|
||
message(WARNING, text)
|
||
|
||
## Создать сообщение об ошибке.
|
||
func error(text: String) -> void:
|
||
message(ERROR, text)
|
||
|
||
## Создать сообщение о критической ошибке.
|
||
func fatal(text: String) -> void:
|
||
message(FATAL, text)
|
||
|
||
## Форматировать строку для файла.
|
||
func format_file(msg: Dictionary) -> String:
|
||
return _format_file.format(
|
||
{
|
||
"year": "%02d" % msg["year"],
|
||
"month": "%02d" % msg["month"],
|
||
"day": "%02d" % msg["day"],
|
||
"hour": "%02d" % msg["hour"],
|
||
"minute":"%02d" % msg["minute"],
|
||
"second":"%02d" % msg["second"],
|
||
"level": _names[msg["level"]],
|
||
"text": msg["text"],
|
||
}
|
||
)
|
||
|
||
## Форматировать строку для вывода редактора.
|
||
func format_stdout(msg: Dictionary) -> String:
|
||
return _format_default_output.format(
|
||
{
|
||
"year": "%02d" % msg["year"],
|
||
"month": "%02d" % msg["month"],
|
||
"day": "%02d" % msg["day"],
|
||
"hour": "%02d" % msg["hour"],
|
||
"minute":"%02d" % msg["minute"],
|
||
"second":"%02d" % msg["second"],
|
||
"level": _names[msg["level"]],
|
||
"text": msg["text"],
|
||
}
|
||
)
|
||
|
||
## Создать сообщение с пользовательским уровнем.
|
||
func message(level: int, text: String) -> void:
|
||
assert(has_level(level), "Has not level.")
|
||
assert(text, "Invalid message text.")
|
||
|
||
if _log_enabled and _level & level:
|
||
var msg : Dictionary = Time.get_datetime_dict_from_system()
|
||
msg["level"] = level
|
||
msg["level_name"] = _names[level]
|
||
msg["text"] = " " + text
|
||
|
||
_msg_array.append(msg)
|
||
if (_flt_msg(msg)):
|
||
logged.emit(msg)
|
||
|
||
if not Engine.is_editor_hint():
|
||
if _file_write_enabled:
|
||
_file.store_line(format_file(msg))
|
||
_file.call_deferred('flush')
|
||
|
||
if _default_output_enabled:
|
||
print_debug(format_stdout(msg))
|
||
|
||
|
||
func _open_file() -> void:
|
||
if Engine.is_editor_hint():
|
||
return
|
||
|
||
if is_instance_valid(_file) and _file.is_open():
|
||
_file.close()
|
||
|
||
_file = FileAccess.open(get_file_path(), FileAccess.READ_WRITE)
|
||
|
||
var err := FileAccess.get_open_error()
|
||
if err:
|
||
_file = FileAccess.open(get_file_path(), FileAccess.WRITE)
|
||
_file.close()
|
||
_file = FileAccess.open(get_file_path(), FileAccess.READ_WRITE)
|
||
return print_debug(error_string(err))
|
||
|
||
err = _file.get_error()
|
||
if err:
|
||
return print_debug(error_string(err))
|
||
_file.seek_end(0)
|
||
|
||
|
||
func _close_file() -> void:
|
||
if is_instance_valid(_file) and _file.is_open():
|
||
_file = null
|
||
|
||
|
||
func save_file(path):
|
||
var f = FileAccess.open(path, FileAccess.WRITE)
|
||
var err := FileAccess.get_open_error()
|
||
if err:
|
||
print_debug(error_string(err))
|
||
var log_text = _file.get_as_text()
|
||
f.store_line(log_text)
|
||
f.close()
|
||
|
||
|
||
func set_filtrs(flt_array: Array):
|
||
_filters = flt_array
|
||
|
||
|
||
func _flt_msg(msg):
|
||
var flt_arr: Array = []
|
||
for item in _filters:
|
||
if item.button_pressed:
|
||
flt_arr.append(item.get_meta('filter'))
|
||
if msg['text'].find(item.get_meta('filter')) != -1:
|
||
return true
|
||
if msg['level_name'] in flt_arr:
|
||
return true
|
||
return false
|
||
|
||
|
||
func add_msgs():
|
||
for msg in _msg_array:
|
||
if (_flt_msg(msg)):
|
||
logged.emit(msg)
|