From a6337c5a222b7249ec48b582a91cdf4d411f5699 Mon Sep 17 00:00:00 2001 From: Caelan Sayler Date: Wed, 12 Mar 2025 22:02:47 +0000 Subject: [PATCH] Bring back C# sample logging! --- samples/CSharpAvalonia/MainWindow.axaml.cs | 17 +++++----- samples/CSharpAvalonia/MemoryLogger.cs | 39 ++++++++++++++++++++++ samples/CSharpAvalonia/Program.cs | 3 ++ samples/CSharpWpf/App.xaml.cs | 3 ++ samples/CSharpWpf/MainWindow.xaml.cs | 16 ++++----- samples/CSharpWpf/MemoryLogger.cs | 39 ++++++++++++++++++++++ src/lib-csharp/VelopackApp.cs | 21 +++++++----- 7 files changed, 113 insertions(+), 25 deletions(-) create mode 100644 samples/CSharpAvalonia/MemoryLogger.cs create mode 100644 samples/CSharpWpf/MemoryLogger.cs diff --git a/samples/CSharpAvalonia/MainWindow.axaml.cs b/samples/CSharpAvalonia/MainWindow.axaml.cs index 2fb336cb..65daa57a 100644 --- a/samples/CSharpAvalonia/MainWindow.axaml.cs +++ b/samples/CSharpAvalonia/MainWindow.axaml.cs @@ -4,6 +4,7 @@ using Avalonia.Controls; using Avalonia.Interactivity; using Avalonia.Threading; using Velopack; +using Velopack.Logging; namespace CSharpAvalonia; @@ -19,6 +20,8 @@ public partial class MainWindow : Window var updateUrl = SampleHelper.GetReleasesDir(); // replace with your update path/url _um = new UpdateManager(updateUrl); + TextLog.Text = Program.Log.ToString(); + Program.Log.LogUpdated += LogUpdated; UpdateStatus(); } @@ -29,7 +32,7 @@ public partial class MainWindow : Window // ConfigureAwait(true) so that UpdateStatus() is called on the UI thread _update = await _um.CheckForUpdatesAsync().ConfigureAwait(true); } catch (Exception ex) { - LogMessage("Error checking for updates", ex); + Program.Log.LogError(ex, "Error checking for updates"); } UpdateStatus(); @@ -42,7 +45,7 @@ public partial class MainWindow : Window // ConfigureAwait(true) so that UpdateStatus() is called on the UI thread await _um.DownloadUpdatesAsync(_update, Progress).ConfigureAwait(true); } catch (Exception ex) { - LogMessage("Error downloading updates", ex); + Program.Log.LogError(ex, "Error downloading updates"); } UpdateStatus(); @@ -53,16 +56,12 @@ public partial class MainWindow : Window _um.ApplyUpdatesAndRestart(_update); } - private void LogMessage(string text, Exception e = null) + private void LogUpdated(object sender, LogUpdatedEventArgs e) { // logs can be sent from other threads Dispatcher.UIThread.InvokeAsync( () => { - TextLog.Text += text + Environment.NewLine; - if (e != null) { - TextLog.Text += e.ToString() + Environment.NewLine; - } - + TextLog.Text = e.Text; ScrollLog.ScrollToEnd(); }); } @@ -78,7 +77,7 @@ public partial class MainWindow : Window private void Working() { - LogMessage(""); + Program.Log.LogInformation(""); BtnCheckUpdate.IsEnabled = false; BtnDownloadUpdate.IsEnabled = false; BtnRestartApply.IsEnabled = false; diff --git a/samples/CSharpAvalonia/MemoryLogger.cs b/samples/CSharpAvalonia/MemoryLogger.cs new file mode 100644 index 00000000..8e290e98 --- /dev/null +++ b/samples/CSharpAvalonia/MemoryLogger.cs @@ -0,0 +1,39 @@ +using System; +using System.Text; +using Velopack.Logging; + +namespace CSharpAvalonia; + +public class LogUpdatedEventArgs : EventArgs +{ + public string Text { get; set; } +} + +public class MemoryLogger : IVelopackLogger +{ + public event EventHandler LogUpdated; + private readonly StringBuilder _sb = new StringBuilder(); + + public IDisposable BeginScope(TState state) + { + return null; + } + + public override string ToString() + { + lock (_sb) { + return _sb.ToString(); + } + } + + public void Log(VelopackLogLevel logLevel, string message, Exception exception) + { + lock (_sb) { + message = $"{logLevel}: {message}"; + if (exception != null) message += "\n" + exception.ToString(); + Console.WriteLine("log: " + message); + _sb.AppendLine(message); + LogUpdated?.Invoke(this, new LogUpdatedEventArgs { Text = _sb.ToString() }); + } + } +} \ No newline at end of file diff --git a/samples/CSharpAvalonia/Program.cs b/samples/CSharpAvalonia/Program.cs index bfd77e02..a8ebf077 100644 --- a/samples/CSharpAvalonia/Program.cs +++ b/samples/CSharpAvalonia/Program.cs @@ -6,6 +6,8 @@ namespace CSharpAvalonia; class Program { + public static MemoryLogger Log { get; private set; } = new(); + // Initialization code. Don't use any Avalonia, third-party APIs or any // SynchronizationContext-reliant code before AppMain is called: things aren't initialized // yet and stuff might break. @@ -16,6 +18,7 @@ class Program // It's important to Run() the VelopackApp as early as possible in app startup. VelopackApp.Build() .OnFirstRun((v) => { /* Your first run code here */ }) + .SetLogger(Log) .Run(); // Now it's time to run Avalonia diff --git a/samples/CSharpWpf/App.xaml.cs b/samples/CSharpWpf/App.xaml.cs index 1a9f4e83..d8bfb2db 100644 --- a/samples/CSharpWpf/App.xaml.cs +++ b/samples/CSharpWpf/App.xaml.cs @@ -5,6 +5,8 @@ namespace CSharpWpf { public partial class App : Application { + public static MemoryLogger Log { get; private set; } = new(); + // Since WPF has an "automatic" Program.Main, we need to create our own. // In order for this to work, you must also add the following to your .csproj: // CSharpWpf.App @@ -15,6 +17,7 @@ namespace CSharpWpf // It's important to Run() the VelopackApp as early as possible in app startup. VelopackApp.Build() .OnFirstRun((v) => { /* Your first run code here */ }) + .SetLogger(Log) .Run(); // We can now launch the WPF application as normal. diff --git a/samples/CSharpWpf/MainWindow.xaml.cs b/samples/CSharpWpf/MainWindow.xaml.cs index cb0fe328..afa5df21 100644 --- a/samples/CSharpWpf/MainWindow.xaml.cs +++ b/samples/CSharpWpf/MainWindow.xaml.cs @@ -1,6 +1,7 @@ using System.Text; using System.Windows; using Velopack; +using Velopack.Logging; namespace CSharpWpf { @@ -16,6 +17,8 @@ namespace CSharpWpf string updateUrl = SampleHelper.GetReleasesDir(); // replace with your update url _um = new UpdateManager(updateUrl); + TextLog.Text = App.Log.ToString(); + App.Log.LogUpdated += LogUpdated; UpdateStatus(); } @@ -26,7 +29,7 @@ namespace CSharpWpf // ConfigureAwait(true) so that UpdateStatus() is called on the UI thread _update = await _um.CheckForUpdatesAsync().ConfigureAwait(true); } catch (Exception ex) { - LogMessage("Error checking for updates", ex); + App.Log.LogError(ex, "Error checking for updates"); } UpdateStatus(); } @@ -38,7 +41,7 @@ namespace CSharpWpf // ConfigureAwait(true) so that UpdateStatus() is called on the UI thread await _um.DownloadUpdatesAsync(_update, Progress).ConfigureAwait(true); } catch (Exception ex) { - LogMessage("Error downloading updates", ex); + App.Log.LogError(ex, "Error downloading updates"); } UpdateStatus(); } @@ -48,14 +51,11 @@ namespace CSharpWpf _um.ApplyUpdatesAndRestart(_update); } - private void LogMessage(string text, Exception e = null) + private void LogUpdated(object sender, LogUpdatedEventArgs e) { // logs can be sent from other threads this.Dispatcher.InvokeAsync(() => { - TextLog.Text += text + Environment.NewLine; - if (e != null) { - TextLog.Text += e.ToString() + Environment.NewLine; - } + TextLog.Text = e.Text; ScrollLog.ScrollToEnd(); }); } @@ -70,7 +70,7 @@ namespace CSharpWpf private void Working() { - LogMessage(""); + App.Log.LogInformation(""); BtnCheckUpdate.IsEnabled = false; BtnDownloadUpdate.IsEnabled = false; BtnRestartApply.IsEnabled = false; diff --git a/samples/CSharpWpf/MemoryLogger.cs b/samples/CSharpWpf/MemoryLogger.cs new file mode 100644 index 00000000..2daf978f --- /dev/null +++ b/samples/CSharpWpf/MemoryLogger.cs @@ -0,0 +1,39 @@ +using System; +using System.Text; +using Velopack.Logging; + +namespace CSharpWpf; + +public class LogUpdatedEventArgs : EventArgs +{ + public string Text { get; set; } +} + +public class MemoryLogger : IVelopackLogger +{ + public event EventHandler LogUpdated; + private readonly StringBuilder _sb = new StringBuilder(); + + public IDisposable BeginScope(TState state) + { + return null; + } + + public override string ToString() + { + lock (_sb) { + return _sb.ToString(); + } + } + + public void Log(VelopackLogLevel logLevel, string message, Exception exception) + { + lock (_sb) { + message = $"{logLevel}: {message}"; + if (exception != null) message += "\n" + exception.ToString(); + Console.WriteLine("log: " + message); + _sb.AppendLine(message); + LogUpdated?.Invoke(this, new LogUpdatedEventArgs { Text = _sb.ToString() }); + } + } +} \ No newline at end of file diff --git a/src/lib-csharp/VelopackApp.cs b/src/lib-csharp/VelopackApp.cs index e369b7c9..68f79620 100644 --- a/src/lib-csharp/VelopackApp.cs +++ b/src/lib-csharp/VelopackApp.cs @@ -35,6 +35,7 @@ namespace Velopack private string[]? _args; private bool _autoApply = true; private IVelopackLocator? _customLocator; + private IVelopackLogger? _customLogger; private VelopackApp() { @@ -74,6 +75,17 @@ namespace Velopack return this; } + /// + /// Adds a custom logger to the Velopack application. This will be used for all Velopack diagnostic + /// messages in addition to the default log file location. This will be cached and re-used throughout + /// the lifetime of the application. If you have also provided a custom locator, this logger will be ignored. + /// + public VelopackApp SetLogger(IVelopackLogger logger) + { + _customLogger = logger; + return this; + } + /// /// This hook is triggered when the application is started for the first time after installation. /// @@ -151,13 +163,6 @@ namespace Velopack { var args = _args ?? Environment.GetCommandLineArgs().Skip(1).ToArray(); - // internal hook run by the Velopack tooling to check everything is working - if (args.Length >= 1 && args[0].Equals("--veloapp-version", StringComparison.OrdinalIgnoreCase)) { - Console.WriteLine(VelopackRuntimeInfo.VelopackNugetVersion); - Exit(0); - return; - } - if (VelopackLocator.IsCurrentSet) { VelopackLocator.Current.Log.Error( "VelopackApp.Build().Run() was called more than once. This is not allowed and can lead to unexpected behaviour."); @@ -167,7 +172,7 @@ namespace Velopack VelopackLocator.SetCurrentLocator(_customLocator); } - var locator = VelopackLocator.GetCurrentOrCreateDefault(); + var locator = VelopackLocator.GetCurrentOrCreateDefault(_customLogger); var log = locator.Log; log.Info($"Starting VelopackApp.Run (library version {VelopackRuntimeInfo.VelopackNugetVersion}).");