From 30253bc035d6d6592ea7343fc7a1f05a55c7674a Mon Sep 17 00:00:00 2001 From: Jasmine Stephens Date: Wed, 1 May 2024 21:40:22 -0700 Subject: [PATCH] Adding new LogHelper class to improve logging This adds a new LogHelper class to improve logging within the game. It wraps Foster's Logger, but now will add extra "[Warning]" and "[Error]" strings to warnings and errors to better highlight those areas. when logging to console, it will also highlight them in yellow or red. The WriteToLog and OpenLog functions were also moved to the LogHelper class to centralize things. It also includes a new error wrapper. Small note: Unfortunately, the Error and Warnings don't work entirely correctly due to a bug with Foster, where some Error and Warning function overloads were pointing to the wrong functions. This has already been fixed, but is not in a release yet. This should get fixed automatically next time Foster pushes a new release, and we update to use it. For now, if people would like to use this, they should also be able to call LogHelper.Error instead of Log.Error, which should give the proper handling. https://github.com/FosterFramework/Foster/commit/8ad29878ff9260168bfb0a54522175ef0e836f9b https://github.com/FosterFramework/Foster/commit/822fa8c8a5e02c4ac85a63d38983ce9bd7fb6f37 --- Celeste64.Launcher/Program.cs | 7 +- Source/Data/Assets.cs | 2 +- Source/Data/Map.cs | 3 +- Source/Data/PersistedData/PersistedData.cs | 2 +- .../PersistedData/VersionedPersistedData.cs | 2 +- Source/Game.cs | 60 +--------- Source/Helpers/LogHelper.cs | 113 ++++++++++++++++++ Source/Mod/Core/GameMod.cs | 6 +- Source/Mod/Core/ModLoader.cs | 3 +- Source/Mod/Helpers/SkinInfo.cs | 2 +- Source/Mod/Menu/ModSelectionMenu.cs | 3 +- Source/Scenes/GameErrorMessage.cs | 3 +- Source/Scenes/World.cs | 6 +- 13 files changed, 132 insertions(+), 80 deletions(-) create mode 100644 Source/Helpers/LogHelper.cs diff --git a/Celeste64.Launcher/Program.cs b/Celeste64.Launcher/Program.cs index 78500c31..b4e28e42 100644 --- a/Celeste64.Launcher/Program.cs +++ b/Celeste64.Launcher/Program.cs @@ -19,7 +19,12 @@ public static void Main(string[] args) Version loaderVersion = typeof(Program).Assembly.GetName().Version!; Game.LoaderVersion = $"Fuji: v.{loaderVersion.Major}.{loaderVersion.Minor}.{loaderVersion.Build}"; Game.IsDynamicRes = parsedArgs.Has("dynamic-res"); - Game.AppArgs = parsedArgs; // Expose our parsed args to the game + + // Expose our parsed args to the game + Game.AppArgs = parsedArgs; + + LogHelper.Initialize(); + if (!string.IsNullOrEmpty(BuildProperties.ModVersion())) { Game.LoaderVersion += "-" + BuildProperties.ModVersion(); diff --git a/Source/Data/Assets.cs b/Source/Data/Assets.cs index 8e428c47..c840fb60 100644 --- a/Source/Data/Assets.cs +++ b/Source/Data/Assets.cs @@ -118,7 +118,7 @@ internal static void LoadAssetsForMod(GameMod mod) if (modFs == null) { - Log.Error($"Error loading assets for {mod.ModInfo.Id}. Mod FileSystem not initialized."); + Log.Error($"Failed to load assets for {mod.ModInfo.Id}. Mod FileSystem not initialized."); return; } diff --git a/Source/Data/Map.cs b/Source/Data/Map.cs index f2829ad9..6d01e1f9 100644 --- a/Source/Data/Map.cs +++ b/Source/Data/Map.cs @@ -154,8 +154,7 @@ public Map(string name, string virtPath, Stream stream) readExceptionMessage = e.Message; - Log.Error($"Failed to load map {name}, more details below."); - Log.Error(e.ToString()); + LogHelper.Error($"Failed to load map {name}", e); } if (Data != null) diff --git a/Source/Data/PersistedData/PersistedData.cs b/Source/Data/PersistedData/PersistedData.cs index cc335c73..e82dfdc9 100644 --- a/Source/Data/PersistedData/PersistedData.cs +++ b/Source/Data/PersistedData/PersistedData.cs @@ -24,7 +24,7 @@ public virtual void Serialize(Utf8JsonWriter writer, object instance) } catch (Exception e) { - Log.Error(e.ToString()); + LogHelper.Error("Failed to load persisted data", e); return null; } } diff --git a/Source/Data/PersistedData/VersionedPersistedData.cs b/Source/Data/PersistedData/VersionedPersistedData.cs index 1a4b0d09..040594f0 100644 --- a/Source/Data/PersistedData/VersionedPersistedData.cs +++ b/Source/Data/PersistedData/VersionedPersistedData.cs @@ -24,7 +24,7 @@ namespace Celeste64; } catch (Exception e) { - Log.Error(e.ToString()); + LogHelper.Error("Failed to load versioned persisted data", e); return null; } } diff --git a/Source/Game.cs b/Source/Game.cs index abdefc7e..70a743d2 100644 --- a/Source/Game.cs +++ b/Source/Game.cs @@ -237,7 +237,6 @@ public override void Shutdown() instance = null; Log.Info("Shutting down..."); - WriteToLog(); } public bool IsMidTransition => transitionStep != TransitionStep.None; @@ -284,65 +283,10 @@ private void HandleError(Exception e) } scenes.Clear(); - Log.Error("== ERROR ==\n\n" + e.ToString()); - WriteToLog(); + LogHelper.Error("An Unhandled Exception occurred: ", e); UnsafelySetScene(new GameErrorMessage(e)); } - // Fuji Custom - public static void WriteToLog() - { - if (!Settings.WriteLog) - { - return; - } - - // construct a log message - const string LogFileName = "Log.txt"; - StringBuilder log = new(); - lock (Log.Logs) - log.AppendLine(Log.Logs.ToString()); - - // write to file - string path = LogFileName; - { - if (App.Running) - { - try - { - path = Path.Join(App.UserPath, LogFileName); - } - catch - { - path = LogFileName; - } - } - - File.WriteAllText(path, log.ToString()); - } - } - - internal static void OpenLog() - { - const string LogFileName = "Log.txt"; - string path = ""; - if (App.Running) - { - try - { - path = Path.Join(App.UserPath, LogFileName); - } - catch - { - path = LogFileName; - } - } - if (File.Exists(path)) - { - new Process { StartInfo = new ProcessStartInfo(path) { UseShellExecute = true } }.Start(); - } - } - internal void ReloadAssets(bool reloadAll) { if (!scenes.TryPeek(out var scene)) @@ -629,8 +573,6 @@ public override void Update() // in case new music was played Settings.SyncSettings(); transitionStep = TransitionStep.FadeIn; - - WriteToLog(); } else if (transitionStep == TransitionStep.FadeIn) { diff --git a/Source/Helpers/LogHelper.cs b/Source/Helpers/LogHelper.cs new file mode 100644 index 00000000..958e08e8 --- /dev/null +++ b/Source/Helpers/LogHelper.cs @@ -0,0 +1,113 @@ +using System.Diagnostics; +using System.Text; +namespace Celeste64; + +/// +/// Fuji Custom +/// This class improves logging functionality by better distinguishing between info messages, warnings, and logs +/// It also provides functions for writing logs to the log file, and opening the log file +/// This wraps fosters Log events by subscribing to the OnInfo, OnWarn, and OnError events +/// +public static class LogHelper +{ + public static readonly StringBuilder Logs = new StringBuilder(); + + public static void Initialize() + { + Log.OnInfo += Info; + Log.OnWarn += Warn; + Log.OnError += Error; + } + + public static void Info(ReadOnlySpan text) + { + Append(text); + Console.Out.WriteLine(text); + WriteToLog(); + } + + public static void Warn(ReadOnlySpan text) + { + Append($"[Warning] {text}"); + Console.ForegroundColor = ConsoleColor.Yellow; + Console.Out.WriteLine($"[Warning] {text}"); + Console.ResetColor(); + WriteToLog(); + } + + public static void Error(ReadOnlySpan text) + { + Append($"[Error] {text}"); + Console.ForegroundColor = ConsoleColor.Red; + Console.Out.WriteLine($"[Error] {text}"); + Console.ResetColor(); + WriteToLog(); + } + + public static void Error(ReadOnlySpan text, Exception ex) + { + Error($"{text}\n {ex}"); + } + + public static void WriteToLog() + { + if (!Settings.WriteLog) + { + return; + } + + // construct a log message + const string LogFileName = "Log.txt"; + StringBuilder log = new(); + lock (Logs) + log.AppendLine(Logs.ToString()); + + // write to file + string path = LogFileName; + { + if (App.Running) + { + try + { + path = Path.Join(App.UserPath, LogFileName); + } + catch + { + path = LogFileName; + } + } + + File.WriteAllText(path, log.ToString()); + } + } + + public static void OpenLog() + { + const string LogFileName = "Log.txt"; + string path = ""; + if (App.Running) + { + try + { + path = Path.Join(App.UserPath, LogFileName); + } + catch + { + path = LogFileName; + } + } + if (File.Exists(path)) + { + new Process { StartInfo = new ProcessStartInfo(path) { UseShellExecute = true } }.Start(); + } + } + + public static void Append(ReadOnlySpan message) + { + lock (Logs) + { + Logs.Append(message); + Logs.Append('\n'); + } + } +} diff --git a/Source/Mod/Core/GameMod.cs b/Source/Mod/Core/GameMod.cs index 3b263120..ab56494b 100644 --- a/Source/Mod/Core/GameMod.cs +++ b/Source/Mod/Core/GameMod.cs @@ -160,8 +160,7 @@ public bool SaveSettings() } catch (Exception e) { - Log.Error($"Failed to save the settings of {ModInfo.Id}!"); - Log.Error(e.Message); + LogHelper.Error($"Failed to save the settings of {ModInfo.Id}!", e); return false; } } @@ -219,8 +218,7 @@ public bool LoadSettings() } catch (Exception e) { - Log.Error($"Failed to save the settings of {ModInfo.Id}!"); - Log.Error(e.Message); + LogHelper.Error($"Failed to save the settings of {ModInfo.Id}!", e); return false; } } diff --git a/Source/Mod/Core/ModLoader.cs b/Source/Mod/Core/ModLoader.cs index 43e94bff..216d00e9 100644 --- a/Source/Mod/Core/ModLoader.cs +++ b/Source/Mod/Core/ModLoader.cs @@ -254,8 +254,7 @@ internal static bool Load(ModInfo info, IModFilesystem fs) catch (Exception ex) { FailedToLoadMods.Add(info.Id); - Log.Error($"Fuji Error: An error occurred while trying to load mod: {info.Id}"); - Log.Error(ex.ToString()); + LogHelper.Error($"Fuji Error: An error occurred while trying to load mod: {info.Id}", ex); return false; } diff --git a/Source/Mod/Helpers/SkinInfo.cs b/Source/Mod/Helpers/SkinInfo.cs index 0dc9948b..a9ce2e29 100644 --- a/Source/Mod/Helpers/SkinInfo.cs +++ b/Source/Mod/Helpers/SkinInfo.cs @@ -56,7 +56,7 @@ public override int Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSeri } catch (Exception ex) { - Log.Error("Error: Could not parse value in skin file: " + ex.ToString()); + LogHelper.Error("Error: Could not parse value in skin file: ", ex); return 0; } } diff --git a/Source/Mod/Menu/ModSelectionMenu.cs b/Source/Mod/Menu/ModSelectionMenu.cs index d3237092..bcbddb55 100644 --- a/Source/Mod/Menu/ModSelectionMenu.cs +++ b/Source/Mod/Menu/ModSelectionMenu.cs @@ -47,8 +47,7 @@ internal ModSelectionMenu(Menu? rootMenu) })); FailedToLoadModsMenu.Add(new Option("FujiOpenLogFile", () => { - Game.WriteToLog(); - Game.OpenLog(); + LogHelper.OpenLog(); })); } diff --git a/Source/Scenes/GameErrorMessage.cs b/Source/Scenes/GameErrorMessage.cs index ae6e417f..23952158 100644 --- a/Source/Scenes/GameErrorMessage.cs +++ b/Source/Scenes/GameErrorMessage.cs @@ -12,8 +12,7 @@ public GameErrorMessage(Exception e) menu.Add(new Menu.Option("FujiOpenLogFile", () => { - Game.WriteToLog(); - Game.OpenLog(); + LogHelper.OpenLog(); })); menu.Add(new Menu.Option("QuitToMainMenu", () => diff --git a/Source/Scenes/World.cs b/Source/Scenes/World.cs index 602fa37a..89b9d9cc 100644 --- a/Source/Scenes/World.cs +++ b/Source/Scenes/World.cs @@ -88,8 +88,7 @@ public World(EntryInfo entry) badMapWarningMenu.Add(new Menu.Option("FujiOpenLogFile", () => { - Game.WriteToLog(); - Game.OpenLog(); + LogHelper.OpenLog(); })); badMapWarningMenu.Add(new Menu.Option("QuitToMainMenu", () => Game.Instance.Goto(new Transition() @@ -522,8 +521,7 @@ public override void Update() catch (Exception err) { string currentModName = ModManager.Instance.CurrentLevelMod != null && ModManager.Instance.CurrentLevelMod.ModInfo != null ? ModManager.Instance.CurrentLevelMod.ModInfo.Id : "unknown"; - Log.Error($"--- ERROR in the map {currentModName}:{Entry.Map}. More details below ---"); - Log.Error(err.ToString()); + LogHelper.Error($"--- ERROR in the map {currentModName}:{Entry.Map}. More details below ---", err); Panic(err, $"Oops, critical error :(\n{err.Message}\nYou can try to recover from this error by pressing Retry,\nbut we can't promise stability!", Panicked); } // We wrap most of Update() in a try-catch to hopefully catch errors that occur during gameplay.