Bring back C# sample logging!

This commit is contained in:
Caelan Sayler
2025-03-12 22:02:47 +00:00
committed by Caelan
parent 4f10c94565
commit a6337c5a22
7 changed files with 113 additions and 25 deletions

View File

@@ -4,6 +4,7 @@ using Avalonia.Controls;
using Avalonia.Interactivity; using Avalonia.Interactivity;
using Avalonia.Threading; using Avalonia.Threading;
using Velopack; using Velopack;
using Velopack.Logging;
namespace CSharpAvalonia; namespace CSharpAvalonia;
@@ -19,6 +20,8 @@ public partial class MainWindow : Window
var updateUrl = SampleHelper.GetReleasesDir(); // replace with your update path/url var updateUrl = SampleHelper.GetReleasesDir(); // replace with your update path/url
_um = new UpdateManager(updateUrl); _um = new UpdateManager(updateUrl);
TextLog.Text = Program.Log.ToString();
Program.Log.LogUpdated += LogUpdated;
UpdateStatus(); UpdateStatus();
} }
@@ -29,7 +32,7 @@ public partial class MainWindow : Window
// ConfigureAwait(true) so that UpdateStatus() is called on the UI thread // ConfigureAwait(true) so that UpdateStatus() is called on the UI thread
_update = await _um.CheckForUpdatesAsync().ConfigureAwait(true); _update = await _um.CheckForUpdatesAsync().ConfigureAwait(true);
} catch (Exception ex) { } catch (Exception ex) {
LogMessage("Error checking for updates", ex); Program.Log.LogError(ex, "Error checking for updates");
} }
UpdateStatus(); UpdateStatus();
@@ -42,7 +45,7 @@ public partial class MainWindow : Window
// ConfigureAwait(true) so that UpdateStatus() is called on the UI thread // ConfigureAwait(true) so that UpdateStatus() is called on the UI thread
await _um.DownloadUpdatesAsync(_update, Progress).ConfigureAwait(true); await _um.DownloadUpdatesAsync(_update, Progress).ConfigureAwait(true);
} catch (Exception ex) { } catch (Exception ex) {
LogMessage("Error downloading updates", ex); Program.Log.LogError(ex, "Error downloading updates");
} }
UpdateStatus(); UpdateStatus();
@@ -53,16 +56,12 @@ public partial class MainWindow : Window
_um.ApplyUpdatesAndRestart(_update); _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 // logs can be sent from other threads
Dispatcher.UIThread.InvokeAsync( Dispatcher.UIThread.InvokeAsync(
() => { () => {
TextLog.Text += text + Environment.NewLine; TextLog.Text = e.Text;
if (e != null) {
TextLog.Text += e.ToString() + Environment.NewLine;
}
ScrollLog.ScrollToEnd(); ScrollLog.ScrollToEnd();
}); });
} }
@@ -78,7 +77,7 @@ public partial class MainWindow : Window
private void Working() private void Working()
{ {
LogMessage(""); Program.Log.LogInformation("");
BtnCheckUpdate.IsEnabled = false; BtnCheckUpdate.IsEnabled = false;
BtnDownloadUpdate.IsEnabled = false; BtnDownloadUpdate.IsEnabled = false;
BtnRestartApply.IsEnabled = false; BtnRestartApply.IsEnabled = false;

View File

@@ -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<LogUpdatedEventArgs> LogUpdated;
private readonly StringBuilder _sb = new StringBuilder();
public IDisposable BeginScope<TState>(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() });
}
}
}

View File

