setup_log.gd 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209
  1. class_name ModLoaderSetupLog
  2. # Slimed down version of ModLoaderLog for the ModLoader Self Setup
  3. const MOD_LOG_PATH := "user://logs/modloader.log"
  4. enum VERBOSITY_LEVEL {
  5. ERROR,
  6. WARNING,
  7. INFO,
  8. DEBUG,
  9. }
  10. class ModLoaderLogEntry:
  11. extends Resource
  12. var mod_name: String
  13. var message: String
  14. var type: String
  15. var time: String
  16. func _init(_mod_name: String, _message: String, _type: String, _time: String) -> void:
  17. mod_name = _mod_name
  18. message = _message
  19. type = _type
  20. time = _time
  21. func get_entry() -> String:
  22. return time + get_prefix() + message
  23. func get_prefix() -> String:
  24. return "%s %s: " % [type.to_upper(), mod_name]
  25. func get_md5() -> String:
  26. return str(get_prefix(), message).md5_text()
  27. # API log functions
  28. # =============================================================================
  29. # Logs the error in red and a stack trace. Prefixed FATAL-ERROR
  30. # Stops the execution in editor
  31. # Always logged
  32. static func fatal(message: String, mod_name: String) -> void:
  33. _log(message, mod_name, "fatal-error")
  34. # Logs the message and pushed an error. Prefixed ERROR
  35. # Always logged
  36. static func error(message: String, mod_name: String) -> void:
  37. _log(message, mod_name, "error")
  38. # Logs the message and pushes a warning. Prefixed WARNING
  39. # Logged with verbosity level at or above warning (-v)
  40. static func warning(message: String, mod_name: String) -> void:
  41. _log(message, mod_name, "warning")
  42. # Logs the message. Prefixed INFO
  43. # Logged with verbosity level at or above info (-vv)
  44. static func info(message: String, mod_name: String) -> void:
  45. _log(message, mod_name, "info")
  46. # Logs the message. Prefixed SUCCESS
  47. # Logged with verbosity level at or above info (-vv)
  48. static func success(message: String, mod_name: String) -> void:
  49. _log(message, mod_name, "success")
  50. # Logs the message. Prefixed DEBUG
  51. # Logged with verbosity level at or above debug (-vvv)
  52. static func debug(message: String, mod_name: String) -> void:
  53. _log(message, mod_name, "debug")
  54. # Logs the message formatted with [method JSON.print]. Prefixed DEBUG
  55. # Logged with verbosity level at or above debug (-vvv)
  56. static func debug_json_print(message: String, json_printable, mod_name: String) -> void:
  57. message = "%s\n%s" % [message, JSON.stringify(json_printable, " ")]
  58. _log(message, mod_name, "debug")
  59. # Internal log functions
  60. # =============================================================================
  61. static func _log(message: String, mod_name: String, log_type: String = "info") -> void:
  62. var time := "%s " % _get_time_string()
  63. var log_entry := ModLoaderLogEntry.new(mod_name, message, log_type, time)
  64. match log_type.to_lower():
  65. "fatal-error":
  66. push_error(message)
  67. _write_to_log_file(log_entry.get_entry())
  68. _write_to_log_file(JSON.stringify(get_stack(), " "))
  69. assert(false, message)
  70. "error":
  71. printerr(message)
  72. push_error(message)
  73. _write_to_log_file(log_entry.get_entry())
  74. "warning":
  75. print(log_entry.get_prefix() + message)
  76. push_warning(message)
  77. _write_to_log_file(log_entry.get_entry())
  78. "info", "success":
  79. print(log_entry.get_prefix() + message)
  80. _write_to_log_file(log_entry.get_entry())
  81. "debug":
  82. print(log_entry.get_prefix() + message)
  83. _write_to_log_file(log_entry.get_entry())
  84. # Internal Date Time
  85. # =============================================================================
  86. # Returns the current time as a string in the format hh:mm:ss
  87. static func _get_time_string() -> String:
  88. var date_time := Time.get_datetime_dict_from_system()
  89. return "%02d:%02d:%02d" % [ date_time.hour, date_time.minute, date_time.second ]
  90. # Returns the current date as a string in the format yyyy-mm-dd
  91. static func _get_date_string() -> String:
  92. var date_time := Time.get_datetime_dict_from_system()
  93. return "%s-%02d-%02d" % [ date_time.year, date_time.month, date_time.day ]
  94. # Returns the current date and time as a string in the format yyyy-mm-dd_hh:mm:ss
  95. static func _get_date_time_string() -> String:
  96. return "%s_%s" % [ _get_date_string(), _get_time_string() ]
  97. # Internal File
  98. # =============================================================================
  99. static func _write_to_log_file(string_to_write: String) -> void:
  100. if not FileAccess.file_exists(MOD_LOG_PATH):
  101. _rotate_log_file()
  102. var log_file := FileAccess.open(MOD_LOG_PATH, FileAccess.READ_WRITE)
  103. if log_file == null:
  104. assert(false, "Could not open log file, error code: %s" % error)
  105. return
  106. log_file.seek_end()
  107. log_file.store_string("\n" + string_to_write)
  108. log_file.close()
  109. # Keeps log backups for every run, just like the Godot; gdscript implementation of
  110. # https://github.com/godotengine/godot/blob/1d14c054a12dacdc193b589e4afb0ef319ee2aae/core/io/logger.cpp#L151
  111. static func _rotate_log_file() -> void:
  112. var MAX_LOGS: int = ProjectSettings.get_setting("debug/file_logging/max_log_files")
  113. if FileAccess.file_exists(MOD_LOG_PATH):
  114. if MAX_LOGS > 1:
  115. var datetime := _get_date_time_string().replace(":", ".")
  116. var backup_name: String = MOD_LOG_PATH.get_basename() + "_" + datetime
  117. if MOD_LOG_PATH.get_extension().length() > 0:
  118. backup_name += "." + MOD_LOG_PATH.get_extension()
  119. var dir := DirAccess.open(MOD_LOG_PATH.get_base_dir())
  120. if not dir == null:
  121. dir.copy(MOD_LOG_PATH, backup_name)
  122. _clear_old_log_backups()
  123. # only File.WRITE creates a new file, File.READ_WRITE throws an error
  124. var log_file := FileAccess.open(MOD_LOG_PATH, FileAccess.WRITE)
  125. if log_file == null:
  126. assert(false, "Could not open log file, error code: %s" % error)
  127. log_file.store_string('%s Created log' % _get_date_string())
  128. log_file.close()
  129. static func _clear_old_log_backups() -> void:
  130. var MAX_LOGS := int(ProjectSettings.get_setting("debug/file_logging/max_log_files"))
  131. var MAX_BACKUPS := MAX_LOGS - 1 # -1 for the current new log (not a backup)
  132. var basename := MOD_LOG_PATH.get_file().get_basename() as String
  133. var extension := MOD_LOG_PATH.get_extension() as String
  134. var dir := DirAccess.open(MOD_LOG_PATH.get_base_dir())
  135. if dir == null:
  136. return
  137. dir.list_dir_begin() # TODOGODOT4 fill missing arguments https://github.com/godotengine/godot/pull/40547
  138. var file := dir.get_next()
  139. var backups := []
  140. while file.length() > 0:
  141. if (not dir.current_is_dir() and
  142. file.begins_with(basename) and
  143. file.get_extension() == extension and
  144. not file == MOD_LOG_PATH.get_file()):
  145. backups.append(file)
  146. file = dir.get_next()
  147. dir.list_dir_end()
  148. if backups.size() > MAX_BACKUPS:
  149. backups.sort()
  150. backups.resize(backups.size() - MAX_BACKUPS)
  151. for file_to_delete in backups:
  152. dir.remove(file_to_delete)