From 441c41543cb43885c15004d8ebe07d763948835d Mon Sep 17 00:00:00 2001 From: Stone_Red <56473591+Stone-Red-Code@users.noreply.github.com> Date: Tue, 30 Apr 2024 18:21:55 +0200 Subject: [PATCH] Add logging --- src/Markdowser/App.axaml.cs | 21 ++++++++++ src/Markdowser/Commands/HyperlinkCommand.cs | 5 +-- src/Markdowser/Markdowser.csproj | 2 + src/Markdowser/Models/Settings.cs | 11 +++++- .../Processing/Processors/HtmlProcessor.cs | 6 +-- src/Markdowser/Program.cs | 32 +++++++++++++++- src/Markdowser/Utilities/GlobalState.cs | 38 +++++++++++++++++++ src/Markdowser/Utilities/HttpPathResolver.cs | 3 +- .../ViewModels/ContentViewModelBase.cs | 8 +++- .../ViewModels/MainWindowViewModel.cs | 11 ++++-- src/Markdowser/ViewModels/ViewModelBase.cs | 5 +++ 11 files changed, 127 insertions(+), 15 deletions(-) diff --git a/src/Markdowser/App.axaml.cs b/src/Markdowser/App.axaml.cs index 1249b3b..9a29aca 100644 --- a/src/Markdowser/App.axaml.cs +++ b/src/Markdowser/App.axaml.cs @@ -3,10 +3,15 @@ using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Markup.Xaml; using Avalonia.Styling; +using CuteUtils.Logging; + using Markdowser.Models; +using Markdowser.Utilities; using Markdowser.ViewModels; using Markdowser.Views; +using System.IO; + namespace Markdowser; public partial class App : Application @@ -18,6 +23,11 @@ public partial class App : Application public override void OnFrameworkInitializationCompleted() { + if (!Directory.Exists(Configuration.ApplicationDataPath)) + { + _ = Directory.CreateDirectory(Configuration.ApplicationDataPath); + } + if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) { desktop.MainWindow = new MainWindow @@ -25,12 +35,23 @@ public partial class App : Application DataContext = new MainWindowViewModel(), }; + // Clear log files + GlobalState.Logger.ClearLogFile(LogSeverity.Debug); + GlobalState.Logger.ClearLogFile(LogSeverity.Info); + GlobalState.Logger.ClearLogFile(LogSeverity.Warn); + GlobalState.Logger.ClearLogFile(LogSeverity.Error); + GlobalState.Logger.ClearLogFile(LogSeverity.Fatal); + desktop.MainWindow.Closing += (sender, e) => { + GlobalState.Logger.LogInfo("Saving settings..."); Settings.SaveSettings(); + GlobalState.Logger.LogInfo("Settings saved."); }; } + GlobalState.Logger.LogInfo("Application started."); + base.OnFrameworkInitializationCompleted(); if (Current is not null) diff --git a/src/Markdowser/Commands/HyperlinkCommand.cs b/src/Markdowser/Commands/HyperlinkCommand.cs index 6c97b5e..c2a10d8 100644 --- a/src/Markdowser/Commands/HyperlinkCommand.cs +++ b/src/Markdowser/Commands/HyperlinkCommand.cs @@ -1,7 +1,6 @@ using Markdowser.Utilities; using System; -using System.Diagnostics; using System.Windows.Input; namespace Markdowser.Commands; @@ -23,7 +22,7 @@ public class HyperlinkCommand : ICommand { if (parameter is string url) { - Debug.WriteLine($"Hyperlink clicked: {url}"); + GlobalState.Logger.LogDebug($"Hyperlink clicked: {url}"); if (Uri.IsWellFormedUriString(url, UriKind.Absolute)) { @@ -35,7 +34,7 @@ public class HyperlinkCommand : ICommand } else { - Debug.WriteLine($"Invalid URL: {url}"); + GlobalState.Logger.LogDebug($"Invalid URL: {url}"); } } } diff --git a/src/Markdowser/Markdowser.csproj b/src/Markdowser/Markdowser.csproj index 24b1d1b..3c4970c 100644 --- a/src/Markdowser/Markdowser.csproj +++ b/src/Markdowser/Markdowser.csproj @@ -30,12 +30,14 @@ + + diff --git a/src/Markdowser/Models/Settings.cs b/src/Markdowser/Models/Settings.cs index 1188d85..0dcb68e 100644 --- a/src/Markdowser/Models/Settings.cs +++ b/src/Markdowser/Models/Settings.cs @@ -1,5 +1,6 @@ using Markdowser.Utilities; +using System; using System.IO; using System.Reflection; using System.Text.Json; @@ -32,7 +33,15 @@ public class Settings { if (File.Exists(Configuration.SettingsFilePath)) { - return JsonSerializer.Deserialize(File.ReadAllText(Configuration.SettingsFilePath)) ?? new Settings(); + try + { + return JsonSerializer.Deserialize(File.ReadAllText(Configuration.SettingsFilePath)) ?? new Settings(); + } + catch (Exception ex) + { + GlobalState.Logger.LogWarn(ex.Message); + return new Settings(); + } } else { diff --git a/src/Markdowser/Processing/Processors/HtmlProcessor.cs b/src/Markdowser/Processing/Processors/HtmlProcessor.cs index 1de3f72..edaa421 100644 --- a/src/Markdowser/Processing/Processors/HtmlProcessor.cs +++ b/src/Markdowser/Processing/Processors/HtmlProcessor.cs @@ -1,10 +1,10 @@ using HtmlAgilityPack; +using Markdowser.Utilities; using Markdowser.ViewModels; using Markdowser.ViewModels.Content; using System; -using System.Diagnostics; using System.IO; using System.Linq; using System.Net.Http; @@ -56,11 +56,11 @@ internal partial class HtmlProcessor : IContentProcessor htmlDoc.LoadHtml(html.ToString()); string title = htmlDoc.DocumentNode.SelectSingleNode("html/head/title")?.InnerText?.Trim() ?? httpResponseMessage.RequestMessage?.RequestUri?.ToString() ?? "Untitled"; - Debug.WriteLine("Converting HTML to markdown..."); + GlobalState.Logger.LogDebug("Converting HTML to markdown..."); string markdown = markdownConverter.Convert(html.ToString()); - Debug.WriteLine("Processing markdown..."); + GlobalState.Logger.LogDebug("Processing markdown..."); int currentLine = 0; diff --git a/src/Markdowser/Program.cs b/src/Markdowser/Program.cs index 12b002f..9564c36 100644 --- a/src/Markdowser/Program.cs +++ b/src/Markdowser/Program.cs @@ -1,11 +1,17 @@ using Avalonia; using Avalonia.ReactiveUI; +using Markdowser.Utilities; + using Projektanker.Icons.Avalonia; using Projektanker.Icons.Avalonia.FontAwesome; using Projektanker.Icons.Avalonia.MaterialDesign; +using ReactiveUI; + using System; +using System.Reactive; +using System.Threading.Tasks; namespace Markdowser; @@ -17,8 +23,30 @@ internal static class Program [STAThread] public static void Main(string[] args) { - _ = BuildAvaloniaApp() - .StartWithClassicDesktopLifetime(args); + AppDomain.CurrentDomain.UnhandledException += (sender, e) => + { + GlobalState.Logger.LogFatal(e.ExceptionObject.ToString() ?? "Unknown error."); + }; + + TaskScheduler.UnobservedTaskException += (sender, e) => + { + GlobalState.Logger.LogFatal(e.Exception.ToString()); + }; + + RxApp.DefaultExceptionHandler = Observer.Create(ex => + { + GlobalState.Logger.LogFatal(ex.ToString()); + }); + + try + { + _ = BuildAvaloniaApp() + .StartWithClassicDesktopLifetime(args); + } + catch (Exception ex) + { + GlobalState.Logger.LogFatal(ex.ToString()); + } } // Avalonia configuration, don't remove; also used by visual designer. diff --git a/src/Markdowser/Utilities/GlobalState.cs b/src/Markdowser/Utilities/GlobalState.cs index 856fb75..38cc0f6 100644 --- a/src/Markdowser/Utilities/GlobalState.cs +++ b/src/Markdowser/Utilities/GlobalState.cs @@ -1,5 +1,7 @@ using Avalonia.Controls; +using CuteUtils.Logging; + using System; using System.Collections.Generic; using System.Collections.ObjectModel; @@ -16,6 +18,42 @@ internal static class GlobalState private static string url = string.Empty; + public static Logger Logger { get; } = new() + { + Config = new() + { + DebugConfig = new() + { + LogTarget = LogTarget.DebugConsole + }, + InfoConfig = new() + { + LogTarget = LogTarget.DebugConsole | LogTarget.File, + FilePath = Configuration.LogFilePath + }, + WarnConfig = new() + { + LogTarget = LogTarget.DebugConsole | LogTarget.File, + FilePath = Configuration.LogFilePath + }, + ErrorConfig = new() + { + LogTarget = LogTarget.DebugConsole | LogTarget.File, + FilePath = Configuration.LogFilePath + }, + FatalConfig = new() + { + LogTarget = LogTarget.DebugConsole | LogTarget.File, + FilePath = Configuration.LogFilePath + }, + FormatConfig = new() + { + DebugConsoleFormat = new LogFormatBuilder().DateTime().Text(" ").LogSeverity(padding: -6).FilePath().Text(":").MemberName().Text(":").LineNumber().Text(Environment.NewLine).Message(), + FileFormat = new LogFormatBuilder().DateTime().Text(" ").LogSeverity(padding: -6).FilePath().Text(":").MemberName().Text(":").LineNumber().Text(Environment.NewLine).Message(), + } + } + }; + public static Stack BackHistory { get; } = new(); public static Stack ForwardHistory { get; } = new(); diff --git a/src/Markdowser/Utilities/HttpPathResolver.cs b/src/Markdowser/Utilities/HttpPathResolver.cs index d959dff..0761c35 100644 --- a/src/Markdowser/Utilities/HttpPathResolver.cs +++ b/src/Markdowser/Utilities/HttpPathResolver.cs @@ -10,7 +10,6 @@ using Markdowser.Models; using System; using System.Collections.Generic; -using System.Diagnostics; using System.IO; using System.Net.Http; using System.Threading.Tasks; @@ -40,7 +39,7 @@ public class HttpPathResolver : IPathResolver relativeOrAbsolutePath = new Uri(new Uri(GlobalState.Url), relativeOrAbsolutePath).ToString(); } - Debug.WriteLine($"Resolving image: {relativeOrAbsolutePath}"); + GlobalState.Logger.LogDebug($"Resolving image: {relativeOrAbsolutePath}"); HttpResponseMessage httpResponseMessage; diff --git a/src/Markdowser/ViewModels/ContentViewModelBase.cs b/src/Markdowser/ViewModels/ContentViewModelBase.cs index 95f0edf..13e720a 100644 --- a/src/Markdowser/ViewModels/ContentViewModelBase.cs +++ b/src/Markdowser/ViewModels/ContentViewModelBase.cs @@ -1,6 +1,12 @@ -namespace Markdowser.ViewModels; +using CuteUtils.Logging; + +using Markdowser.Utilities; + +namespace Markdowser.ViewModels; public abstract class ContentViewModelBase(string title) { public string Title { get; } = title; + + public Logger Logger { get; } = GlobalState.Logger; } \ No newline at end of file diff --git a/src/Markdowser/ViewModels/MainWindowViewModel.cs b/src/Markdowser/ViewModels/MainWindowViewModel.cs index 2c70421..437a0d0 100644 --- a/src/Markdowser/ViewModels/MainWindowViewModel.cs +++ b/src/Markdowser/ViewModels/MainWindowViewModel.cs @@ -12,7 +12,6 @@ using ReactiveUI; using System; using System.Collections.ObjectModel; -using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Net.Http; using System.Reflection; @@ -220,7 +219,7 @@ public partial class MainWindowViewModel : ViewModelBase if (string.IsNullOrWhiteSpace(Url)) { Content = DefaultContent; - Debug.WriteLine("URL is empty."); + Logger.LogDebug("URL is empty."); CurrentTab.Header = "New Tab"; return; } @@ -257,7 +256,7 @@ public partial class MainWindowViewModel : ViewModelBase _ = Task.Run(async () => { - Debug.WriteLine("Fetching URL..."); + Logger.LogInfo($"Fetching URL: {Url}"); try { @@ -288,6 +287,8 @@ public partial class MainWindowViewModel : ViewModelBase Content = await contentProcessorManager.ProcessContent(httpResponseMessage, new Progress(p => Progress = p.Percentage)); Dispatcher.UIThread.Post(() => CurrentTab.Header = Content.Title); + + Logger.LogInfo($"Fetched URL: {Url}"); } catch (HttpRequestException e) { @@ -307,6 +308,8 @@ public partial class MainWindowViewModel : ViewModelBase _ = errorMessage.AppendLine($"Failed to fetch URL: {e.Message}"); Content = new MarkdownContentViewModel("Error", errorMessage.ToString()); + + Logger.LogError(e.Message); } catch (Exception e) { @@ -317,6 +320,8 @@ public partial class MainWindowViewModel : ViewModelBase Content = new MarkdownContentViewModel("Error", errorMessage.ToString()); Dispatcher.UIThread.Post(() => CurrentTab.Header = $"Error: {e.GetType().Name}"); + + Logger.LogError(e.Message); } finally { diff --git a/src/Markdowser/ViewModels/ViewModelBase.cs b/src/Markdowser/ViewModels/ViewModelBase.cs index 5c0baf0..87a9fd4 100644 --- a/src/Markdowser/ViewModels/ViewModelBase.cs +++ b/src/Markdowser/ViewModels/ViewModelBase.cs @@ -2,7 +2,10 @@ using Avalonia.Controls.Notifications; using Avalonia.Platform; +using CuteUtils.Logging; + using Markdowser.Models; +using Markdowser.Utilities; using ReactiveUI; @@ -19,6 +22,8 @@ public class ViewModelBase : ReactiveObject public WindowNotificationManager WindowNotificationManager => windowNotificationManager!; + public Logger Logger => GlobalState.Logger; + protected internal void InitializeWindowNotificationManager(Window window) { TopLevel? topLevel = TopLevel.GetTopLevel(window);