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.