From 9231688ae7d92d41f5dc59ba9d90a56177bb6e65 Mon Sep 17 00:00:00 2001
From: Niko Ehrenfeuchter <nikolaus.ehrenfeuchter@unibas.ch>
Date: Wed, 17 Jan 2018 13:59:17 +0100
Subject: [PATCH] Switch all logging code to NLog.

Currently this is almost entirely untested and configuration for email
notifications is yet to be done.

Refers to #3
---
 ATXConfigTest/ATXConfigTest.csproj            |   5 +
 ATXConfigTest/AutoTxConfigTest.cs             |   2 +
 ATXConfigTest/packages.config                 |   4 +
 ATXSerializables/ATXCommon.csproj             |   7 +
 .../Serializables/ServiceConfig.cs            |  12 +-
 .../Serializables/ServiceStatus.cs            |  35 ++-
 ATXSerializables/packages.config              |   4 +
 ATXTray/ATXTray.csproj                        |   4 +
 ATXTray/AutoTxTray.cs                         |  35 +++
 ATXTray/packages.config                       |   1 +
 AutoTx/ATXProject.csproj                      |   8 +-
 AutoTx/AutoTx.cs                              | 228 +++++++++++-------
 AutoTx/Email.cs                               |  36 +--
 AutoTx/Logging.cs                             |  81 -------
 AutoTx/RoboCommand.cs                         |  20 +-
 AutoTx/SystemChecks.cs                        |   8 +-
 AutoTx/packages.config                        |   4 +
 17 files changed, 279 insertions(+), 215 deletions(-)
 create mode 100644 ATXConfigTest/packages.config
 create mode 100644 ATXSerializables/packages.config
 delete mode 100644 AutoTx/Logging.cs
 create mode 100644 AutoTx/packages.config

diff --git a/ATXConfigTest/ATXConfigTest.csproj b/ATXConfigTest/ATXConfigTest.csproj
index f5d0375..08e5ee6 100644
--- a/ATXConfigTest/ATXConfigTest.csproj
+++ b/ATXConfigTest/ATXConfigTest.csproj
@@ -32,6 +32,10 @@
     <WarningLevel>4</WarningLevel>
   </PropertyGroup>
   <ItemGroup>
+    <Reference Include="NLog, Version=4.0.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c, processorArchitecture=MSIL">
+      <HintPath>..\packages\NLog.4.3.11\lib\net45\NLog.dll</HintPath>
+      <Private>True</Private>
+    </Reference>
     <Reference Include="System" />
     <Reference Include="System.Core" />
     <Reference Include="System.Xml.Linq" />
@@ -46,6 +50,7 @@
   </ItemGroup>
   <ItemGroup>
     <None Include="App.config" />
+    <None Include="packages.config" />
   </ItemGroup>
   <ItemGroup>
     <ProjectReference Include="..\ATXSerializables\ATXCommon.csproj">
diff --git a/ATXConfigTest/AutoTxConfigTest.cs b/ATXConfigTest/AutoTxConfigTest.cs
index 100291f..47af5d8 100644
--- a/ATXConfigTest/AutoTxConfigTest.cs
+++ b/ATXConfigTest/AutoTxConfigTest.cs
@@ -1,5 +1,7 @@
 using System;
 using System.IO;
+using NLog;
+using NLog.Config;
 using ATXCommon.Serializables;
 
 namespace ATXConfigTest
diff --git a/ATXConfigTest/packages.config b/ATXConfigTest/packages.config
new file mode 100644
index 0000000..8e85834
--- /dev/null
+++ b/ATXConfigTest/packages.config
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<packages>
+  <package id="NLog" version="4.3.11" targetFramework="net45" />
+</packages>
\ No newline at end of file
diff --git a/ATXSerializables/ATXCommon.csproj b/ATXSerializables/ATXCommon.csproj
index a282202..6437a4c 100644
--- a/ATXSerializables/ATXCommon.csproj
+++ b/ATXSerializables/ATXCommon.csproj
@@ -30,6 +30,10 @@
     <WarningLevel>4</WarningLevel>
   </PropertyGroup>
   <ItemGroup>
+    <Reference Include="NLog, Version=4.0.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c, processorArchitecture=MSIL">
+      <HintPath>..\packages\NLog.4.3.11\lib\net45\NLog.dll</HintPath>
+      <Private>True</Private>
+    </Reference>
     <Reference Include="System" />
     <Reference Include="System.Configuration" />
     <Reference Include="System.Core" />
@@ -47,6 +51,9 @@
     <Compile Include="Serializables\ServiceStatus.cs" />
     <Compile Include="TimeUtils.cs" />
   </ItemGroup>
+  <ItemGroup>
+    <None Include="packages.config" />
+  </ItemGroup>
   <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
   <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 
        Other similar extension points exist, see Microsoft.Common.targets.
diff --git a/ATXSerializables/Serializables/ServiceConfig.cs b/ATXSerializables/Serializables/ServiceConfig.cs
index 9f5e635..eba8b41 100644
--- a/ATXSerializables/Serializables/ServiceConfig.cs
+++ b/ATXSerializables/Serializables/ServiceConfig.cs
@@ -3,6 +3,8 @@ using System.Collections.Generic;
 using System.Configuration;
 using System.IO;
 using System.Xml.Serialization;
+using NLog;
+using NLog.Config;
 
 namespace ATXCommon.Serializables
 {
@@ -13,6 +15,7 @@ namespace ATXCommon.Serializables
     public class ServiceConfig
     {
         [XmlIgnore] public string ValidationWarnings;
+        [XmlIgnore] private static readonly Logger Log = LogManager.GetCurrentClassLogger();
 
         public ServiceConfig() {
             ValidationWarnings = "";
@@ -136,11 +139,13 @@ namespace ATXCommon.Serializables
         }
 
         public static ServiceConfig Deserialize(string file) {
+            Log.Debug("Trying to read service configuration XML file: [{0}]", file);
             var xs = new XmlSerializer(typeof(ServiceConfig));
             var reader = File.OpenText(file);
             var config = (ServiceConfig) xs.Deserialize(reader);
             reader.Close();
             ValidateConfiguration(config);
+            Log.Debug("Finished deserializing service configuration XML file.");
             return config;
         }
 
@@ -181,8 +186,11 @@ namespace ATXCommon.Serializables
 
             // NON-CRITICAL stuff just adds messages to ValidationWarnings:
             // DestinationDirectory
-            if (!c.DestinationDirectory.StartsWith(@"\\"))
-                c.ValidationWarnings += " - <DestinationDirectory> is not a UNC path!\n";
+            if (!c.DestinationDirectory.StartsWith(@"\\")) {
+                var msg = "<DestinationDirectory> is not a UNC path!\n";
+                c.ValidationWarnings += " - " + msg;
+                Log.Warn(msg);
+            }
         }
 
         public string Summary() {
diff --git a/ATXSerializables/Serializables/ServiceStatus.cs b/ATXSerializables/Serializables/ServiceStatus.cs
index 9d88b50..49758fc 100644
--- a/ATXSerializables/Serializables/ServiceStatus.cs
+++ b/ATXSerializables/Serializables/ServiceStatus.cs
@@ -1,6 +1,7 @@
 using System;
 using System.IO;
 using System.Xml.Serialization;
+using NLog;
 
 namespace ATXCommon.Serializables
 {
@@ -10,6 +11,7 @@ namespace ATXCommon.Serializables
         [XmlIgnore] string _storageFile; // remember where we came from
         [XmlIgnore] private ServiceConfig _config;
         [XmlIgnore] public string ValidationWarnings;
+        [XmlIgnore] private static readonly Logger Log = LogManager.GetCurrentClassLogger();
 
         private DateTime _lastStatusUpdate;
         private DateTime _lastStorageNotification;
@@ -63,7 +65,7 @@ namespace ATXCommon.Serializables
             get { return _limitReason; }
             set {
                 _limitReason = value;
-                // log("LimitReason was updated (" + value + "), calling Serialize()...");
+                Log.Trace("LimitReason was updated ({0}).", value);
                 Serialize();
             }
         }
@@ -72,7 +74,7 @@ namespace ATXCommon.Serializables
             get { return _currentTransferSrc; }
             set {
                 _currentTransferSrc = value;
-                // log("CurrentTransferSrc was updated (" + value + "), calling Serialize()...");
+                Log.Trace("CurrentTransferSrc was updated ({0}).", value);
                 Serialize();
             }
         }
@@ -81,7 +83,7 @@ namespace ATXCommon.Serializables
             get { return _currentTargetTmp; }
             set {
                 _currentTargetTmp = value;
-                // log("CurrentTargetTmp was updated (" + value + "), calling Serialize()...");
+                Log.Trace("CurrentTargetTmp was updated ({0}).", value);
                 Serialize();
             }
         }
