# 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 _file_write_enabled: _file.store_line(format_file(msg)) _file.call_deferred('flush') if _default_output_enabled: print(format_stdout(msg)) func _open_file() -> void: 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)