diff --git a/Common/Common.csproj b/Common/Common.csproj
index 345380b..5d87ed2 100644
--- a/Common/Common.csproj
+++ b/Common/Common.csproj
@@ -8,6 +8,9 @@
+
+
+
diff --git a/Common/Logging/ILogger.cs b/Common/Logging/ILogger.cs
index 766525f..a229e54 100644
--- a/Common/Logging/ILogger.cs
+++ b/Common/Logging/ILogger.cs
@@ -10,7 +10,7 @@ public interface ILogger
///
/// Event that is thrown whenever a new log entry was entered with any Log method.
///
- event EventHandler NewLogEntry;
+ event EventHandler? NewLogEntry;
///
/// Initialize the Logger once before using it.
diff --git a/Common/Logging/LogLevel.cs b/Common/Logging/LogLevel.cs
index bd02a1c..f6c6775 100644
--- a/Common/Logging/LogLevel.cs
+++ b/Common/Logging/LogLevel.cs
@@ -2,13 +2,14 @@
///
/// Level of a log entry.
+/// Listed from lowest priority to highest priority.
///
public enum LogLevel
{
Undefined = 0,
- Error = 1,
- Warn = 2,
+ Trace = 1,
+ Debug = 2,
Info = 3,
- Debug = 4,
- Trace = 5
+ Warn = 4,
+ Error = 5
}
diff --git a/Common/Logging/SerilogLogger.cs b/Common/Logging/SerilogLogger.cs
new file mode 100644
index 0000000..cb99569
--- /dev/null
+++ b/Common/Logging/SerilogLogger.cs
@@ -0,0 +1,168 @@
+using Common.Logging;
+using Serilog;
+using Serilog.Core;
+using Serilog.Events;
+
+namespace Common.Logger;
+
+///
+/// Implements a Logger that uses the Serilog package to create log entries and distributes them into different sinks.
+/// Implemented Sinks: File (rolling file), Debug ().
+///
+public class SerilogLogger : Logging.ILogger
+{
+ private readonly LoggingLevelSwitch loggingLevelSwitch;
+ public event EventHandler? NewLogEntry;
+
+ ///
+ /// Constructor of a Logger that uses the Serilog package to create log entries and distributes them into different sinks.
+ ///
+ ///
+ /// path where the logfile shall be created.
+ /// rolling file is created with daily interval. date is automatically added to file name.
+ /// Example: "C:/log/log-.txt"
+ ///
+ ///
+ /// if true the log entries are written to .
+ /// Minimum level is ignored for this log entries, therefore every log entry will be logged.
+ ///
+ public SerilogLogger(string logFilePath, bool logToDebug)
+ {
+ // create logging level switch and initialized with lowest level
+ this.loggingLevelSwitch = new LoggingLevelSwitch() { MinimumLevel = Serilog.Events.LogEventLevel.Verbose };
+
+ // create logger instance
+ if (logToDebug)
+ {
+ Log.Logger = new LoggerConfiguration()
+ .WriteTo.Debug()
+ .WriteTo.File(logFilePath,
+ rollingInterval: RollingInterval.Day,
+ levelSwitch: loggingLevelSwitch,
+ outputTemplate: "{Message:lj}") // outputTemplate = plain message, formatting is done inside log entry
+ .CreateLogger();
+ }
+ else
+ {
+ Log.Logger = new LoggerConfiguration()
+ .WriteTo.File(logFilePath,
+ rollingInterval: RollingInterval.Day,
+ levelSwitch: loggingLevelSwitch,
+ outputTemplate: "{Message:lj}") // outputTemplate = plain message, formatting is done inside log entry
+ .CreateLogger();
+ }
+ }
+
+ public void Initialize()
+ {
+ this.Initialize(LogLevel.Trace);
+ }
+
+ public void Initialize(LogLevel minimumLogLevel)
+ {
+ this.SetMinimumLogLevel(minimumLogLevel);
+ // Nothing else to do for this logger
+ }
+
+ public void SetMinimumLogLevel(LogLevel newMinimumLogLevel)
+ {
+ this.loggingLevelSwitch.MinimumLevel = this.ConvertGenericToSerilogLogLevel(newMinimumLogLevel);
+ }
+
+ private LogEventLevel ConvertGenericToSerilogLogLevel(LogLevel newMinimumLogLevel) => newMinimumLogLevel switch
+ {
+ LogLevel.Undefined => LogEventLevel.Verbose,
+ LogLevel.Trace => LogEventLevel.Verbose,
+ LogLevel.Debug => LogEventLevel.Debug,
+ LogLevel.Info => LogEventLevel.Information,
+ LogLevel.Warn => LogEventLevel.Warning,
+ LogLevel.Error => LogEventLevel.Error,
+ _ => throw new NotImplementedException($"'{nameof(ConvertGenericToSerilogLogLevel)}()' does not contain an entry for {nameof(LogLevel)} {newMinimumLogLevel}"),
+ };
+
+ public void StopLogging()
+ {
+ Log.CloseAndFlush();
+ }
+
+ private void CreateLogEntry(LogEntry logEntry)
+ {
+ var serilogEventLevel = this.ConvertGenericToSerilogLogLevel(logEntry.LogLevel);
+ // formatting is done inside logEntry.ToString() method => therefore log plain message text with right category
+ switch (serilogEventLevel)
+ {
+ case LogEventLevel.Verbose:
+ Log.Verbose(logEntry.ToString());
+ break;
+
+ case LogEventLevel.Debug:
+ Log.Debug(logEntry.ToString());
+ break;
+
+ case LogEventLevel.Information:
+ Log.Information(logEntry.ToString());
+ break;
+
+ case LogEventLevel.Warning:
+ Log.Warning(logEntry.ToString());
+ break;
+
+ case LogEventLevel.Error:
+ Log.Error(logEntry.ToString());
+ break;
+
+ case LogEventLevel.Fatal:
+ Log.Error(logEntry.ToString());
+ break;
+
+ default:
+ throw new NotImplementedException($"'{nameof(CreateLogEntry)}()' does not contain an implementation for {nameof(LogEventLevel)} {serilogEventLevel}.");
+ }
+ }
+
+
+ #region Logging methods
+ public void LogTrace(string message, string category = "")
+ {
+ this.CreateLogEntry(new LogEntry { Category = category, LogLevel = LogLevel.Trace, Message = message });
+ }
+
+ public void LogDebug(string message, string category = "")
+ {
+ this.CreateLogEntry(new LogEntry { Category = category, LogLevel = LogLevel.Debug, Message = message });
+ }
+
+ public void LogInfo(string message, string category = "")
+ {
+ this.CreateLogEntry(new LogEntry { Category = category, LogLevel = LogLevel.Info, Message = message });
+ }
+
+ public void LogWarn(string message, string category = "")
+ {
+ this.CreateLogEntry(new LogEntry { Category = category, LogLevel = LogLevel.Warn, Message = message });
+ }
+
+ public void LogError(string message, string category = "")
+ {
+ this.CreateLogEntry(new LogEntry { Category = category, LogLevel = LogLevel.Error, Message = message });
+ }
+
+ public void LogException(Exception exception, string category = "")
+ {
+ this.CreateLogEntry(new LogEntry { Category = category, LogLevel = LogLevel.Error, Exception = exception });
+ }
+
+ public void LogException(Exception exception, string message, string category = "")
+ {
+ this.CreateLogEntry(
+ new LogEntry
+ {
+ Category = category,
+ LogLevel = LogLevel.Error,
+ Message = message,
+ Exception = exception
+ });
+ }
+ #endregion
+
+}
diff --git a/MultiTerm.Wpf/App.xaml.cs b/MultiTerm.Wpf/App.xaml.cs
index cc8e2ae..b02790e 100644
--- a/MultiTerm.Wpf/App.xaml.cs
+++ b/MultiTerm.Wpf/App.xaml.cs
@@ -1,10 +1,10 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using MultiTerm.Core.ViewModel;
-using Common.StartupHelpers;
using System.Windows;
-using MultiTerm.Core.Common;
using MultiTerm.Core.Helpers;
+using Common.Logging;
+using Common.Logger;
namespace MultiTerm.Wpf;
@@ -19,6 +19,7 @@ public partial class App : Application
.ConfigureServices((hostContext, services) =>
{
services.AddSingleton();
+ services.AddSingleton(new SerilogLogger("C:/log/multiterm-log-.txt", true));
// viewmodels
services.AddSingleton();
@@ -33,6 +34,11 @@ public partial class App : Application
{
await AppHost!.StartAsync();
+ // create logger and initialize
+ var logger = AppHost.Services.GetRequiredService();
+ logger.Initialize(LogLevel.Trace);
+ logger.LogInfo("Application started.", nameof(App));
+
// instanciate startup form and show
var startupForm = AppHost.Services.GetRequiredService();
startupForm.Show();
@@ -42,6 +48,11 @@ public partial class App : Application
protected override async void OnExit(ExitEventArgs e)
{
+ // log application exit and stop logger (if still available)
+ var logger = AppHost!.Services.GetRequiredService();
+ logger?.LogInfo("Application exited.", nameof(App));
+ logger?.StopLogging();
+
await AppHost!.StopAsync();
base.OnExit(e);
}