Git Product home page Git Product logo

godot_addon_gdlogging's Introduction

gdlogging Godot addon

This is a composable logging addon for version 4 of the Godot game engine. To see all available functions and classes see logger.gd.

Usage

var console_sink := Log.ConsoleRichSink.new()
Log.add_sink(console_sink)

var dir_sink := Log.DirSink.new("mylog", "res://logs", 4042)
var buffered_pipe := Log.BufferedPipe.new(dir_sink, 500)
# Don't log TRACE messages to the log file
var file_filtered_pipe := Log.FilteringPipe.new(buffered_pipe, Log.DEBUG)
Log.add_sink(file_filtered_pipe)

Log.debug("Hello World")
# [24/Jan/14 13:28:03] [         Global] [DBG] Hello World
var logger := Log.Logger.new("MyClass")
logger.debug("Hello World")
# [24/Jan/14 13:28:03] [        MyClass] [DBG] Hello World
Log.info(Log.format_error(ERR_FILE_NOT_FOUND))
# [24/Jan/14 13:28:03] [         Global] [INF] File: Not found error.
var timer := Log.LogTimer.new("MyTimer", 0, logger)
OS.delay_msec(1111)
timer.stop()
# [24/Jan/14 13:28:04] [        MyClass] [INF] MyTimer took 1.111963 seconds.
const threshold_msec: int = 1000
timer.set_threshold_msec(threshold_msec)
timer.start()
OS.delay_msec(800)
timer.stop()
# Prints nothing, because the timer was stopped before the threshold was reached.
timer.start()
OS.delay_msec(1111)
timer.stop()
# [24/Jan/14 13:28:06] [        MyClass] [INF] MyTimer exceeded threshold of 1000 msec: took 1.111750 seconds.

Log Levels

  • Log.TRACE: Prints the call site in debug builds. The stack depth can be configured per call.
  • Log.DEBUG: Debug messages
  • Log.INFO: Informational messages
  • Log.WARN: Warnings
  • Log.ERROR: Errors

Pipes

  • Logger: Can receive messages from other loggers and pipes. Users will call the log functions.
  • FilteringPipe: Filters messages by level and forwards them to another pipe.
  • BroadcastPipe: Broadcasts messages to multiple pipes.
  • BufferedPipe: Buffers messages and forwards them to another pipe.

Sinks

All sinks are also pipes.

  • ConsoleSink: Outputs messages to stderr and stdout. Does not support colors.
  • ConsoleRichSink: Outputs messages to the godot Output console. Supports colors.
  • DirSink: Outputs messages to log files and rotates them. Uses a thread for file io.
  • MemoryWindowSink: Keeps n log messages in memory. Can be used to display the last n messages in a GUI. BBCode color support can be configured.

Custom Sinks/Pipes

Classes ending in Pipe are sinks that forward messages to another sink. Classes ending in Sink write messages to a destination.

To create a custom pipe or sink extend the Log.LogPipe or Log.LogSink respectively and implement the methods.

class MyCustomSink extends Log.LogSink:
  # LogPipe and LogSink functions below

  ## Write many log records to the sink
  func write_bulks(p_log_records: Array[Dictionary], p_formatted_messages: PackedStringArray) -> void:
    pass
  ## Flushes the buffer of the sink if it has one.
  func flush_buffer() -> void:
    pass
  ## Cleans up resources used by the sink.
  func close() -> void:
    pass

  # LogSink specific functions below

  ## Sets the log record formatter.
  func set_log_record_formatter(p_log_record_formatter: LogRecordFormatter) -> void:
    pass
  ## Gets the log record formatter.
  func get_log_record_formatter() -> LogRecordFormatter:
    pass

Custom Formatters

class MyLogRecordFormatter extends Log.LogRecordFormatter:
  func format(log_record: Dictionary, p_sink_capabilties: Dictionary) -> String:
    # currently only the bbcode capability is used but user sinks can add their own capabilities

    var time_unix: float = log_record["time_unix"]
    var level: Log.LogLevel = log_record["level"]
    var unformatted_message: String = log_record["unformatted_message"]

    var time_str: String = Time.get_date_string_from_unix_time(time_unix)
    var level_str := Log.get_level_name(level)
    var formatted_message := "[%s] [%s] %s" % [
      time_str,
      level_str,
      unformatted_message
    ]
    return formatted_message
# Loggers use the global formatter by default but this can be overridden in the constructor.
Log.set_log_record_formatter(MyLogRecordFormatter.new())

Installation

