337 lines
13 KiB
GDScript
337 lines
13 KiB
GDScript
extends Node
|
|
|
|
const enable_debug = true
|
|
var frame_count = 0
|
|
# Inside SweetLogger or a static helper class:
|
|
static func get_current_function_name() -> String:
|
|
var stack = get_stack()
|
|
# Index 0 is 'get_current_function_name'
|
|
# Index 1 is the function that called 'get_current_function_name'
|
|
if stack.size() > 2:
|
|
return stack[2].function
|
|
return "unknown_func"
|
|
|
|
static func get_caller_file_name() -> String:
|
|
var stack = get_stack()
|
|
if stack.size() > 2:
|
|
return stack[2].source.get_file() # e.g., "station.gd"
|
|
return "unknown_file"
|
|
|
|
func _process(delta: float) -> void:
|
|
frame_count += 1
|
|
|
|
|
|
## Global Logger singleton for consistent logging across the game.
|
|
## All logs are prefixed with [peer_id]: to identify which instance is logging.
|
|
## Uses print_rich with colorized backgrounds for log type, local time, peer IDs, and script context.
|
|
|
|
# COLORS
|
|
#===================================================================================#
|
|
## Log type configuration with background color, message background color, and contrast text color
|
|
const LOG_TYPES = {
|
|
"log": {
|
|
"bg_color": "#2d2d2d",
|
|
"message_bg_color": "#3d3d3d",
|
|
"text_color": "#ffffff"
|
|
},
|
|
"info": {
|
|
"bg_color": "#2563eb",
|
|
"message_bg_color": "#3b82f6",
|
|
"text_color": "#ffffff"
|
|
},
|
|
"warning": {
|
|
"bg_color": "#fbbf24",
|
|
"message_bg_color": "#fcd34d",
|
|
"text_color": "#ffffff"
|
|
},
|
|
"error": {
|
|
"bg_color": "#dc2626",
|
|
"message_bg_color": "#ef4444",
|
|
"text_color": "#ffffff"
|
|
},
|
|
"debug": {
|
|
"bg_color": "#7c3aed",
|
|
"message_bg_color": "#a78bfa",
|
|
"text_color": "#ffffff"
|
|
}
|
|
}
|
|
|
|
## Contrast text color that works on all backgrounds
|
|
const CONTRAST_TEXT = "#ffffff"
|
|
|
|
## Predefined colors for special peer IDs
|
|
const SPECIAL_PEER_COLORS = {
|
|
"SERVER": {
|
|
"bg_color": "#7c3aed",
|
|
"text_color": "#ffffff"
|
|
},
|
|
"DISCONNECTED": {
|
|
"bg_color": "#6b7280",
|
|
"text_color": "#ffffff"
|
|
}
|
|
}
|
|
|
|
const PEER_COLOR_PALETTE = [
|
|
{"bg_color": "#10b981", "text_color": "#ffffff"}, # emerald green
|
|
{"bg_color": "#06b6d4", "text_color": "#ffffff"}, # cyan
|
|
{"bg_color": "#ec4899", "text_color": "#ffffff"}, # fuchsia pink
|
|
{"bg_color": "#f97316", "text_color": "#ffffff"}, # orange
|
|
{"bg_color": "#14b8a6", "text_color": "#ffffff"}, # teal
|
|
{"bg_color": "#84cc16", "text_color": "#000000"}, # lime green
|
|
{"bg_color": "#0ea5e9", "text_color": "#ffffff"}, # sky blue
|
|
{"bg_color": "#22c55e", "text_color": "#ffffff"}, # green
|
|
{"bg_color": "#e11d48", "text_color": "#ffffff"}, # rose red
|
|
{"bg_color": "#d946ef", "text_color": "#ffffff"}, # magenta
|
|
]
|
|
|
|
## Background color for the entire log line
|
|
const LOG_LINE_BG_COLOR = "#1a1a1a"
|
|
|
|
## Background color for the script/function name column
|
|
const SCRIPT_FUNCTION_BG_COLOR = "#4a5a6a"
|
|
|
|
## Background color for the local time column (between log type and peer id)
|
|
const TIMESTAMP_BG_COLOR = "#1e3a5f"
|
|
#===================================================================================#
|
|
|
|
# WIDTHS
|
|
#===================================================================================#
|
|
## Column widths for alignment (in characters)
|
|
const PEER_ID_COLUMN_WIDTH = 6
|
|
const LOG_TYPE_COLUMN_WIDTH = 10
|
|
## Default mm:ss:ms; with SHOW_TIMESTAMP_HOURS, hh:mm:ss:ms
|
|
const TIMESTAMP_COLUMN_WIDTH_MMSSMS = 9
|
|
const TIMESTAMP_COLUMN_WIDTH_HHMMSSMS = 12
|
|
const SCRIPT_FUNCTION_COLUMN_WIDTH = 46
|
|
|
|
const TRUNCATE_PEER_NAME = 6
|
|
const TRUNCATE_SCRIPT_NAME = 20
|
|
const TRUNCATE_FUNCTION_NAME = 26
|
|
#===================================================================================#
|
|
|
|
# CONFIGURATION
|
|
#===================================================================================#
|
|
## Cache for peer ID colors to ensure consistency
|
|
var _peer_color_cache: Dictionary = {}
|
|
|
|
## Enable/disable showing script name and function name in logs
|
|
@export var SHOW_SCRIPT_NAME = true
|
|
@export var SHOW_FUNCTION_NAME = true
|
|
## When false (default), timestamp is local mm:ss:ms. When true, local hh:mm:ss:ms (wider column).
|
|
@export var SHOW_TIMESTAMP_HOURS = true
|
|
#===================================================================================#
|
|
|
|
# GETTERS
|
|
#===================================================================================#
|
|
func _get_peer_id() -> String:
|
|
"""Get the current peer ID, or return a default identifier if not connected."""
|
|
if multiplayer == null:
|
|
return "DISCONNECTED"
|
|
|
|
# Check if we're the server (server always has ID 1, but we check is_server() for clarity)
|
|
if multiplayer.is_server():
|
|
return "SERVER"
|
|
|
|
# Get the unique ID (will be 1 for server, or random positive int > 1 for clients)
|
|
var peer_id = multiplayer.get_unique_id()
|
|
return str(peer_id)
|
|
|
|
func _get_peer_color_config(peer_id_str: String) -> Dictionary:
|
|
"""Get color configuration for a peer ID string."""
|
|
# Check special peer IDs first
|
|
if SPECIAL_PEER_COLORS.has(peer_id_str):
|
|
return SPECIAL_PEER_COLORS[peer_id_str]
|
|
|
|
# Check cache
|
|
if _peer_color_cache.has(peer_id_str):
|
|
return _peer_color_cache[peer_id_str]
|
|
|
|
# Generate consistent color based on peer ID
|
|
# Try to parse the peer_id_str as a number
|
|
var peer_id_num = peer_id_str.to_int()
|
|
var color_index: int
|
|
if peer_id_num > 0:
|
|
color_index = peer_id_num % PEER_COLOR_PALETTE.size()
|
|
else:
|
|
# If parsing fails, use hash of the string for consistency
|
|
var hash_value = peer_id_str.hash()
|
|
color_index = abs(hash_value) % PEER_COLOR_PALETTE.size()
|
|
|
|
var color_config = PEER_COLOR_PALETTE[color_index].duplicate()
|
|
_peer_color_cache[peer_id_str] = color_config
|
|
return color_config
|
|
|
|
#===================================================================================#
|
|
|
|
# FORMATTERS
|
|
#===================================================================================#
|
|
func _format_message(message: String, args: Array = []) -> String:
|
|
"""Format message with optional arguments."""
|
|
if args.is_empty():
|
|
return message
|
|
|
|
var formatted = message
|
|
for i in range(args.size()):
|
|
formatted = formatted.replace("{" + str(i) + "}", str(args[i]))
|
|
|
|
return formatted
|
|
|
|
func _format_rich_text(text: String, bg_color: String, text_color: String) -> String:
|
|
"""Format text with background and text colors using BBCode."""
|
|
return "[bgcolor=%s][color=%s]%s[/color][/bgcolor]" % [bg_color, text_color, text]
|
|
|
|
func _format_space_with_bg(bg_color: String) -> String:
|
|
"""Format a space character with a background color."""
|
|
return "[bgcolor=%s] [/bgcolor]" % bg_color
|
|
|
|
func _get_padding(text_length: int, width: int) -> String:
|
|
"""Get padding string for a given text length and target width."""
|
|
if text_length >= width:
|
|
return ""
|
|
var padding = ""
|
|
for i in range(width - text_length):
|
|
padding += " "
|
|
return padding
|
|
|
|
func _pad_text(text: String, width: int) -> String:
|
|
"""Pad text to a specific width for column alignment."""
|
|
return text + _get_padding(text.length(), width)
|
|
|
|
func _truncate_text(text: String, max_length: int) -> String:
|
|
"""Truncate text to a maximum length."""
|
|
if text.length() <= max_length:
|
|
return text
|
|
return text.substr(0, max_length)
|
|
|
|
func _get_local_timestamp_string() -> String:
|
|
"""Local time: mm:ss:ms by default, or hh:mm:ss:ms when SHOW_TIMESTAMP_HOURS is true."""
|
|
var unix = Time.get_unix_time_from_system()
|
|
var ms = clampi(int(floor(fmod(unix, 1.0) * 1000.0)), 0, 999)
|
|
var t = Time.get_time_dict_from_system()
|
|
if SHOW_TIMESTAMP_HOURS:
|
|
return "%02d:%02d:%02d:%03d" % [t.hour, t.minute, t.second, ms]
|
|
return "%02d:%02d:%03d" % [t.minute, t.second, ms]
|
|
|
|
#===================================================================================#
|
|
|
|
# PRINT
|
|
#===================================================================================#
|
|
func _print_rich_log(peer_id_str: String, log_type: String, message: String, script_name: String = "", function_name: String = "") -> void:
|
|
"""Print a rich formatted log with peer ID and log type colors."""
|
|
var peer_color = _get_peer_color_config(peer_id_str)
|
|
var log_config = LOG_TYPES.get(log_type, LOG_TYPES["log"])
|
|
|
|
# Use peer ID directly without "Peer" prefix
|
|
var peer_label = peer_id_str
|
|
var log_type_label = str(frame_count) + "-" + log_type.to_upper()
|
|
|
|
# Pad peer ID column for alignment
|
|
var peer_padded = _truncate_text(_pad_text(peer_label, PEER_ID_COLUMN_WIDTH), TRUNCATE_PEER_NAME)
|
|
|
|
# Format peer ID with its background color
|
|
var peer_formatted = _format_rich_text(" " + peer_padded, peer_color.bg_color, peer_color.text_color)
|
|
|
|
# Format log type with its background color
|
|
var log_type_padded = _pad_text(log_type_label, LOG_TYPE_COLUMN_WIDTH)
|
|
var log_type_formatted = _format_rich_text(log_type_padded, log_config.bg_color, log_config.text_color)
|
|
|
|
# Local time column (between log type and peer id)
|
|
var ts_width = TIMESTAMP_COLUMN_WIDTH_HHMMSSMS if SHOW_TIMESTAMP_HOURS else TIMESTAMP_COLUMN_WIDTH_MMSSMS
|
|
var time_padded = _pad_text(_get_local_timestamp_string(), ts_width)
|
|
var time_formatted = _format_rich_text(time_padded, TIMESTAMP_BG_COLOR, CONTRAST_TEXT)
|
|
|
|
# Build the context string (script name and function name) combined in one column
|
|
var context_string = ""
|
|
var script_function_parts: Array[String] = []
|
|
|
|
if SHOW_SCRIPT_NAME and script_name != "":
|
|
var script_truncated = _truncate_text(script_name, TRUNCATE_SCRIPT_NAME)
|
|
script_function_parts.append(script_truncated)
|
|
|
|
if SHOW_FUNCTION_NAME and function_name != "":
|
|
var function_truncated = _truncate_text(function_name, TRUNCATE_FUNCTION_NAME)
|
|
script_function_parts.append(function_truncated)
|
|
|
|
if not script_function_parts.is_empty():
|
|
# Combine script and function names with a separator
|
|
var combined = "::".join(script_function_parts)
|
|
# Pad, then format with custom background color (so whitespace has background too)
|
|
var combined_padded = _pad_text(combined, SCRIPT_FUNCTION_COLUMN_WIDTH)
|
|
var combined_formatted = _format_rich_text(combined_padded, SCRIPT_FUNCTION_BG_COLOR, log_config.text_color)
|
|
context_string = combined_formatted
|
|
|
|
# Format message with text color only (no background)
|
|
var message_formatted = "[color=%s]%s[/color]" % [log_config.text_color, message]
|
|
|
|
# Format spaces with peer ID background color
|
|
var peer_space = _format_space_with_bg(peer_color.bg_color)
|
|
|
|
# Combine log type, context, and message with peer-colored spaces
|
|
var peer_and_message = peer_formatted
|
|
if context_string != "":
|
|
peer_and_message += peer_space + context_string
|
|
peer_and_message += peer_space + message_formatted
|
|
|
|
# Create the full log line: log type, timestamp, then peer id and message
|
|
var log_line = log_type_formatted + time_formatted + peer_and_message
|
|
var wrapped_line = "[bgcolor=%s]%s[/bgcolor]" % [LOG_LINE_BG_COLOR, log_line]
|
|
|
|
print_rich(wrapped_line)
|
|
|
|
#===================================================================================#
|
|
|
|
# CALLERS
|
|
#===================================================================================#
|
|
func log(message: String, args: Array = [], script_name: String = "", function_name: String = "") -> void:
|
|
"""Basic log function with peer_id prefix."""
|
|
if script_name.is_empty():
|
|
script_name = get_caller_file_name()
|
|
if function_name.is_empty():
|
|
function_name = get_current_function_name()
|
|
var formatted = _format_message(message, args)
|
|
var peer_id_str = _get_peer_id()
|
|
_print_rich_log(peer_id_str, "log", formatted, script_name, function_name)
|
|
|
|
func info(message: String, args: Array = [], script_name: String = "", function_name: String = "") -> void:
|
|
"""Log an informational message."""
|
|
if script_name.is_empty():
|
|
script_name = get_caller_file_name()
|
|
if function_name.is_empty():
|
|
function_name = get_current_function_name()
|
|
var formatted = _format_message(message, args)
|
|
var peer_id_str = _get_peer_id()
|
|
_print_rich_log(peer_id_str, "info", formatted, script_name, function_name)
|
|
|
|
func warning(message: String, args: Array = [], script_name: String = "", function_name: String = "") -> void:
|
|
"""Log a warning message."""
|
|
if script_name.is_empty():
|
|
script_name = get_caller_file_name()
|
|
if function_name.is_empty():
|
|
function_name = get_current_function_name()
|
|
var formatted = _format_message(message, args)
|
|
var peer_id_str = _get_peer_id()
|
|
_print_rich_log(peer_id_str, "warning", formatted, script_name, function_name)
|
|
|
|
func error(message: String, args: Array = [], script_name: String = "", function_name: String = "") -> void:
|
|
"""Log an error message."""
|
|
if script_name.is_empty():
|
|
script_name = get_caller_file_name()
|
|
if function_name.is_empty():
|
|
function_name = get_current_function_name()
|
|
var formatted = _format_message(message, args)
|
|
var peer_id_str = _get_peer_id()
|
|
_print_rich_log(peer_id_str, "error", formatted, script_name, function_name)
|
|
|
|
func debug(message: String, args: Array = [], script_name: String = "", function_name: String = "") -> void:
|
|
"""Log a debug message."""
|
|
if not enable_debug:
|
|
return
|
|
if script_name.is_empty():
|
|
script_name = get_caller_file_name()
|
|
if function_name.is_empty():
|
|
function_name = get_current_function_name()
|
|
var formatted = _format_message(message, args)
|
|
var peer_id_str = _get_peer_id()
|
|
_print_rich_log(peer_id_str, "debug", formatted, script_name, function_name)
|
|
#===================================================================================#
|