Logger outputs logfile and savegame on crash (#439)
This commit is contained in:
@@ -105,8 +105,6 @@ func _ready():
|
||||
if global_id.empty():
|
||||
global_id = name
|
||||
|
||||
|
||||
|
||||
# Manage player location at room start
|
||||
if player != null \
|
||||
and escoria.object_manager.get_start_location() != null:
|
||||
|
||||
@@ -2,11 +2,23 @@
|
||||
extends Object
|
||||
class_name ESCLogger
|
||||
|
||||
# Log file format
|
||||
const LOG_FILE_FORMAT: String = "log_%s_%s.log"
|
||||
|
||||
|
||||
# The path of the ESC file that was reported last (used for removing
|
||||
# duplicate warnings
|
||||
var warning_path: String
|
||||
|
||||
# Log file handler
|
||||
var log_file: File
|
||||
|
||||
# Crash save filename
|
||||
var crash_savegame_filename
|
||||
|
||||
# Did we crash already?
|
||||
onready var crashed = false
|
||||
|
||||
|
||||
# Valid log levels
|
||||
enum { LOG_ERROR, LOG_WARNING, LOG_INFO, LOG_DEBUG, LOG_TRACE }
|
||||
@@ -22,6 +34,24 @@ var _level_map: Dictionary = {
|
||||
}
|
||||
|
||||
|
||||
# Logger constructor
|
||||
func _init():
|
||||
# Open logfile in write mode
|
||||
log_file = File.new()
|
||||
var log_file_path = ProjectSettings.get_setting(
|
||||
"escoria/debug/log_file_path"
|
||||
)
|
||||
var date = OS.get_datetime()
|
||||
log_file_path = log_file_path.plus_file(LOG_FILE_FORMAT % [
|
||||
str(date["year"]) + str(date["month"]) + str(date["day"]),
|
||||
str(date["hour"]) + str(date["minute"]) + str(date["second"])
|
||||
])
|
||||
log_file.open(
|
||||
log_file_path,
|
||||
File.WRITE
|
||||
)
|
||||
|
||||
|
||||
# Log a trace message
|
||||
#
|
||||
# #### Parameters
|
||||
@@ -29,9 +59,10 @@ var _level_map: Dictionary = {
|
||||
# * string: Text to log
|
||||
# * args: Additional information
|
||||
func trace(string: String, args = []):
|
||||
if _get_log_level() >= LOG_TRACE:
|
||||
if _get_log_level() >= LOG_TRACE and !crashed:
|
||||
var argsstr = str(args) if !args.empty() else ""
|
||||
printerr("(T)\t" + string + " \t" + argsstr)
|
||||
_log("(T)\t" + string + " \t" + argsstr)
|
||||
|
||||
|
||||
# Log a debug message
|
||||
#
|
||||
@@ -40,9 +71,9 @@ func trace(string: String, args = []):
|
||||
# * string: Text to log
|
||||
# * args: Additional information
|
||||
func debug(string: String, args = []):
|
||||
if _get_log_level() >= LOG_DEBUG:
|
||||
if _get_log_level() >= LOG_DEBUG and !crashed:
|
||||
var argsstr = str(args) if !args.empty() else ""
|
||||
printerr("(D)\t" + string + " \t" + argsstr)
|
||||
_log("(D)\t" + string + " \t" + argsstr)
|
||||
|
||||
|
||||
# Log an info message
|
||||
@@ -52,7 +83,7 @@ func debug(string: String, args = []):
|
||||
# * string: Text to log
|
||||
# * args: Additional information
|
||||
func info(string: String, args = []):
|
||||
if _get_log_level() >= LOG_INFO:
|
||||
if _get_log_level() >= LOG_INFO and !crashed:
|
||||
var argsstr = []
|
||||
if !args.empty():
|
||||
for arg in args:
|
||||
@@ -61,7 +92,7 @@ func info(string: String, args = []):
|
||||
argsstr.append(p.global_id)
|
||||
else:
|
||||
argsstr.append(str(arg))
|
||||
print("(I)\t" + string + " \t" + str(argsstr))
|
||||
_log("(I)\t" + string + " \t" + str(argsstr))
|
||||
|
||||
|
||||
# Log a warning message
|
||||
@@ -71,11 +102,17 @@ func info(string: String, args = []):
|
||||
# * string: Text to log
|
||||
# * args: Additional information
|
||||
func warning(string: String, args = []):
|
||||
if _get_log_level() >= LOG_WARNING:
|
||||
if _get_log_level() >= LOG_WARNING and !crashed:
|
||||
var argsstr = str(args) if !args.empty() else ""
|
||||
printerr("(W)\t" + string + " \t" + argsstr)
|
||||
_log("(W)\t" + string + " \t" + argsstr, true)
|
||||
if ProjectSettings.get_setting("escoria/debug/terminate_on_warnings"):
|
||||
print_stack()
|
||||
_perform_stack_trace_log()
|
||||
_log("%s\n- %s" % [
|
||||
ProjectSettings.get_setting("escoria/debug/crash_message"),
|
||||
log_file.get_path_absolute()
|
||||
])
|
||||
crashed = true
|
||||
escoria.quit()
|
||||
assert(false)
|
||||
|
||||
|
||||
@@ -85,12 +122,25 @@ func warning(string: String, args = []):
|
||||
#
|
||||
# * string: Text to log
|
||||
# * args: Additional information
|
||||
func error(string: String, args = []):
|
||||
if _get_log_level() >= LOG_ERROR:
|
||||
func error(string: String, args = [], do_savegame: bool = true):
|
||||
if _get_log_level() >= LOG_ERROR and !crashed:
|
||||
var argsstr = str(args) if !args.empty() else ""
|
||||
printerr("(E)\t" + string + " \t" + argsstr)
|
||||
_log("(E)\t" + string + " \t" + argsstr, true)
|
||||
if ProjectSettings.get_setting("escoria/debug/terminate_on_errors"):
|
||||
print_stack()
|
||||
_perform_stack_trace_log()
|
||||
if do_savegame:
|
||||
_perform_save_game_log()
|
||||
|
||||
_log("%s\n- %s\n- %s" % [
|
||||
ProjectSettings.get_setting("escoria/debug/crash_message"),
|
||||
log_file.get_path_absolute().get_base_dir().plus_file(
|
||||
escoria.save_manager.crash_savegame_filename.get_file()
|
||||
),
|
||||
log_file.get_path_absolute()
|
||||
])
|
||||
|
||||
crashed = true
|
||||
escoria.quit()
|
||||
assert(false)
|
||||
|
||||
|
||||
@@ -135,7 +185,76 @@ func report_errors(p_path: String, errors: Array) -> void:
|
||||
error(text)
|
||||
|
||||
|
||||
# Write message:
|
||||
# - in logfile
|
||||
# - in stdout, or stderr if err is true.
|
||||
#
|
||||
# #### Parameters
|
||||
#
|
||||
# * message: Message to log
|
||||
# * err: if true, write in stderr
|
||||
func _log(message:String, err: bool = false):
|
||||
if err:
|
||||
printerr(message)
|
||||
else:
|
||||
print(message)
|
||||
_write_logfile(message)
|
||||
|
||||
|
||||
# Returns the currently set log level
|
||||
# **Returns** Log level as set in the configuration
|
||||
func _get_log_level() -> int:
|
||||
return _level_map[ProjectSettings.get_setting("escoria/debug/log_level")]
|
||||
|
||||
|
||||
# Creates a savegame file and save it in output log location
|
||||
func _perform_save_game_log():
|
||||
_log("Performing emergency savegame.")
|
||||
var error = escoria.save_manager.save_game_crash()
|
||||
if error == OK:
|
||||
_log(
|
||||
"Emergency savegame created successfully in folder: %s" %
|
||||
ProjectSettings.get_setting(
|
||||
"escoria/debug/log_file_path"
|
||||
)
|
||||
)
|
||||
else:
|
||||
_log("Emergency savegame creation failed!", false)
|
||||
|
||||
|
||||
# Logs and writes the stack trace into stdout and log file.
|
||||
func _perform_stack_trace_log():
|
||||
_log("Stack trace:")
|
||||
print_stack()
|
||||
_write_stack_logfile()
|
||||
|
||||
|
||||
# Write a message in the output logfile
|
||||
#
|
||||
# #### Parameters
|
||||
#
|
||||
# * message: Message to write
|
||||
func _write_logfile(message: String) -> void:
|
||||
if log_file.is_open():
|
||||
log_file.store_string(message + "\n")
|
||||
|
||||
|
||||
# Write the stacktrace in the output logfile
|
||||
func _write_stack_logfile():
|
||||
var frame_number = 0
|
||||
for stack in get_stack().slice(2, get_stack().size()):
|
||||
_write_logfile(
|
||||
"Frame %s - %s:%s in function '%s'" % [
|
||||
str(frame_number),
|
||||
stack["source"],
|
||||
stack["line"],
|
||||
stack["function"],
|
||||
]
|
||||
)
|
||||
frame_number += 1
|
||||
|
||||
|
||||
# Close the log file cleanly
|
||||
func close_logs():
|
||||
_log("Closing logs peacefully.")
|
||||
log_file.close()
|
||||
|
||||
@@ -7,9 +7,15 @@ var save_enabled: bool = true
|
||||
# Variable containing the saves folder obtained from Project Settings
|
||||
var save_folder: String
|
||||
|
||||
# Filename of the latest crash savegame file
|
||||
var crash_savegame_filename: String
|
||||
|
||||
# Template for savegames filenames
|
||||
const SAVE_NAME_TEMPLATE: String = "save_%03d.tres"
|
||||
|
||||
# Template for crash savegames filenames
|
||||
const CRASH_SAVE_NAME_TEMPLATE: String = "crash_autosave_%s_%s.tres"
|
||||
|
||||
# Variable containing the settings folder obtained from Project Settings
|
||||
var settings_folder: String
|
||||
|
||||
@@ -74,7 +80,61 @@ func save_game(id: int, p_savename: String):
|
||||
["Save requested while saving is not possible. Save canceled."])
|
||||
return
|
||||
|
||||
var save_game := ESCSaveGame.new()
|
||||
var save_game := _do_save_game(p_savename)
|
||||
|
||||
var directory: Directory = Directory.new()
|
||||
if not directory.dir_exists(save_folder):
|
||||
directory.make_dir_recursive(save_folder)
|
||||
|
||||
var save_path = save_folder.plus_file(SAVE_NAME_TEMPLATE % id)
|
||||
var error: int = ResourceSaver.save(save_path, save_game)
|
||||
if error != OK:
|
||||
escoria.logger.report_errors(
|
||||
"esc_save_data_resources.gd",
|
||||
["There was an issue writing the save %s to %s" % [id, save_path]]
|
||||
)
|
||||
|
||||
|
||||
# Performs an emergency savegame in case of crash.
|
||||
func save_game_crash():
|
||||
var datetime = OS.get_datetime()
|
||||
var datetime_string = "%02d/%02d/%02d %02d:%02d" % [
|
||||
datetime["day"],
|
||||
datetime["month"],
|
||||
datetime["year"],
|
||||
datetime["hour"],
|
||||
datetime["minute"],
|
||||
]
|
||||
|
||||
var save_game := _do_save_game("Crash %s" % datetime_string)
|
||||
|
||||
var save_file_path: String = ProjectSettings.get_setting(
|
||||
"escoria/debug/log_file_path"
|
||||
)
|
||||
crash_savegame_filename = save_file_path.plus_file(
|
||||
CRASH_SAVE_NAME_TEMPLATE % [
|
||||
str(datetime["year"]) + str(datetime["month"])
|
||||
+ str(datetime["day"]),
|
||||
str(datetime["hour"]) + str(datetime["minute"])
|
||||
+ str(datetime["second"])
|
||||
]
|
||||
)
|
||||
|
||||
var error: int = ResourceSaver.save(crash_savegame_filename, save_game)
|
||||
if error != OK:
|
||||
escoria.logger.report_errors(
|
||||
"esc_save_data_resources.gd",
|
||||
["There was an issue writing the crash save to %s"
|
||||
% crash_savegame_filename])
|
||||
return error
|
||||
|
||||
|
||||
# Actual savegame function.
|
||||
#
|
||||
# ## Parameters
|
||||
# - p_savename: name of the savegame
|
||||
func _do_save_game(p_savename: String) -> ESCSaveGame:
|
||||
var save_game = ESCSaveGame.new()
|
||||
save_game.escoria_version = escoria.ESCORIA_VERSION
|
||||
save_game.game_version = ProjectSettings.get_setting(
|
||||
"escoria/main/game_version"
|
||||
@@ -94,17 +154,8 @@ func save_game(id: int, p_savename: String):
|
||||
escoria.globals_manager.save_game(save_game)
|
||||
escoria.object_manager.save_game(save_game)
|
||||
escoria.main.save_game(save_game)
|
||||
return save_game
|
||||
|
||||
var directory: Directory = Directory.new()
|
||||
if not directory.dir_exists(save_folder):
|
||||
directory.make_dir_recursive(save_folder)
|
||||
|
||||
var save_path = save_folder.plus_file(SAVE_NAME_TEMPLATE % id)
|
||||
var error: int = ResourceSaver.save(save_path, save_game)
|
||||
if error != OK:
|
||||
escoria.logger.report_errors(
|
||||
"esc_save_data_resources.gd",
|
||||
["There was an issue writing the save %s to %s" % [id, save_path]])
|
||||
|
||||
# Load a savegame file from its id.
|
||||
#
|
||||
|
||||
@@ -134,6 +134,16 @@ func _ready():
|
||||
)
|
||||
|
||||
|
||||
func _notification(what):
|
||||
match what:
|
||||
MainLoop.NOTIFICATION_WM_QUIT_REQUEST:
|
||||
escoria.logger.close_logs()
|
||||
get_tree().quit()
|
||||
MainLoop.NOTIFICATION_WM_GO_BACK_REQUEST:
|
||||
escoria.logger.close_logs()
|
||||
get_tree().quit()
|
||||
|
||||
|
||||
# Called by Escoria's main_scene as very very first event EVER.
|
||||
# Usually you'll want to show some logos animations before spawning the main
|
||||
# menu in the escoria/main/game_start_script 's :init event
|
||||
@@ -339,3 +349,8 @@ func run_event_from_script(script: ESCScript, event_name: String):
|
||||
[]
|
||||
)
|
||||
return
|
||||
|
||||
|
||||
# Function called to quit the game.
|
||||
func quit():
|
||||
get_tree().notification(MainLoop.NOTIFICATION_WM_QUIT_REQUEST)
|
||||
|
||||
@@ -211,6 +211,30 @@ func set_escoria_debug_settings():
|
||||
}
|
||||
ProjectSettings.add_property_info(property_info)
|
||||
|
||||
# Define output log file path
|
||||
if not ProjectSettings.has_setting("escoria/debug/log_file_path"):
|
||||
ProjectSettings.set_setting("escoria/debug/log_file_path", "user://")
|
||||
var property_info = {
|
||||
"name": "escoria/debug/log_file_path",
|
||||
"type": TYPE_STRING,
|
||||
"hint": PROPERTY_HINT_DIR
|
||||
}
|
||||
ProjectSettings.add_property_info(property_info)
|
||||
|
||||
# Define crash message
|
||||
if not ProjectSettings.has_setting("escoria/debug/crash_message"):
|
||||
ProjectSettings.set_setting(
|
||||
"escoria/debug/crash_message",
|
||||
"We're sorry, but the game crashed. Please send us the " +
|
||||
"following files:\n"
|
||||
)
|
||||
var property_info = {
|
||||
"name": "escoria/debug/crash_message",
|
||||
"type": TYPE_STRING,
|
||||
"hint": PROPERTY_HINT_MULTILINE_TEXT
|
||||
}
|
||||
ProjectSettings.add_property_info(property_info)
|
||||
|
||||
# Room selector preference
|
||||
if not ProjectSettings.has_setting("escoria/debug/enable_room_selector"):
|
||||
ProjectSettings.set_setting("escoria/debug/enable_room_selector", false)
|
||||
|
||||
@@ -22,7 +22,7 @@ func _on_options_pressed():
|
||||
|
||||
# Quit the game
|
||||
func _on_quit_pressed():
|
||||
get_tree().quit()
|
||||
escoria.quit()
|
||||
|
||||
|
||||
# Hide the options panel again
|
||||
|
||||
@@ -28,7 +28,7 @@ func _on_load_game_pressed():
|
||||
|
||||
# Quit the game
|
||||
func _on_quit_pressed():
|
||||
get_tree().quit()
|
||||
escoria.quit()
|
||||
|
||||
|
||||
# Hide the save slots again
|
||||
|
||||
Reference in New Issue
Block a user