@@ -90,7 +92,7 @@ namespace ATXCommon.Serializables
             get { return _serviceSuspended; }
             set {
                 _serviceSuspended = value;
-                // log("ServiceSuspended was updated (" + value + "), calling Serialize()...");
+                Log.Trace("ServiceSuspended was updated ({0}).", value);
                 Serialize();
             }
         }
@@ -99,7 +101,7 @@ namespace ATXCommon.Serializables
             get { return _transferInProgress; }
             set {
                 _transferInProgress = value;
-                // log("FilecopyFinished was updated (" + value + "), calling Serialize()...");
+                Log.Trace("FilecopyFinished was updated ({0}).", value);
                 Serialize();
             }
         }
@@ -119,7 +121,7 @@ namespace ATXCommon.Serializables
             get { return _currentTransferSize; }
             set {
                 _currentTransferSize = value;
-                // log("CurrentTransferSize was updated (" + value + "), calling Serialize()...");
+                Log.Trace("CurrentTransferSize was updated ({0}).", value);
                 Serialize();
             }
         }
@@ -137,9 +139,10 @@ namespace ATXCommon.Serializables
              * to test for this (plus, we can't serialize anyway without it).
              */
             if (_storageFile == null) {
-                // log("File name for XML serialization is not set, doing nothing.");
+                Log.Trace("File name for XML serialization is not set, doing nothing!");
                 return;
             }
+            Log.Trace("Serializing status...");
             // update the timestamp:
             LastStatusUpdate = DateTime.Now;
             try {
@@ -150,23 +153,13 @@ namespace ATXCommon.Serializables
                 writer.Close();
             }
             catch (Exception ex) {
-                log("Error in Serialize(): " + ex.Message);
+                Log.Error("Error in Serialize(): {0}", ex.Message);
             }
-            log("Finished serializing " + _storageFile);
-        }
-
-        static void log(string message) {
-            // use Console.WriteLine until proper logging is there (running as a system
-            // service means those messages will disappear):
-            Console.WriteLine(message);
-            /*
-            using (var sw = File.AppendText(@"C:\Tools\AutoTx\console.log")) {
-                sw.WriteLine(message);
-            }
-             */
+            Log.Debug("Finished serializing [{0}].", _storageFile);
         }
 
         public static ServiceStatus Deserialize(string file, ServiceConfig config) {
+            Log.Debug("Trying to deserialize status XML file [{0}].", file);
             ServiceStatus status;
 
             var xs = new XmlSerializer(typeof(ServiceStatus));
@@ -174,10 +167,12 @@ namespace ATXCommon.Serializables
                 var reader = File.OpenText(file);
                 status = (ServiceStatus) xs.Deserialize(reader);
                 reader.Close();
+                Log.Debug("Finished deserializing service status XML file.");
             }
             catch (Exception) {
                 // if reading the status XML fails, we return an empty (new) one
                 status = new ServiceStatus();
+                Log.Warn("Deserializing [{0}] failed, creating new status using defauls.", file);
             }
             status._config = config;
             ValidateStatus(status);
diff --git a/ATXSerializables/packages.config b/ATXSerializables/packages.config
new file mode 100644
index 0000000..8e85834
--- /dev/null
+++ b/ATXSerializables/packages.config
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<packages>
+  <package id="NLog" version="4.3.11" targetFramework="net45" />
+</packages>
\ No newline at end of file
diff --git a/ATXTray/ATXTray.csproj b/ATXTray/ATXTray.csproj
index 667121e..1c86b23 100644
--- a/ATXTray/ATXTray.csproj
+++ b/ATXTray/ATXTray.csproj
@@ -47,6 +47,10 @@
       <HintPath>..\packages\Microsoft.WindowsAPICodePack-Shell.1.1.0.0\lib\Microsoft.WindowsAPICodePack.ShellExtensions.dll</HintPath>
       <Private>True</Private>
     </Reference>
+    <Reference Include="NLog, Version=4.0.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c, processorArchitecture=MSIL">
+      <HintPath>..\packages\NLog.4.3.11\lib\net45\NLog.dll</HintPath>
+      <Private>True</Private>
+    </Reference>
     <Reference Include="System" />
     <Reference Include="System.Core" />
     <Reference Include="System.Xml.Linq" />
diff --git a/ATXTray/AutoTxTray.cs b/ATXTray/AutoTxTray.cs
index 0052127..0f2a971 100644
--- a/ATXTray/AutoTxTray.cs
+++ b/ATXTray/AutoTxTray.cs
@@ -6,12 +6,17 @@ using System.IO;
 using System.Windows.Forms;
 using ATXCommon.Serializables;
 using Microsoft.WindowsAPICodePack.Dialogs;