cd <godot_project_dir>
cd addons
git submodule add [email protected]:raldone01/godot_addon_gdlogging.git gdlogging
cd gdlogging
git checkout v2.0.0

Project -> Project Settings -> Plugins -> gdlogging -> Activate

Autoloads are a bit janky so you may need to restart the editor for errors to go away.

Troubleshooting

Caution

Deleting the .godot directory has the potential to cause data loss. Make sure to back up your project before doing so and carefully research the implications.

If there are errors when loading the plugin that persist after restarting the editor, try deleting the .godot directory in the project directory.

Licensing

Marked parts are licensed under the LICENSE-godot-logger.md (MIT) license. The rest is licensed under LICENSE.md (MIT).

godot_addon_gdlogging's People

Contributors

raldone01 avatar

Stargazers

Dragonforge Development avatar LI, Wai Yin avatar  avatar Sam Biggins avatar val avatar juho avatar  avatar  avatar Paolo Pustorino avatar Russell Matney avatar CasseyCola avatar

Watchers

 avatar

godot_addon_gdlogging's Issues

Error using Log.Logger.trace: Trying to assign value of type 'int' to a variable of type 'String'

Hello,

I'm receiving an error on this line in logger.gd:474 in the LogRecordFormatter#format() method:

var line: String = frame["line"]

because frame["line"] is an integer and it expects "line" to be a String.

I'm calling it like this (using a ConsoleSink):

var log := Log.Logger.new("Player")

log.trace("hp: %f, maxhp: %f, mana: %f, maxmana: %f, hp+: %f, mp+: %f" % [ health, max_health, mana, max_mana, health_regeneration, mana_regeneration ])

Changing the type to int or wrapping frame["line"] in a str() call to convert it to String first solves the issue.

Plugin does not load correctly (at least in Godot4.3 betas)

Subj.
It seems they have made some changes / improvements in 4.3. Now I can observe the following scenario:

  • logger.gd is about to be added as autoload Log
  • however, while being parsed, it references Log which is not yet there
  • parsing fails
  • autoload is displayed as if it is there, but all other scripts using Log are now broken (they don't display in Inspector), because they reference logger.gd / Log which are not valid

This is not an issue if I replace Log in logger.gd with a reference to a static field, e.g.:

# Copyright (c) 2024 The gdlogging Contributors
# Inspired by https://github.com/KOBUGE-Games/godot-logger/blob/master/logger.gd
@tool
class_name CLog # <--- added class_name to reference static field
extends Node

# ...

static var _log_instance: CLog # <-- static field as an alterntative to referencing Log autoload

#...

	func write_bulks(p_log_records: Array[Dictionary], p_formatted_messages: PackedStringArray) -> void:
                # all Log.* references changed to CLog._log_instance
		CLog._log_instance._logger_direct_console.warning("LogSink: write_bulks() not implemented.")
#...

func _init() -> void:
	_global_broadcast_sink = BroadcastPipe.new()
	var log_formatter := LogRecordFormatter.new()
	_global_logger = Logger.new("Global", LogLevel.TRACE, log_formatter, _global_broadcast_sink)
	_logger_direct_console = Logger.new("gdlogging", LogLevel.TRACE, log_formatter, ConsoleSink.new())
	if Engine.is_editor_hint():
		add_sink(ConsoleSink.new())
	_log_instance = self # <----------------- added this line

Add syntactic sugar

Hi. It would be great to add helper functions to reduce the boilerplate when creating complex scenarios. I added the following functions locally, but maybe worth implementing similar approach in this repo:

class LogSink extends RefCounted:
# ...
	func buffered(buffer_size: int = 20) -> LogSink:
		return BufferedPipe.new(self, buffer_size)
	func filtered(p_level: LogLevel) -> LogSink:
		return FilteringPipe.new(self, p_level)

Usage:

# first - filters, then - buffers, then - pushes to console
var console: Log.LogSink = Log.ConsoleSink.new().buffered(20).filtered(Log.LogLevel.DEBUG)

Error using Log.format_error(): Invalid get index 'Node(logger.gd)::error'

The following code in logger.gd:697 has a bug where it incorrectly indexes "error" instead of "p_error":

static func format_error(p_error: int) -> String:
	if ERROR_MESSAGES.has(p_error):
		return ERROR_MESSAGES[error]
	return "Unknown p_error (%d)." % p_error

Calling like this:

	if data.load_data() != OK:
		logger.error("Error loading save file: %s" % Log.format_error(FileAccess.get_open_error()))

Changing "error" to "p_error" fixes the crash.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.