Skip to content

Commit 5abde4f

Browse files
authored
chore(logging): Introduce NLog, AvaloniaILogSink for NLog, and log exceptions in View Model. (#9)
1 parent 22990ad commit 5abde4f

6 files changed

Lines changed: 118 additions & 7 deletions

File tree

SharpFM.App/App.axaml.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,15 @@
22
using Avalonia.Controls.ApplicationLifetimes;
33
using Avalonia.Markup.Xaml;
44
using SharpFM.App.ViewModels;
5+
using Microsoft.Extensions.Logging;
6+
using NLog.Extensions.Logging;
57

68
namespace SharpFM.App;
79

810
public partial class App : Application
911
{
12+
ILogger logger = LoggerFactory.Create(builder => builder.AddNLog()).CreateLogger<MainWindow>();
13+
1014
public override void Initialize()
1115
{
1216
AvaloniaXamlLoader.Load(this);
@@ -18,7 +22,7 @@ public override void OnFrameworkInitializationCompleted()
1822
{
1923
desktop.MainWindow = new MainWindow
2024
{
21-
DataContext = new MainWindowViewModel()
25+
DataContext = new MainWindowViewModel(logger)
2226
};
2327
}
2428

SharpFM.App/AvaloniaNLogSink.cs

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
using System;
2+
using Avalonia;
3+
using Avalonia.Logging;
4+
using NLog;
5+
6+
namespace SharpFM.App;
7+
8+
/// <summary>
9+
/// Avalonia Log Sink that writes to NLog Loggers.
10+
/// </summary>
11+
public class AvaloniaNLogSink : ILogSink
12+
{
13+
/// <summary>
14+
/// AvaloniaNLogSink is always enabled.
15+
/// </summary>
16+
public bool IsEnabled(LogEventLevel level, string area) => true;
17+
18+
public void Log(LogEventLevel level, string area, object? source, string messageTemplate)
19+
{
20+
Log(level, area, source, messageTemplate, Array.Empty<object?>());
21+
}
22+
23+
public void Log(LogEventLevel level, string area, object? source, string messageTemplate, params object?[] propertyValues)
24+
{
25+
ILogger? logger = source is not null ? LogManager.GetLogger(source.GetType().ToString())
26+
: LogManager.GetLogger(typeof(AvaloniaNLogSink).ToString());
27+
28+
logger.Log(LogLevelToNLogLevel(level), $"{area}: {messageTemplate}", propertyValues);
29+
}
30+
31+
private static LogLevel LogLevelToNLogLevel(LogEventLevel level)
32+
{
33+
return level switch
34+
{
35+
LogEventLevel.Verbose => LogLevel.Trace,
36+
LogEventLevel.Debug => LogLevel.Debug,
37+
LogEventLevel.Information => LogLevel.Info,
38+
LogEventLevel.Warning => LogLevel.Warn,
39+
LogEventLevel.Error => LogLevel.Error,
40+
LogEventLevel.Fatal => LogLevel.Fatal,
41+
_ => LogLevel.Trace,
42+
};
43+
}
44+
}
45+
46+
public static class NLogSinkExtensions
47+
{
48+
/// <summary>
49+
/// Creates an instance of the AvaloniaNLogSink and assigns it to the global sink.
50+
/// </summary>
51+
public static AppBuilder LogToNLog(
52+
this AppBuilder builder)
53+
{
54+
Avalonia.Logging.Logger.Sink = new AvaloniaNLogSink();
55+
return builder;
56+
}
57+
}

SharpFM.App/Program.cs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,15 @@ class Program
99
// SynchronizationContext-reliant code before AppMain is called: things aren't initialized
1010
// yet and stuff might break.
1111
[STAThread]
12-
public static void Main(string[] args) => BuildAvaloniaApp()
13-
.StartWithClassicDesktopLifetime(args);
12+
public static void Main(string[] args)
13+
{
14+
BuildAvaloniaApp().StartWithClassicDesktopLifetime(args);
15+
}
1416

1517
// Avalonia configuration, don't remove; also used by visual designer.
1618
public static AppBuilder BuildAvaloniaApp()
1719
=> AppBuilder.Configure<App>()
1820
.UsePlatformDetect()
1921
.WithInterFont()
20-
.LogToTrace();
22+
.LogToNLog();
2123
}

SharpFM.App/SharpFM.App.csproj

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,11 @@
4343
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
4444
<PrivateAssets>all</PrivateAssets>
4545
</PackageReference>
46+
47+
<PackageReference Include="NLog" Version="5.2.4" />
48+
<PackageReference Include="NLog.Extensions.Logging" Version="5.3.4" />
49+
50+
<None Update="nlog.config" CopyToOutputDirectory="Always" />
4651

4752
<ProjectReference Include="..\SharpFM.Core\SharpFM.Core.csproj" />
4853
</ItemGroup>

SharpFM.App/ViewModels/MainWindowViewModel.cs

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,17 @@
77
using Avalonia;
88
using Avalonia.Controls.ApplicationLifetimes;
99
using FluentAvalonia.UI.Data;
10+
using Microsoft.Extensions.Logging;
1011
using SharpFM.App.Models;
1112
using SharpFM.Core;
1213

1314
namespace SharpFM.App.ViewModels;
1415

1516
public partial class MainWindowViewModel : INotifyPropertyChanged
1617
{
17-
public ClipDbContext _context;
18+
private readonly ILogger _logger;
19+
20+
public readonly ClipDbContext _context;
1821

1922
public event PropertyChangedEventHandler? PropertyChanged;
2023

@@ -23,12 +26,14 @@ private void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
2326
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
2427
}
2528

26-
public MainWindowViewModel()
29+
public MainWindowViewModel(ILogger logger)
2730
{
31+
_logger = logger;
32+
2833
_context = new ClipDbContext();
2934
_context.Database.EnsureCreated();
3035

31-
Console.WriteLine($"Database path: {_context.DbPath}.");
36+
_logger.LogInformation($"Database path: {_context.DbPath}.");
3237

3338
FileMakerClips = new ObservableCollection<ClipViewModel>();
3439

@@ -104,6 +109,7 @@ public void NewEmptyItem()
104109
}
105110
catch (Exception e)
106111
{
112+
_logger.LogCritical("Error creating new Clip.", e);
107113
}
108114
}
109115

@@ -129,6 +135,7 @@ public void CopyAsClass()
129135
}
130136
catch (Exception e)
131137
{
138+
_logger.LogCritical("Error Copying as Class.", e);
132139
}
133140
}
134141