+using NLog;
+using NLog.Config;
+using NLog.Targets;
 using Timer = System.Timers.Timer;
 
 namespace ATXTray
 {
     public class AutoTxTray : ApplicationContext
     {
+        private static readonly Logger Log = LogManager.GetCurrentClassLogger();
+
         private static readonly string AppTitle = Path.GetFileNameWithoutExtension(Application.ExecutablePath);
         private static readonly Timer AppTimer = new Timer(1000);
         private static bool _terminate = false;
@@ -46,6 +51,26 @@ namespace ATXTray
         private readonly ToolStripMenuItem _miTxEnqueue = new ToolStripMenuItem();
 
         public AutoTxTray() {
+            
+            #region logging configuration
+            
+            var logConfig = new LoggingConfiguration();
+            var fileTarget = new FileTarget {
+                FileName = AppTitle + ".log",
+                Layout = @"${date:format=yyyy-MM-dd HH\:mm\:ss} [${level}] ${message}"
+                // Layout = @"${date:format=yyyy-MM-dd HH\:mm\:ss} [${level}] (${logger}) ${message}"
+            };
+            logConfig.AddTarget("file", fileTarget);
+            var logRule = new LoggingRule("*", LogLevel.Debug, fileTarget);
+            logConfig.LoggingRules.Add(logRule);
+            LogManager.Configuration = logConfig;
+            
+            #endregion
+
+
+            Log.Info("{0} initializing...", AppTitle);
+            Log.Debug(" - config file: [{0}]", ConfigFile);
+            Log.Debug(" - status file: [{0}]", StatusFile);
 
             _notifyIcon.Icon = _tiStopped;
             _notifyIcon.Visible = true;
@@ -54,13 +79,16 @@ namespace ATXTray
             // this doesn't work properly, the menu will not close etc. so we disable it for now:
             // _notifyIcon.Click += ShowContextMenu;
 
+            Log.Trace("Trying to read service config and status files...");
             try {
                 _config = ServiceConfig.Deserialize(ConfigFile);
                 ReadStatus();
+                Log.Trace("Completed reading service config and status files.");
                 SetupContextMenu();
             }
             catch (Exception ex) {
                 var msg = "Error during initialization: " + ex.Message;
+                Log.Error(msg);
                 _notifyIcon.ShowBalloonTip(5000, AppTitle, msg, ToolTipIcon.Error);
                 // we cannot terminate the message loop (Application.Run()) while the constructor
                 // is being run as it is not active yet - therefore we simply remember that we want
@@ -71,9 +99,13 @@ namespace ATXTray
 
             AppTimer.Elapsed += AppTimerElapsed;
             AppTimer.Enabled = true;
+            Log.Trace("Enabled timer.");
+
+            Log.Info("AutoTxTray initialization completed.");
         }
 
         private void SetupContextMenu() {
+            Log.Trace("Building context menu...");
             _miExit.Text = @"Exit";
             _miExit.Click += MiExitClick;
 
@@ -108,9 +140,11 @@ namespace ATXTray
             });
 
             _notifyIcon.ContextMenuStrip = _cmStrip;
+            Log.Trace("Finished building context menu.");
         }
         
         private void AutoTxTrayExit() {
+            Log.Info("Shutting down {0}.", AppTitle);
             _notifyIcon.Visible = false;
             Application.Exit();
         }
@@ -190,6 +224,7 @@ namespace ATXTray
             if (age == _statusAge)
                 return;
 
+            Log.Debug("Status file was updated, trying to re-read...");
             _statusAge = age;
             _status = ServiceStatus.Deserialize(StatusFile, _config);
         }
diff --git a/ATXTray/packages.config b/ATXTray/packages.config
index e06f06e..1aa4655 100644
--- a/ATXTray/packages.config
+++ b/ATXTray/packages.config
@@ -2,4 +2,5 @@
 <packages>
   <package id="Microsoft.WindowsAPICodePack-Core" version="1.1.0.0" targetFramework="net45" />
   <package id="Microsoft.WindowsAPICodePack-Shell" version="1.1.0.0" targetFramework="net45" />
+  <package id="NLog" version="4.3.11" targetFramework="net45" />
 </packages>
\ No newline at end of file
diff --git a/AutoTx/ATXProject.csproj b/AutoTx/ATXProject.csproj
index 15cfffa..2c12ad6 100644
--- a/AutoTx/ATXProject.csproj
+++ b/AutoTx/ATXProject.csproj
@@ -50,6 +50,10 @@
     <StartupObject>AutoTx.Program</StartupObject>
   </PropertyGroup>
   <ItemGroup>
+    <Reference Include="NLog, Version=4.0.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c, processorArchitecture=MSIL">
+      <HintPath>..\packages\NLog.4.3.11\lib\net45\NLog.dll</HintPath>
+      <Private>True</Private>
+    </Reference>
     <Reference Include="RoboSharp">
       <HintPath>..\..\robosharp\RoboSharp\bin\Debug\RoboSharp.dll</HintPath>
     </Reference>
@@ -83,9 +87,6 @@
     <Compile Include="Email.cs">
       <SubType>Component</SubType>
     </Compile>
-    <Compile Include="Logging.cs">
-      <SubType>Component</SubType>
-    </Compile>
     <Compile Include="Program.cs" />
     <Compile Include="ProjectInstaller.cs">
       <SubType>Component</SubType>
@@ -142,6 +143,7 @@
     </BootstrapperPackage>
   </ItemGroup>
   <ItemGroup>
+    <None Include="packages.config" />
     <None Include="Resources\BuildDate.txt" />
   </ItemGroup>
   <ItemGroup>
diff --git a/AutoTx/AutoTx.cs b/AutoTx/AutoTx.cs
index c6f7d84..c862d1a 100644
--- a/AutoTx/AutoTx.cs
+++ b/AutoTx/AutoTx.cs
@@ -9,8 +9,11 @@ using System.Timers;
 using System.DirectoryServices.AccountManagement;
 using System.Globalization;
 using System.Management;
+using NLog;
+using NLog.Config;
 using ATXCommon.Serializables;
 using ATXCommon;
+using NLog.Targets;
 using RoboSharp;
 
 namespace AutoTx
@@ -19,6 +22,8 @@ namespace AutoTx
     {
         #region global variables
 
+        private static readonly Logger Log = LogManager.GetCurrentClassLogger();
+
         // naming convention: variables ending with "Path" are strings, variables
         // ending with "Dir" are DirectoryInfo objects
         private string _configPath;
@@ -71,11 +76,32 @@ namespace AutoTx
 
         public AutoTx() {
             InitializeComponent();
+            SetupLogging();
             CreateEventLog();
             LoadSettings();
             CreateIncomingDirectories();
         }
 
+        /// <summary>
+        /// Set up NLog logging: targets, rules...
+        /// </summary>
+        private void SetupLogging() {
+            var logConfig = new LoggingConfiguration();
+            var fileTarget = new FileTarget {
+                FileName = ServiceName + ".log",
+                ArchiveAboveSize = 1000000,
+                ArchiveFileName = ServiceName + ".{#}.log",
+                MaxArchiveFiles = 9,
+                KeepFileOpen = true,
+                Layout = @"${date:format=yyyy-MM-dd HH\:mm\:ss} [${level}] ${message}",
+                // Layout = @"${date:format=yyyy-MM-dd HH\:mm\:ss} [${level}] (${logger}) ${message}"
+            };
+            logConfig.AddTarget("file", fileTarget);
+            var logRuleFile = new LoggingRule("*", LogLevel.Debug, fileTarget);
+            logConfig.LoggingRules.Add(logRuleFile);
+            LogManager.Configuration = logConfig;
+        }
+
         /// <summary>
         /// Create the event log if it doesn't exist yet.
         /// </summary>
@@ -89,7 +115,7 @@ namespace AutoTx
                 eventLog.Log = ServiceName;
             }
             catch (Exception ex) {
-                writeLog("Error in createEventLog(): " + ex.Message, true);
+                Log.Error("Error in createEventLog(): " + ex.Message, true);
             }
         }
 
