class_name SimpleLogger extends Node enum LogLevel { TRACE, DEBUG, INFO, WARN, ERROR } enum FilterMode { BLACKLIST, WHITELIST, } @export var current_log_level:LogLevel = LogLevel.DEBUG @export var enable_timestamp:bool = true @export var save_log:bool = false @export var enable_caller: bool = true @export var separator:String = " " @export var folder:String = "" @export var log_filename:String = "addons/Logs/data.log" @export var filter_mode: FilterMode = FilterMode.BLACKLIST @export var filtered_scripts: Array[String] = [] const TIMESTAMP_FORMAT:String = "%H:%M:%S" var log_file_path = folder + log_filename var _timers:Dictionary = {} var _tmp_separator:String = "" #region Logger main stuff ## Set a separator, if once is set to true the separator it ## will be used only one time then going back to previous func set_separator(sep: String, once:bool=false) -> void: if once: self._tmp_separator = self.separator self.separator = sep func _print(level: LogLevel, ...args: Array) -> void: if level < current_log_level: return var level_str:String = LogLevel.keys()[level] var timestamp := Time.get_time_string_from_system() var caller := _get_caller_info() if enable_caller else "" if enable_caller and not _should_log(caller + ".gd"): return var text: String = "" for c:int in args.size(): text += str(args[c]) if c < args.size() - 1: text += self.separator var message:String = "" message += "[%s]" % timestamp if self.enable_timestamp else "" message += "[%-16s]" % caller if self.enable_caller else "" message += "[%-6s]: " % level_str message += "%s" % text match level: LogLevel.ERROR: push_error(message) LogLevel.WARN: push_warning(message) _: print(message) if self.save_log: self._save_logs_to_file(message) if self._tmp_separator != "": self.separator = self._tmp_separator self._tmp_separator = "" func _save_logs_to_file(message: String) -> bool: var file := FileAccess.open(self.log_file_path, FileAccess.READ_WRITE) if file == null: DirAccess.make_dir_recursive_absolute(self.folder) var tmp_file = FileAccess.open(self.log_file_path, FileAccess.WRITE) if tmp_file == null: push_error("Failed to create file %s" % self.log_file_path) return false file = tmp_file file.seek_end() file.store_line(message) return true #endregion #region filters func _get_caller_info() -> String: var stack := get_stack() if stack.size() >= 4: var caller_frame: Dictionary = stack[3] var source: String = caller_frame.get("source", "unknown") var file: String = source.get_file() return file.replace(".gd", "") return "unknown" func _should_log(caller: String) -> bool: var is_in_list := caller in filtered_scripts match filter_mode: FilterMode.BLACKLIST: return not is_in_list FilterMode.WHITELIST: return is_in_list return true func mute(script_name: String) -> void: if script_name not in filtered_scripts: filtered_scripts.append(script_name) func unmute(script_name: String) -> void: filtered_scripts.erase(script_name) func set_whitelist(scripts: Array[String]) -> void: filter_mode = FilterMode.WHITELIST filtered_scripts = scripts func set_blacklist(scripts: Array[String]) -> void: filter_mode = FilterMode.BLACKLIST filtered_scripts = scripts func clear_filters() -> void: filtered_scripts.clear() #endregion #region level functions func trace(...args: Array) -> void: callv("_print", [LogLevel.TRACE] + args) func debug(...args: Array) -> void: callv("_print", [LogLevel.DEBUG] + args) func info(...args: Array) -> void: callv("_print", [LogLevel.INFO] + args) func warning(...args: Array) -> void: callv("_print", [LogLevel.WARN] + args) func error(...args: Array) -> void: callv("_print", [LogLevel.ERROR] + args) #endregion #region abbreviated functions func t(...args: Array) -> void: callv("_print", [LogLevel.TRACE] + args) func d(...args: Array) -> void: callv("_print", [LogLevel.DEBUG] + args) func i(...args: Array) -> void: callv("_print", [LogLevel.INFO] + args) func w(...args: Array) -> void: callv("_print", [LogLevel.WARN] + args) func e(...args: Array) -> void: callv("_print", [LogLevel.ERROR] + args) #endregion #region timers func time_start(label: String) -> void: _timers[label] = Time.get_ticks_usec() func time_end(label: String) -> void: if label in _timers: var elapsed :float = (Time.get_ticks_usec() - _timers[label]) / 1000.0 d("## Performance: ", label, " %.2fms" % elapsed) _timers.erase(label) #endregion