diff --git a/README.md b/README.md index b0b1194..784af1e 100644 --- a/README.md +++ b/README.md @@ -1,59 +1,77 @@ # SimpleLogger -Multi-level logging system for Godot with timestamps and file persistence. +Lightweight logging system for Godot 4.5+ ## Features -- 5 log levels: TRACE, DEBUG, INFO, WARN, ERROR -- Full and abbreviated function names -- Optional timestamps -- File logging with auto-directory creation -- Configurable argument separator +- 5 log levels (TRACE, DEBUG, INFO, WARN, ERROR) +- Caller script tracking +- Script filtering (blacklist/whitelist) +- Performance profiling +- Optional file persistence +- Configurable timestamps and separators + +## Quick Start +```gdscript +Log.i("Player spawned") +Log.d("Health:", player.health, "Position:", player.position) +Log.e("Failed to load resource", path) +``` + +Output: +``` +[12:34:56][player ][INFO ]: Player spawned +[12:34:56][player ][DEBUG ]: Health: 100 Position: (0, 0) +[12:34:56][resource_load][ERROR ]: Failed to load resource res://missing.png +``` + +## API + +### Log Levels + +| Full | Short | Use case | +|------|-------|----------| +| `trace()` | `t()` | Granular debugging | +| `debug()` | `d()` | Development info | +| `info()` | `i()` | General events | +| `warning()` | `w()` | Potential issues | +| `error()` | `e()` | Failures | + +### Filtering +```gdscript +Log.mute("chunk_generator.gd") +Log.unmute("chunk_generator.gd") + +Log.set_blacklist(["map.gd", "noise.gd"]) +Log.set_whitelist(["player.gd", "enemy.gd"]) +Log.clear_filters() +``` + +### Performance +```gdscript +Log.time_start("pathfinding") +# ... code ... +Log.time_end("pathfinding") +# Output: [DEBUG ]: PERF pathfinding 12.34ms +``` + +### Separator +```gdscript +Log.set_separator(" | ") +Log.set_separator(" -> ", true) # one-time only +``` ## Properties -| Property | Type | Default | Description | -| --- | --- | --- | --- | -| `current_log_level` | LogLevel | DEBUG | Minimum log level to display | -| `enable_timestamp` | bool | true | Add timestamp to messages | -| `save_log` | bool | false | Save logs to file | -| `separator` | String | " " | Separator between arguments | -| `folder` | String | "" | Directory for log files | -| `log_filename` | String | "addons/Logs/data.log" | Log file path | - -## Usage - -```gdscript -# Full names -Log.trace("message", var1, var2) -Log.debug("message", data) -Log.info("message") -Log.warning("message") -Log.error("message", error_code) - -# Abbreviated -Log.t("message", var1) -Log.d("message", data) -Log.i("message") -Log.w("message") -Log.e("message") - -# Custom separator -Log.set_separator(", ") -Log.info("a", "b", "c") # [INFO ]: a, b, c - -# One-time separator -Log.set_separator(" | ", true) -Log.info("x", "y") # [INFO ]: x | y -Log.info("a", "b") # [INFO ]: a b -``` - -## Output Format - -``` -[HH:MM:SS]|[LEVEL ]: Message # With timestamp -[LEVEL ]: Message # Without timestamp -``` +| Property | Default | Description | +|----------|---------|-------------| +| `current_log_level` | DEBUG | Minimum level to display | +| `enable_timestamp` | true | Show timestamps | +| `enable_caller` | true | Show calling script | +| `save_log` | false | Write to file | +| `filter_mode` | BLACKLIST | Filter behavior | +| `folder` | "" | Log directory | +| `log_filename` | "addons/Logs/data.log" | Log file path | ## MIT License diff --git a/logger.gd b/logger.gd index 1630849..5978ead 100644 --- a/logger.gd +++ b/logger.gd @@ -9,23 +9,28 @@ enum LogLevel { 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" -const LOG_FORMAT_TIMESTAMP:String = "[%s]|[%-6s]: %s" -const LOG_FORMAT:String = "[%-6s]: %s" var log_file_path = folder + log_filename - - +var _timers:Dictionary = {} var _tmp_separator:String = "" @@ -43,20 +48,24 @@ func _print(level: LogLevel, ...args: Array) -> void: 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 = LOG_FORMAT_TIMESTAMP % [ - timestamp, - level_str, - text, - ] if self.enable_timestamp else LOG_FORMAT % [ - level_str, - text, - ] + + 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) @@ -85,6 +94,53 @@ func _save_logs_to_file(message: String) -> bool: #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: @@ -123,3 +179,17 @@ 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