@@ -100,7 +126,6 @@ namespace AutoTx
             try {
                 _transferState = TxState.Stopped;
                 var baseDir = AppDomain.CurrentDomain.BaseDirectory;
-                _logPath = Path.Combine(baseDir, "service.log");
                 _configPath = Path.Combine(baseDir, "configuration.xml");
                 _statusPath = Path.Combine(baseDir, "status.xml");
 
@@ -110,8 +135,10 @@ namespace AutoTx
                 _roboCommand = new RoboCommand();
             }
             catch (Exception ex) {
-                writeLog("Error in LoadSettings(): " + ex.Message + "\n" +
-                    ex.StackTrace, true);
+                // FIXME: combine log and admin-email!
+                var msg = string.Format("Error in LoadSettings(): {0}\n{1}", ex.Message, ex.StackTrace);
+                Log.Error(msg);
+                SendAdminEmail(msg);
                 throw new Exception("Error in LoadSettings.");
             }
             // NOTE: this is explicitly called *outside* the try-catch block so an Exception
@@ -128,15 +155,18 @@ namespace AutoTx
                 _config = ServiceConfig.Deserialize(_configPath);
                 _incomingPath = Path.Combine(_config.SourceDrive, _config.IncomingDirectory);
                 _managedPath = Path.Combine(_config.SourceDrive, _config.ManagedDirectory);
-                writeLogDebug("Loaded config from " + _configPath);
+                Log.Debug("Loaded config from [{0}]", _configPath);
             }
             catch (ConfigurationErrorsException ex) {
-                writeLog("ERROR validating configuration file [" + _configPath +
-                    "]: " + ex.Message);
+                Log.Error("ERROR validating configuration file [{0}]: {1}",
+                    _configPath, ex.Message);
                 throw new Exception("Error validating configuration.");
             }
             catch (Exception ex) {
-                writeLog("Error loading configuration XML: " + ex.Message, true);
+                // FIXME: combine log and admin-email!
+                var msg = string.Format("Error loading configuration XML: {0}", ex.Message);
+                Log.Error(msg);
+                SendAdminEmail(msg);
                 // this should terminate the service process:
                 throw new Exception("Error loading config.");
             }
@@ -148,13 +178,16 @@ namespace AutoTx
         /// </summary>
         private void LoadStatusXml() {
             try {
-                writeLogDebug("Trying to load status from " + _statusPath);
+                Log.Debug("Trying to load status from [{0}]", _statusPath);
                 _status = ServiceStatus.Deserialize(_statusPath, _config);
-                writeLogDebug("Loaded status from " + _statusPath);
+                Log.Debug("Loaded status from [{0}]", _statusPath);
             }
             catch (Exception ex) {
-                writeLog("Error loading status XML from [" + _statusPath + "]: "
-                         + ex.Message + "\n" + ex.StackTrace, true);
+                // FIXME: combine log and admin-email!
+                var msg = string.Format("Error loading status XML from [{0}]: {1} {2}",
+                    _statusPath, ex.Message, ex.StackTrace);
+                Log.Error(msg);
+                SendAdminEmail(msg);
                 // this should terminate the service process:
                 throw new Exception("Error loading status.");
             }
@@ -167,7 +200,7 @@ namespace AutoTx
         public void CheckConfiguration() {
             var configInvalid = false;
             if (CheckSpoolingDirectories() == false) {
-                writeLog("ERROR checking spooling directories (incoming / managed)!");
+                Log.Error("ERROR checking spooling directories (incoming / managed)!");
                 configInvalid = true;
             }
 
@@ -179,8 +212,12 @@ namespace AutoTx
             // then set it to false while the service is running until it is properly
             // shut down via the OnStop() method:
             if (_status.CleanShutdown == false) {
-                writeLog("WARNING: " + ServiceName + " was not shut down properly last time!\n\n" +
-                         "This could indicate the computer has crashed or was forcefully shut off.", true);
+                // FIXME: combine log and admin-email!
+                var msg = string.Format("WARNING: {0} was not shut down properly last time!\n\n" +
+                         "This could indicate the computer has crashed or was forcefully shut off.",
+                    ServiceName);
+                Log.Warn(msg);
+                SendAdminEmail(msg);
             }
             _status.CleanShutdown = false;
 
@@ -222,18 +259,21 @@ namespace AutoTx
                 }
             }
             catch (Exception ex) {
-                writeLog("GraceLocationSummary() failed: " + ex.Message, true);
+                // FIXME: combine log and admin-email!
+                var warn = string.Format("GraceLocationSummary() failed: {0}", ex.Message);
+                Log.Warn(warn);
+                SendAdminEmail(warn);
             }
 
             if (!string.IsNullOrEmpty(_config.ValidationWarnings)) {
-                writeLog("WARNING: some configuration settings might not be optimal:\n" +
-                         _config.ValidationWarnings);
+                Log.Warn("WARNING: some configuration settings might not be optimal:\n{0}",
+                    _config.ValidationWarnings);
             }
             if (!string.IsNullOrEmpty(_status.ValidationWarnings)) {
-                writeLog("WARNING: some status parameters were invalid and have been reset:\n" +
-                         _status.ValidationWarnings);
+                Log.Warn("WARNING: some status parameters were invalid and have been reset:\n{0}",
+                    _status.ValidationWarnings);
             }
-            writeLogDebug(msg);
+            Log.Debug(msg);
         }
 
         #endregion
@@ -250,17 +290,20 @@ namespace AutoTx
                 _mainTimer.Enabled = true;
             }
             catch (Exception ex) {
-                writeLog("Error in OnStart(): " + ex.Message, true);
+                // FIXME: combine log and admin-email!
+                var msg = string.Format("Error in OnStart(): {0}", ex.Message);
+                Log.Error(msg);
+                SendAdminEmail(msg);
             }
 
             // read the build timestamp from the resources:
             var buildTimestamp = Properties.Resources.BuildDate.Trim();
             var buildCommitName = Properties.Resources.BuildCommit.Trim();
-            writeLog("-----------------------");
-            writeLog(ServiceName + " service started.");
-            writeLog("build:  [" + buildTimestamp + "]");
-            writeLog("commit: [" + buildCommitName + "]");
-            writeLog("-----------------------");
+            Log.Info("-----------------------");
+            Log.Info("{0} service started.", ServiceName);
+            Log.Info("build:  [{0}]", buildTimestamp);
+            Log.Info("commit: [{0}]", buildCommitName);
+            Log.Info("-----------------------");
         }
 
         /// <summary>