@@ -6,6 +6,8 @@ namespace CSharpAvalonia;
class Program class Program
{ {
public static MemoryLogger Log { get; private set; } = new();
// Initialization code. Don't use any Avalonia, third-party APIs or any // Initialization code. Don't use any Avalonia, third-party APIs or any
// SynchronizationContext-reliant code before AppMain is called: things aren't initialized // SynchronizationContext-reliant code before AppMain is called: things aren't initialized
// yet and stuff might break. // 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. // It's important to Run() the VelopackApp as early as possible in app startup.
VelopackApp.Build() VelopackApp.Build()
.OnFirstRun((v) => { /* Your first run code here */ }) .OnFirstRun((v) => { /* Your first run code here */ })
.SetLogger(Log)
.Run(); .Run();
// Now it's time to run Avalonia // Now it's time to run Avalonia

View File

@@ -5,6 +5,8 @@ namespace CSharpWpf
{ {
public partial class App : Application 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. // 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: // In order for this to work, you must also add the following to your .csproj:
// <StartupObject>CSharpWpf.App</StartupObject> // <StartupObject>CSharpWpf.App</StartupObject>
@@ -15,6 +17,7 @@ namespace CSharpWpf
// It's important to Run() the VelopackApp as early as possible in app startup. // It's important to Run() the VelopackApp as early as possible in app startup.
VelopackApp.Build() VelopackApp.Build()
.OnFirstRun((v) => { /* Your first run code here */ }) .OnFirstRun((v) => { /* Your first run code here */ })
.SetLogger(Log)
.Run(); .Run();
// We can now launch the WPF application as normal. // We can now launch the WPF application as normal.

View File

@@ -1,6 +1,7 @@
using System.Text; using System.Text;
using System.Windows; using System.Windows;
using Velopack; using Velopack;
using Velopack.Logging;
namespace CSharpWpf namespace CSharpWpf
{ {
@@ -16,6 +17,8 @@ namespace CSharpWpf
string updateUrl = SampleHelper.GetReleasesDir(); // replace with your update url string updateUrl = SampleHelper.GetReleasesDir(); // replace with your update url
_um = new UpdateManager(updateUrl); _um = new UpdateManager(updateUrl);
TextLog.Text = App.Log.ToString();
App.Log.LogUpdated += LogUpdated;
UpdateStatus(); UpdateStatus();
} }
@@ -26,7 +29,7 @@ namespace CSharpWpf
// ConfigureAwait(true) so that UpdateStatus() is called on the UI thread // ConfigureAwait(true) so that UpdateStatus() is called on the UI thread
_update = await _um.CheckForUpdatesAsync().ConfigureAwait(true); _update = await _um.CheckForUpdatesAsync().ConfigureAwait(true);
} catch (Exception ex) { } catch (Exception ex) {
LogMessage("Error checking for updates", ex); App.Log.LogError(ex, "Error checking for updates");
} }
UpdateStatus(); UpdateStatus();
} }
@@ -38,7 +41,7 @@ namespace CSharpWpf
// ConfigureAwait(true) so that UpdateStatus() is called on the UI thread // ConfigureAwait(true) so that UpdateStatus() is called on the UI thread
await _um.DownloadUpdatesAsync(_update, Progress).ConfigureAwait(true); await _um.DownloadUpdatesAsync(_update, Progress).ConfigureAwait(true);
} catch (Exception ex) { } catch (Exception ex) {
LogMessage("Error downloading updates", ex); App.Log.LogError(ex, "Error downloading updates");
} }
UpdateStatus(); UpdateStatus();
} }
@@ -48,14 +51,11 @@ namespace CSharpWpf
_um.ApplyUpdatesAndRestart(_update); _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 // logs can be sent from other threads
this.Dispatcher.InvokeAsync(() => { this.Dispatcher.InvokeAsync(() => {
TextLog.Text += text + Environment.NewLine; TextLog.Text = e.Text;
if (e != null) {
TextLog.Text += e.ToString() + Environment.NewLine;
}
ScrollLog.ScrollToEnd(); ScrollLog.ScrollToEnd();
}); });
} }
@@ -70,7 +70,7 @@ namespace CSharpWpf
private void Working() private void Working()
{ {
LogMessage(""); App.Log.LogInformation("");
BtnCheckUpdate.IsEnabled = false; BtnCheckUpdate.IsEnabled = false;
BtnDownloadUpdate.IsEnabled = false; BtnDownloadUpdate.IsEnabled = false;
BtnRestartApply.IsEnabled = false; BtnRestartApply.IsEnabled = false;

View File

@@ -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<LogUpdatedEventArgs> LogUpdated;
private readonly StringBuilder _sb = new StringBuilder();
public IDisposable BeginScope<TState>(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() });
}
}
}

View File

@@ -35,6 +35,7 @@ namespace Velopack
private string[]? _args; private string[]? _args;
private bool _autoApply = true; private bool _autoApply = true;
private IVelopackLocator? _customLocator; private IVelopackLocator? _customLocator;
private IVelopackLogger? _customLogger;
private VelopackApp() private VelopackApp()
{ {
@@ -74,6 +75,17 @@ namespace Velopack
return this; return this;
} }
/// <summary>
/// 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.
/// </summary>
public VelopackApp SetLogger(IVelopackLogger logger)
{
_customLogger = logger;
return this;
}
/// <summary> /// <summary>
/// This hook is triggered when the application is started for the first time after installation. /// This hook is triggered when the application is started for the first time after installation.
/// </summary> /// </summary>
@@ -151,13 +163,6 @@ namespace Velopack
{ {
var args = _args ?? Environment.GetCommandLineArgs().Skip(1).ToArray(); 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) { if (VelopackLocator.IsCurrentSet) {
VelopackLocator.Current.Log.Error( VelopackLocator.Current.Log.Error(
"VelopackApp.Build().Run() was called more than once. This is not allowed and can lead to unexpected behaviour."); "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); VelopackLocator.SetCurrentLocator(_customLocator);
} }
var locator = VelopackLocator.GetCurrentOrCreateDefault(); var locator = VelopackLocator.GetCurrentOrCreateDefault(_customLogger);
var log = locator.Log; var log = locator.Log;
log.Info($"Starting VelopackApp.Run (library version {VelopackRuntimeInfo.VelopackNugetVersion})."); log.Info($"Starting VelopackApp.Run (library version {VelopackRuntimeInfo.VelopackNugetVersion}).");