@@ -171,6 +178,7 @@ public async Task PasteFileMakerClipData()
171178
}
172179
catch (Exception e)
173180
{
181+
_logger.LogCritical("Error translating FileMaker blob to Xml.", e);
174182
}
175183
}
176184

@@ -196,6 +204,7 @@ public async Task CopySelectedToClip()
196204
}
197205
catch (Exception e)
198206
{
207+
_logger.LogCritical("Error returning the selected Clip FileMaker blob format.", e);
199208
}
200209
}
201210

SharpFM.App/nlog.config

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<!-- XSD manual extracted from package NLog.Schema: https://www.nuget.org/packages/NLog.Schema-->
3+
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
4+
xsi:schemaLocation="NLog NLog.xsd"
5+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
6+
autoReload="true"
7+
internalLogFile="c:\temp\SharpFM-internal.log"
8+
internalLogLevel="Info">
9+
10+
<!-- the targets to write to -->
11+
<targets>
12+
<!-- write colorful logs to the console -->
13+
<target name="logconsole" xsi:type="ColoredConsole" enableAnsiOutput="true"
14+
layout="${time}|${uppercase:${level:padding=5}}|${mdlc:item=ScopeName}|${ndlctiming:currentScope=true}|${logger}|${message} ${exception:format=tostring:innerFormat=tostring:maxInnerExceptionLevel=2}"
15+
useDefaultRowHighlightingRules="true">
16+
<highlight-row condition="level == LogLevel.Fatal" foregroundColor="Red" />
17+
<highlight-row condition="level == LogLevel.Error" foregroundColor="Yellow" />
18+
<highlight-row condition="level == LogLevel.Warn" foregroundColor="Magenta" />
19+
<highlight-row condition="level == LogLevel.Info" foregroundColor="Cyan" />
20+
<highlight-row condition="level == LogLevel.Debug" foregroundColor="Green" />
21+
<highlight-row condition="level == LogLevel.Trace" foregroundColor="DarkGray" />
22+
</target>
23+
24+
<!-- write logs to file in app data folder -->
25+
<target xsi:type="File" name="logfile"
26+
fileName="${specialfolder:folder=ApplicationData}\SharpFM.log"
27+
layout="${longdate}|${uppercase:${level:padding=5}}|${mdlc:item=ScopeName}|${ndlctiming:currentScope=true}|${logger}|${message} ${exception:format=tostring:innerFormat=tostring:maxInnerExceptionLevel=2}" />
28+
</targets>
29+
30+
<!-- rules to map from logger name to target -->
31+
<rules>
32+
<logger name="*" minlevel="Trace" writeTo="logfile,logconsole" />
33+
</rules>
34+
</nlog>

0 commit comments

Comments
 (0)