@@ -269,7 +312,7 @@ namespace AutoTx
         /// the OnShutdown() method is used!
         /// </summary>
         protected override void OnStop() {
-            writeLog(ServiceName + " service stop requested...");
+            Log.Warn(ServiceName + " service stop requested...");
             if (_transferState != TxState.Stopped) {
                 _transferState = TxState.DoNothing;
                 // Stop() is calling Process.Kill() (immediately forcing a termination of the
@@ -281,19 +324,22 @@ namespace AutoTx
                     _roboCommand.Stop();
                 }
                 catch (Exception ex) {
-                    writeLog("Error terminating the RoboCopy process: " + ex.Message, true);
+                    // FIXME: combine log and admin-email!
+                    var msg = string.Format("Error terminating the RoboCopy process: {0}", ex.Message);
+                    Log.Error(msg);
+                    SendAdminEmail(msg);
                 }
                 _status.TransferInProgress = true;
-                writeLog("Not all files were transferred - will resume upon next start");
-                writeLogDebug("CurrentTransferSrc: " + _status.CurrentTransferSrc);
+                Log.Info("Not all files were transferred - will resume upon next start");
+                Log.Debug("CurrentTransferSrc: " + _status.CurrentTransferSrc);
                 // should we delete an incompletely transferred file on the target?
                 SendTransferInterruptedMail();
             }
             // set the shutdown status to clean:
             _status.CleanShutdown = true;
-            writeLog("-----------------------");
-            writeLog(ServiceName + " service stopped");
-            writeLog("-----------------------");
+            Log.Info("-----------------------");
+            Log.Info("{0} service stopped", ServiceName);
+            Log.Info("-----------------------");
         }
 
         /// <summary>
@@ -301,7 +347,7 @@ namespace AutoTx
         /// it doesn't call the OnStop() method, so we have to do this explicitly.
         /// </summary>
         protected override void OnShutdown() {
-            writeLog("System is shutting down, requesting the service to stop.");
+            Log.Warn("System is shutting down, requesting the service to stop.");
             OnStop();
         }
 
@@ -309,9 +355,9 @@ namespace AutoTx
         /// Is executed when the service continues
         /// </summary>
         protected override void OnContinue() {
-            writeLog("-------------------------");
-            writeLog(ServiceName + " service resuming");
-            writeLog("-------------------------");
+            Log.Info("-------------------------");
+            Log.Info("{0} service resuming", ServiceName);
+            Log.Info("-------------------------");
         }
 
         /// <summary>
@@ -329,8 +375,11 @@ namespace AutoTx
                 GC.Collect();
             }
             catch (Exception ex) {
-                writeLog("Error in OnTimedEvent(): " + ex.Message, true);
-                writeLogDebug("Extended Error Info (StackTrace): " + ex.StackTrace);
+                // TODO / FIXME: combine log and admin-email!
+                var msg = string.Format("Error in OnTimedEvent(): {0}", ex.Message);
+                Log.Error(msg);
+                SendAdminEmail(msg);
+                Log.Debug("Extended Error Info (StackTrace): {0}", ex.StackTrace);
             }
             finally {
                 // make sure to enable the timer again:
@@ -366,7 +415,7 @@ namespace AutoTx
                 _status.ServiceSuspended = false;
                 if (!string.IsNullOrEmpty(_status.LimitReason)) {
                     _status.LimitReason = ""; // reset to force a message on next service suspend
-                    writeLog("Service resuming operation (all parameters in valid ranges).");
+                    Log.Info("Service resuming operation (all parameters in valid ranges).");
                 }
                 return;
             }
@@ -376,7 +425,7 @@ namespace AutoTx
                 _status.ServiceSuspended = false;
                 if (!string.IsNullOrEmpty(_status.LimitReason)) {
                     _status.LimitReason = ""; // reset to force a message on next service suspend
-                    writeLog("Service resuming operation (no user logged on).");
+                    Log.Info("Service resuming operation (no user logged on).");
                 }
                 return;
             }
@@ -385,7 +434,7 @@ namespace AutoTx
             _status.ServiceSuspended = true;
             if (limitReason == _status.LimitReason)
                 return;
-            writeLog("Service suspended due to limitiations [" + limitReason + "].");
+            Log.Info("Service suspended due to limitiations [{0}].", limitReason);
             _status.LimitReason = limitReason;
         }
 
@@ -394,7 +443,6 @@ namespace AutoTx
         /// </summary>
         public void RunMainTasks() {
             // mandatory tasks, run on every call:
-            CheckLogSize();
             CheckFreeDiskSpace();
             UpdateServiceState();
 
@@ -433,7 +481,10 @@ namespace AutoTx
                 username = (string) collection.Cast<ManagementBaseObject>().First()["UserName"];
             }
             catch (Exception ex) {
-                writeLog("Error in getCurrentUsername(): " + ex.Message, true);
+                // TODO / FIXME: combine log and admin-email!
+                var msg = string.Format("Error in getCurrentUsername(): {0}", ex.Message);
+                Log.Error(msg);
+                SendAdminEmail(msg);
             }
             return username == "";
         }
@@ -454,7 +505,7 @@ namespace AutoTx
                 }
             }
             catch (Exception ex) {
-                writeLog("Can't find email address for " + username + ": " + ex.Message);
+                Log.Warn("Can't find email address for {0}: {1}", username, ex.Message);
             }
             return "";
         }
@@ -473,7 +524,7 @@ namespace AutoTx
                 }
             }
             catch (Exception ex) {
-                writeLog("Can't find full name for " + username + ": " + ex.Message);
+                Log.Warn("Can't find full name for {0}: {1}", username, ex.Message);
             }
             return "";
         }
@@ -548,13 +599,13 @@ namespace AutoTx
             // having no subdirectories should not happen in theory - in practice it could e.g. if
             // an admin is moving around stuff while the service is operating, so better be safe:
             if (subdirs.Length == 0) {
-                writeLog("WARNING: empty processing directory found: " + queued[0].Name);
+                Log.Warn("WARNING: empty processing directory found: {0}", queued[0].Name);
                 try {
                     queued[0].Delete();
-                    writeLogDebug("Removed empty directory: " + queued[0].Name);
+                    Log.Debug("Removed empty directory: {0}", queued[0].Name);
                 }
                 catch (Exception ex) {
-                    writeLog("Error deleting directory: " + queued[0].Name + " - " + ex.Message);
+                    Log.Error("Error deleting directory: {0} - {1}", queued[0].Name, ex.Message);
                 }
                 return;
             }
@@ -564,7 +615,7 @@ namespace AutoTx
                 StartTransfer(subdirs[0].FullName);
             }
             catch (Exception ex) {
-                writeLog("Error checking for data to be transferred: " + ex.Message);
+                Log.Error("Error checking for data to be transferred: {0}", ex.Message);
                 throw;
             }
         }
@@ -578,7 +629,7 @@ namespace AutoTx
                 if (IncomingDirIsEmpty(userDir))
                     continue;
 
-                writeLog("Found new files in " + userDir.FullName);
+                Log.Info("Found new files in [{0}]", userDir.FullName);
                 MoveToManagedLocation(userDir);
             }
         }
