Skip to content

Instantly share code, notes, and snippets.

@bil-bas
Last active April 23, 2021 02:39
Show Gist options
  • Save bil-bas/0daff3fd31411488fe1b to your computer and use it in GitHub Desktop.
Save bil-bas/0daff3fd31411488fe1b to your computer and use it in GitHub Desktop.
Simple logger for Godot Game Engine (godotgameengine.org)
# GodotLogger by Spooner
# ======================
#
# logger.gd is a simple logging system. It allows for more formatted logging,
# logging levels and logging to a file.
#
# Installation
# ------------
#
# Place this file somewhere (for example, 'res://root/logger.gd')
# and autoload it (in project settings) to make it into a globally accessible singleton.
#
# Logger levels
# -------------
#
# Level.DEBUG - Show all log messages
# Level.INFO - Show info(), warning(), error() and critical() log messages [DEFAULT]
# Level.WARNING - Show warning(), error() and critical() log messages
# Level.ERROR - Show error() and critical() log messages
# Level.CRITICAL - Show only critical() log messages
#
# Time formats
# ------------
#
# TimeFormat.NONE
# TimeFormat.DATETIME [YYYY-MM-DD HH:MM:SS.mmm]
# TimeFormat.TIME [HH:MM:SS.mmm]
# TimeFormat.ELAPSED [H:MM:SS.mmm]
#
# Examples
# --------
#
# Getting a reference to the global logger object with:
# var logger = get_node('/root/logger')
#
# Setting the logger level (default is Level.INFO):
# logger.level = logger.Level.DEBUG
#
# Setting whether to print() message (default is to print):
# logger.print_std = false
#
# Setting showing the current elapsed time (defaults to show TimeFormat.DATETIME):
# logger.time_format = TimeFormat.ELAPSED
#
# Setting time formatter to use your own function (which would normally be called as my_instance.time_formatter()):
# logger.time_format_func = funcref(my_instance, "time_formatter")
#
# Logging to a file (set to 'null' to close the file):
# logger.filename = 'user://log.txt'
#
# Logging messages of various types (will use var2str() to output any non-string being logged):
# logger.info("Creating a new fish object")
# logger.debug([my_vector3, my_vector2, my_list])
# logger.warning("Tried to take over the moon!")
# logger.error("File doesn't exist, so I can't go on")
# logger.critical("Divided by an ocelot error! Segfault immanent")
#
# License: MIT
extends Node
# Levels of debugging available
class Level:
const DEBUG = 0
const INFO = 1 # default
const WARNING = 2
const ERROR = 3
const CRITICAL = 4
# Built in time formatters
class TimeFormat:
const NONE = 0
const ELAPSED = 1
const TIME = 2
const DATETIME = 3 # default
# Print to stdout?
var print_stdout = true setget set_print_stdout, get_print_stdout
func get_print_stdout():
return print_stdout
func set_print_stdout(value):
assert(value in [true, false])
print_stdout = value
# Logging level.
var level = Level.INFO setget set_level, get_level
func get_level():
return level
func set_level(value):
assert(level in [Level.DEBUG, Level.INFO, Level.WARNING, Level.ERROR, Level.CRITICAL])
level = value
# Logging to file.
var file = null
var filename = null setget set_filename, get_filename
func get_filename():
return filename
func set_filename(value):
if file != null:
info("Stopped logging to file: %s" % filename)
file.close()
if value != null:
file = File.new()
filename = value
file.open(filename, File.WRITE)
info("Started logging to file: %s" % filename)
else:
file = null
filename = null
# Log timer
var time_format_func = funcref(self, "format_time_datetime") setget set_time_format_func
func set_time_format_func(value):
time_format_func = value
var time_format = TimeFormat.DATETIME setget set_time_format
func set_time_format(value):
if value == TimeFormat.NONE:
self.time_format_func = funcref(self, "format_time_none")
elif value == TimeFormat.DATETIME:
self.time_format_func = funcref(self, "format_time_datetime")
elif value == TimeFormat.TIME:
self.time_format_func = funcref(self, "format_time_time")
elif value == TimeFormat.ELAPSED:
self.time_format_func = funcref(self, "format_time_elapsed")
else:
assert(false) # Bad time format used.
# --- Time formatters for use by the logger.
func format_time_none():
return ""
func format_time_elapsed():
return "[%s] " % _format_elapsed()
func format_time_time():
return "[%s] " % _format_time()
func format_time_datetime():
return "[%s %s] " % [_format_date(), _format_time()]
# --- General time formatting functions
func _format_time():
"""Not used directly, but might come in useful"""
var time = OS.get_time()
# This is not "correct", but gives impression of ms moving on!
var ms = OS.get_ticks_msec() % 1000
return "%02d:%02d:%02d.%03d" % [time["hour"], time["minute"], time["second"], ms]
func _format_elapsed(time):
"""Not used directly, but might come in useful"""
var time = OS.get_ticks_msec()
var ms = time % 1000
var s = int(time / 1000) % 60
var m = int(time / 60000) % 60
var h = int(time / 3600000)
return "%d:%02d:%02d.%03d " % [h, m, s, ms]
func _format_date():
"""Not used directly, but might come in useful"""
var date = OS.get_date()
return "%d-%02d-%02d" % [date["year"], date["month"], date["day"]]
# --- Message writing methods
func debug(data):
"""Debugging message"""
if level == Level.DEBUG:
_write('DEBUG:', data)
func info(data):
"""Informational message"""
if level <= Level.INFO:
_write('INFO:', data)
func warning(data):
"""Warning message"""
if level <= Level.WARNING:
_write('WARN:', data)
func error(data):
"""Error message"""
if level <= Level.ERROR:
_write('ERROR:', data)
func critical(data):
"""Critical error message"""
_write('CRIT:', data)
func _write(type, data):
"""Actually write out the message string"""
if typeof(data) != TYPE_STRING:
data = var2str(data)
var message = '%s%5s %s' % [time_format_func.call_func(), type, data]
if print_stdout:
print(message)
if file != null:
file.store_line(message)
@SdgGames
Copy link

Thank you for this, it's exactly what I was looking for. Just FYI, I got a couple of errors on Godot 3.2.3.stable.mono. I'm assuming that this works for an earlier version.

res://addons/logger.gd:156 - Parse Error: Variable "time" already defined in the scope (at line 154).
res://addons/logger.gd:95 - Parse Error: The member "filename" already exists in a parent class.

I made a fork and fixed these for myself. I wasn't 100% sure about the first error, but it seems to be working with the edit I made. If you want those changes, feel free to pull them. (I'm haven't used gist.github before, so I'll leave that to you :-) ) Thanks!

@bil-bas
Copy link
Author

bil-bas commented Apr 23, 2021

Hi!

  • The 'var time' in the function tries to recreate the 'time' parameter. You are correct to remove the parameter. If you look, the call to tge function already doesn't pass a parameter. Bad testing on my part, rather than changes to godot...
  • Filename/file_name seems to be new.

Honestly, I'm amazed that there isn't a decent logging system built into godot by now!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment