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