@@ -597,7 +648,7 @@ namespace AutoTx
                 return;
             
             if (_status.CurrentTargetTmp.Length > 0) {
-                writeLogDebug("Finalizing transfer, cleaning up target storage location...");
+                Log.Debug("Finalizing transfer, cleaning up target storage location...");
                 var finalDst = DestinationPath(_status.CurrentTargetTmp);
                 if (!string.IsNullOrWhiteSpace(finalDst)) {
                     if (MoveAllSubDirs(new DirectoryInfo(ExpandCurrentTargetTmp()), finalDst, true)) {
@@ -607,7 +658,7 @@ namespace AutoTx
             }
 
             if (_status.CurrentTransferSrc.Length > 0) {
-                writeLogDebug("Finalizing transfer, moving local data to grace location...");
+                Log.Debug("Finalizing transfer, moving local data to grace location...");
                 MoveToGraceLocation();
                 SendTransferCompletedMail();
                 _status.CurrentTransferSrc = ""; // cleanup completed, so reset CurrentTransferSrc
@@ -630,8 +681,8 @@ namespace AutoTx
                 _status.TransferInProgress == false)
                 return;
 
-            writeLogDebug("Resuming interrupted transfer from '" + _status.CurrentTransferSrc +
-                          "' to '" + ExpandCurrentTargetTmp() + "'");
+            Log.Debug("Resuming interrupted transfer from [{0}] to [{1}]",
+                _status.CurrentTransferSrc, ExpandCurrentTargetTmp());
             StartTransfer(_status.CurrentTransferSrc);
         }
 
@@ -665,7 +716,7 @@ namespace AutoTx
                 return filesInTree.Length == 0;
             }
             catch (Exception e) {
-                writeLog("Error accessing directories: " + e.Message);
+                Log.Error("Error accessing directories: {0}", e.Message);
             }
             // if nothing triggered before, we pretend the dir is empty:
             return true;
@@ -683,21 +734,21 @@ namespace AutoTx
                 if (fileList.Length > 1 ||
                     (string.IsNullOrEmpty(_config.MarkerFile) && fileList.Length > 0)) {
                     if (Directory.Exists(orphanedDir)) {
-                        writeLog("Orphaned directory already exists, skipping individual files.");
+                        Log.Info("Orphaned directory already exists, skipping individual files.");
                         return;
                     }
-                    writeLogDebug("Found individual files, collecting them in 'orphaned' folder.");
+                    Log.Debug("Found individual files, collecting them in 'orphaned' folder.");
                     CreateNewDirectory(orphanedDir, false);
                 }
                 foreach (var file in fileList) {
                     if (file.Name.Equals(_config.MarkerFile))
                         continue;
-                    writeLogDebug("Collecting orphan: " + file.Name);
+                    Log.Debug("Collecting orphan: [{0}]", file.Name);
                     file.MoveTo(Path.Combine(orphanedDir, file.Name));
                 }
             }
             catch (Exception ex) {
-                writeLog("Error collecting orphaned files: " + ex.Message + ex.StackTrace);
+                Log.Error("Error collecting orphaned files: {0}\n{1}", ex.Message, ex.StackTrace);
             }
         }
 
@@ -707,6 +758,7 @@ namespace AutoTx
         /// </summary>
         private void MoveToManagedLocation(DirectoryInfo userDir) {
             string errMsg;
+            string logMsg;  // TODO / FIXME: cleanup var after fixing mail-logging
             try {
                 // first check for individual files and collect them:
                 CollectOrphanedFiles(userDir);
@@ -717,7 +769,10 @@ namespace AutoTx
 
                 // if the user has no directory on the destination move to UNMATCHED instead:
                 if (string.IsNullOrWhiteSpace(DestinationPath(userDir.Name))) {
-                    writeLog("Found unmatched incoming dir: " + userDir.Name, true);
+                    // TODO / FIXME: combine log and admin-email!
+                    logMsg = string.Format("Found unmatched incoming dir: {0}", userDir.Name);
+                    Log.Error(logMsg);
+                    SendAdminEmail(logMsg);
                     target = "UNMATCHED";
                 }
                 
@@ -735,7 +790,10 @@ namespace AutoTx
             catch (Exception ex) {
                 errMsg = ex.Message;
             }
-            writeLog("MoveToManagedLocation(" + userDir.FullName + ") failed: " + errMsg, true);
+            // TODO / FIXME: combine log and admin-email!
+            logMsg = string.Format("MoveToManagedLocation({0}) failed: {1}", userDir.FullName, errMsg);
+            Log.Error(logMsg);
+            SendAdminEmail(logMsg);
         }
 
         /// <summary>
@@ -762,7 +820,7 @@ namespace AutoTx
                         sourceDirectory.Parent.Delete();
                     // check age and size of existing folders in the grace location after
                     // a transfer has completed, trigger a notification if necessary:
-                    writeLogDebug(GraceLocationSummary(_config.GracePeriod));
+                    Log.Debug(GraceLocationSummary(_config.GracePeriod));
                     return;
                 }
                 errMsg = "unable to move " + sourceDirectory.FullName;
@@ -770,7 +828,10 @@ namespace AutoTx
             catch (Exception ex) {
                 errMsg = ex.Message;
             }
-            writeLog("MoveToGraceLocation() failed: " + errMsg, true);
+            // TODO / FIXME: combine log and admin-email!
+            var msg = string.Format("MoveToGraceLocation() failed: {0}", errMsg);
+            Log.Error(msg);
+            SendAdminEmail(msg);
         }
 
         /// <summary>
@@ -784,12 +845,12 @@ namespace AutoTx
         /// <returns>True on success, false otherwise.</returns>
         private bool MoveAllSubDirs(DirectoryInfo sourceDir, string destPath, bool resetAcls = false) {
             // TODO: check whether _transferState should be adjusted while moving dirs!
-            writeLogDebug("MoveAllSubDirs: " + sourceDir.FullName + " to " + destPath);
+            Log.Debug("MoveAllSubDirs: [{0}] to [{1}]", sourceDir.FullName, destPath);
             try {
                 // make sure the target directory that should hold all subdirectories to
                 // be moved is existing:
                 if (string.IsNullOrEmpty(CreateNewDirectory(destPath, false))) {
-                    writeLog("WARNING: destination path doesn't exist: " + destPath);
+                    Log.Warn("WARNING: destination path doesn't exist: {0}", destPath);
                     return false;
                 }
 
@@ -798,7 +859,7 @@ namespace AutoTx
                     // make sure NOT to overwrite the subdirectories:
                     if (Directory.Exists(target))
                         target += "_" + TimeUtils.Timestamp();
-                    writeLogDebug(" - " + subDir.Name + " > " + target);
+                    Log.Debug(" - [{0}] > [{1}]", subDir.Name, target);
                     subDir.MoveTo(target);
 
                     if (resetAcls && _config.EnforceInheritedACLs) {
@@ -806,19 +867,21 @@ namespace AutoTx
                             var acl = Directory.GetAccessControl(target);
                             acl.SetAccessRuleProtection(false, false);
                             Directory.SetAccessControl(target, acl);
-                            writeLogDebug("Successfully reset inherited ACLs on " + target);
+                            Log.Debug("Successfully reset inherited ACLs on [{0}]", target);
                         }
                         catch (Exception ex) {
-                            writeLog("Error resetting inherited ACLs on " + target + ":\n" +
-                                ex.Message);
+                            Log.Error("Error resetting inherited ACLs on [{0}]:\n{1}",
+                                target, ex.Message);
                         }
                     }
                 }
             }
             catch (Exception ex) {
-                writeLog("Error moving directories: " + ex.Message + "\n" +
-                         sourceDir.FullName + "\n" +
-                         destPath, true);
+                // TODO / FIXME: combine log and admin-email!
+                var msg = string.Format("Error moving directories: [{0}] > [{1}]\n{2}",
+                    sourceDir.FullName, destPath, ex.Message);
+                Log.Error(msg);
+                SendAdminEmail(msg);
                 return false;
             }
             return true;
@@ -845,11 +908,14 @@ namespace AutoTx
                     dirPath = dirPath + "_" + TimeUtils.Timestamp();
                 }
                 Directory.CreateDirectory(dirPath);
-                writeLogDebug("Created directory: " + dirPath);
+                Log.Debug("Created directory: [{0}]", dirPath);
                 return dirPath;
             }
             catch (Exception ex) {
-                writeLog("Error in CreateNewDirectory(" + dirPath + "): " + ex.Message, true);
+                // TODO / FIXME: combine log and admin-email!
+                var msg = string.Format("Error in CreateNewDirectory({0}): {1}", dirPath, ex.Message);
+                Log.Error(msg);
+                SendAdminEmail(msg);
             }
             return "";
         }
@@ -861,7 +927,7 @@ namespace AutoTx
         /// <returns>True if existing or creation was successful, false otherwise.</returns>
         private bool CheckForDirectory(string path) {
             if (string.IsNullOrWhiteSpace(path)) {
-                writeLog("ERROR: CheckForDirectory() parameter must not be empty!");
+                Log.Error("ERROR: CheckForDirectory() parameter must not be empty!");
                 return false;
             }
             return CreateNewDirectory(path, false) == path;
@@ -963,8 +1029,8 @@ namespace AutoTx
                         size = FsUtils.GetDirectorySize(subdir.FullName) / MegaBytes;
                     }
                     catch (Exception ex) {
-                        writeLog("ERROR getting directory size of " + subdir.FullName +
-                            " - " + ex.Message);
+                        Log.Error("ERROR getting directory size of [{0}]: {1}",
+                            subdir.FullName, ex.Message);
                     }
                     expired.Add(new Tuple<DirectoryInfo, long, int>(subdir, size, age));
                 }
@@ -987,8 +1053,8 @@ namespace AutoTx
                     CultureInfo.InvariantCulture);
             }
             catch (Exception ex) {
-                writeLogDebug("ERROR parsing timestamp from directory name '" +
-                              dir.Name + "', skipping: " + ex.Message);
+                Log.Warn("ERROR parsing timestamp from directory name [{0}], skipping: {1}",
+                    dir.Name, ex.Message);
                 return -1;
             }
             return (baseTime - dirTimestamp).Days;
diff --git a/AutoTx/Email.cs b/AutoTx/Email.cs
index 8cee0dd..e036f4b 100644
--- a/AutoTx/Email.cs
+++ b/AutoTx/Email.cs
@@ -18,16 +18,16 @@ namespace AutoTx
         public void SendEmail(string recipient, string subject, string body) {
             subject = _config.EmailPrefix + subject;
             if (string.IsNullOrEmpty(_config.SmtpHost)) {
-                writeLogDebug("SendEmail: " + subject + "\n" + body);
+                Log.Debug("SendEmail: {0}\n{1}", subject, body);
                 return;
             }
             if (!recipient.Contains(@"@")) {
-                writeLogDebug("Invalid recipient, trying to resolve via AD: " + recipient);
+                Log.Debug("Invalid recipient, trying to resolve via AD: {0}", recipient);
                 recipient = GetEmailAddress(recipient);
             }
             if (string.IsNullOrWhiteSpace(recipient)) {
-                writeLogDebug("Invalid or empty recipient given, NOT sending email!");
-                writeLogDebug("SendEmail: " + subject + "\n" + body);
+                Log.Info("Invalid or empty recipient given, NOT sending email!");
+                Log.Debug("SendEmail: {0}\n{1}", subject, body);
                 return;
             }
             try {
@@ -47,9 +47,8 @@ namespace AutoTx
                 smtpClient.Send(mail);
             }
             catch (Exception ex) {
-                writeLog("Error in SendEmail(): " + ex.Message + "\n" +
-                         "InnerException: " + ex.InnerException + "\n" +
-                         "StackTrace: " + ex.StackTrace);
+                Log.Error("Error in SendEmail(): {0}\nInnerException: {1}\nStackTrace: {2}",
+                    ex.Message, ex.InnerException, ex.StackTrace);
             }
         }
 
@@ -82,8 +81,8 @@ namespace AutoTx
 
             var delta = (int)(DateTime.Now - _status.LastAdminNotification).TotalMinutes;
             if (delta < _config.AdminNotificationDelta) {
-                writeLogDebug("Suppressed admin email, interval too short (" + delta + " vs. " +
-                    _config.AdminNotificationDelta + "):\n\n" + subject + "\n" + body);
+                Log.Debug("Suppressed admin email, interval too short ({0} vs. {1}):\n\n{2}\n{3}",
+                    delta, _config.AdminNotificationDelta, subject, body);
                 return;
             }
 
@@ -110,7 +109,7 @@ namespace AutoTx
             if (delta < _config.StorageNotificationDelta)
                 return;
 
-            writeLog("WARNING: " + spaceDetails);
+            Log.Warn("WARNING: {0}", spaceDetails);
             _status.LastStorageNotification = DateTime.Now;
 
             var substitutions = new List<Tuple<string, string>> {
@@ -125,7 +124,10 @@ namespace AutoTx
                 SendEmail(_config.AdminEmailAdress, subject, body);
             }
             catch (Exception ex) {
-                writeLog("Error loading email template: " + ex.Message, true);
+                // TODO / FIXME: combine log and admin-email!
+                var msg = string.Format("Error loading email template: {0}", ex.Message);
+                Log.Error(msg);
+                SendAdminEmail(msg);
             }
         }
 
@@ -158,10 +160,13 @@ namespace AutoTx
             try {
                 var body = LoadMailTemplate("Transfer-Success.txt", substitutions);
                 SendEmail(userDir, ServiceName + " - Transfer Notification", body);
-                writeLogDebug("Sent transfer completed notification to " + userDir);
+                Log.Debug("Sent transfer completed notification to {0}", userDir);
             }
             catch (Exception ex) {
-                writeLog("Error loading email template: " + ex.Message, true);
+                // TODO / FIXME: combine log and admin-email!
+                var msg = string.Format("Error loading email template: {0}", ex.Message);
+                Log.Error(msg);
+                SendAdminEmail(msg);
             }
         }
 
@@ -189,7 +194,10 @@ namespace AutoTx
                     body);
             }
             catch (Exception ex) {
-                writeLog("Error loading email template: " + ex.Message, true);
+                // TODO / FIXME: combine log and admin-email!
+                var msg = string.Format("Error loading email template: {0}", ex.Message);
+                Log.Error(msg);
+                SendAdminEmail(msg);
             }
         }
     }
diff --git a/AutoTx/Logging.cs b/AutoTx/Logging.cs
deleted file mode 100644
index f438fd6..0000000
--- a/AutoTx/Logging.cs
+++ /dev/null
@@ -1,81 +0,0 @@
-using System;
-using System.IO;
-
-namespace AutoTx
-{
-    public partial class AutoTx
-    {
-        #region global variables
-
-        private string _logPath;
-
-        #endregion
-
-        /// <summary>
-        /// Write a message to the log file and optionally send a mail to the admin address.
-        /// </summary>
-        /// <param name="message">The text to write into the log.</param>
-        /// <param name="notify">Send the log message to the admin email as well.</param>
-        public void writeLog(string message, bool notify = false) {
-            message = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + ": " + message;
-            using (var sw = File.AppendText(_logPath)) {
-                sw.WriteLine(message);
-            }
-            if (notify)
-                SendAdminEmail(message);
-        }
-
-        /// <summary>
-        /// Call writeLog() if debug mode is enabled, optionally send an admin notification.
-        /// </summary>
-        public void writeLogDebug(string logText, bool notify = false) {
-            if (_config.Debug)
-                writeLog("[DEBUG] " + logText, notify);
-        }
-
-        /// <summary>
-        /// Check the size of the existing logfiles and trigger rotation if necessary.
-        /// </summary>
-        public void CheckLogSize() {
-            const long logSizeLimit = 1000000; // maximum log file size in bytes
-            FileInfo logInfo;
-            try {
-                logInfo = new FileInfo(_logPath);
-            }
-            catch (Exception ex) {
-                writeLog(ex.Message);
-                return;
-            }
-            if (logInfo.Length <= logSizeLimit) return;
-            try {
-                RotateLogFiles("log file size above threshold: " + logInfo.Length + " bytes\n");
-            }
-            catch (Exception ex) {
-                writeLog(ex.Message);
-            }
-        }
-
-        /// <summary>
-        /// Rotate the existing logfiles.
-        /// </summary>
-        /// <param name="message"></param>
-        public void RotateLogFiles(string message) {
-            const int maxToKeep = 10; // number of log files to keep
-
-            for (var i = maxToKeep - 2; i >= 0; i--) {
-                var curName = _logPath + "." + i;
-                if (i == 0) curName = _logPath; // the current logfile (i==0) should not have a suffix
-                var newName = _logPath + "." + (i + 1);
-                if (!File.Exists(curName)) continue;
-                if (File.Exists(newName)) {
-                    message += "deleting: " + newName + "\n";
-                    File.Delete(newName);
-                }
-                message += "moving " + curName + " to " + newName + "\n";
-                File.Move(curName, newName);
-            }
-            // This will re-create the current log file:
-            writeLogDebug(message);
-        }
-    }
-}
\ No newline at end of file
diff --git a/AutoTx/RoboCommand.cs b/AutoTx/RoboCommand.cs
index 9c04350..ae4c4c5 100644
--- a/AutoTx/RoboCommand.cs
+++ b/AutoTx/RoboCommand.cs
@@ -72,11 +72,11 @@ namespace AutoTx
                 _roboCommand.RetryOptions.RetryCount = 0;
                 _roboCommand.RetryOptions.RetryWaitTime = 2;
                 _roboCommand.Start();
-                writeLogDebug("Transfer started, total size: " + 
-                    _status.CurrentTransferSize / MegaBytes + " MB");
+                Log.Info("Transfer started, total size: {0} MB",
+                    _status.CurrentTransferSize / MegaBytes);
             }
             catch (ManagementException ex) {
-                writeLog("Error in StartTransfer(): " + ex.Message);
+                Log.Error("Error in StartTransfer(): {0}", ex.Message);
             }
         }
 
@@ -88,10 +88,10 @@ namespace AutoTx
             if (_transferState != TxState.Active)
                 return;
 
-            writeLog("Pausing the active transfer...");
+            Log.Info("Pausing the active transfer...");
             _roboCommand.Pause();
             _transferState = TxState.Paused;
-            writeLogDebug("Transfer paused");
+            Log.Debug("Transfer paused");
         }
 
         /// <summary>
@@ -102,10 +102,10 @@ namespace AutoTx
             if (_transferState != TxState.Paused)
                 return;
 
-            writeLog("Resuming the paused transfer...");
+            Log.Info("Resuming the paused transfer...");
             _roboCommand.Resume();
             _transferState = TxState.Active;
-            writeLogDebug("Transfer resumed");
+            Log.Debug("Transfer resumed");
         }
 
         #region robocommand callbacks
@@ -123,7 +123,7 @@ namespace AutoTx
                 }
             }
             catch (Exception ex) {
-                writeLog("Error in RsFileProcessed() " + ex.Message);
+                Log.Error("Error in RsFileProcessed(): {0}", ex.Message);
             }
         }
 
@@ -135,7 +135,7 @@ namespace AutoTx
                 return;
 
             _roboCommand.Stop();
-            writeLogDebug("Transfer stopped");
+            Log.Debug("Transfer stopped");
             _transferState = TxState.Stopped;
             _roboCommand.Dispose();
             _roboCommand = new RoboCommand();
@@ -153,7 +153,7 @@ namespace AutoTx
                 return;
 
             _txProgress = progress;
-            writeLogDebug("Transfer progress " + progress + "%");
+            Log.Debug("Transfer progress {0}%", progress);
         }
 
         #endregion
diff --git a/AutoTx/SystemChecks.cs b/AutoTx/SystemChecks.cs
index 6898c00..31b7aef 100644
--- a/AutoTx/SystemChecks.cs
+++ b/AutoTx/SystemChecks.cs
@@ -22,7 +22,7 @@ namespace AutoTx
                 }
             }
             catch (Exception ex) {
-                writeLog("Error in GetFreeMemory: " + ex.Message);
+                Log.Warn("Error in GetFreeMemory: {0}", ex.Message);
             }
             return -1;
         }
@@ -42,7 +42,7 @@ namespace AutoTx
                 }
             }
             catch (Exception ex) {
-                writeLog("Error in GetCpuUsage: " + ex.Message);
+                Log.Warn("Error in GetCpuUsage: {0}", ex.Message);
             }
             return -1;
         }
@@ -58,7 +58,7 @@ namespace AutoTx
                 return dInfo.TotalFreeSpace / MegaBytes;
             }
             catch (Exception ex) {
-                writeLog("Error in GetFreeDriveSpace(" + drive + "): " + ex.Message);
+                Log.Warn("Error in GetFreeDriveSpace({0}): {1}", drive, ex.Message);
             }
             return 0;
         }
@@ -95,7 +95,7 @@ namespace AutoTx
                     }
                 }
                 catch (Exception ex) {
-                    writeLog("Error in checkProcesses() " + ex.Message);
+                    Log.Warn("Error in checkProcesses(): {0}", ex.Message);
                 }
             }
             return "";
diff --git a/AutoTx/packages.config b/AutoTx/packages.config
new file mode 100644
index 0000000..8e85834
--- /dev/null
+++ b/AutoTx/packages.config
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<packages>
+  <package id="NLog" version="4.3.11" targetFramework="net45" />
+</packages>
\ No newline at end of file
-- 
GitLab