From 12c16e40a7008b6c59fb6513f1b1306f2f393605 Mon Sep 17 00:00:00 2001 From: Oleg Nenashev Date: Fri, 9 Dec 2016 01:41:20 +0100 Subject: [PATCH] Logging subsystem refactoring - use log4net (#145) * Save the progress * Add log4net Log appender for Windows service events * Get rid of the IEventLogger API, we use log4net now --- .../Logging/WrapperServiceEventLogProvider.cs | 28 +++++ src/Core/ServiceWrapper/Main.cs | 109 +++++++++--------- src/Core/ServiceWrapper/winsw.csproj | 1 + .../Extensions/AbstractWinSWExtension.cs | 6 +- .../WinSWCore/Extensions/IWinSWExtension.cs | 6 +- .../Extensions/WinSWExtensionManager.cs | 49 +++++--- .../Logging/IServiceEventLogProvider.cs | 19 +++ .../Logging/ServiceEventLogAppender.cs | 43 +++++++ src/Core/WinSWCore/Util/IEventWriter.cs | 13 --- src/Core/WinSWCore/WinSWCore.csproj | 4 +- .../RunawayProcessKillerExtension.cs | 4 +- .../SharedDirectoryMapper.cs | 27 ++--- .../SharedDirectoryMapper.csproj | 15 +++ .../SharedDirectoryMapper/packages.config | 4 + .../Extensions/RunawayProcessKillerTest.cs | 10 +- .../Extensions/SharedDirectoryMapperTest.cs | 10 +- src/Test/winswTests/Util/TestLogger.cs | 19 --- src/Test/winswTests/winswTests.csproj | 1 - src/packages/repositories.config | 1 + 19 files changed, 231 insertions(+), 138 deletions(-) create mode 100644 src/Core/ServiceWrapper/Logging/WrapperServiceEventLogProvider.cs create mode 100644 src/Core/WinSWCore/Logging/IServiceEventLogProvider.cs create mode 100644 src/Core/WinSWCore/Logging/ServiceEventLogAppender.cs delete mode 100644 src/Core/WinSWCore/Util/IEventWriter.cs create mode 100644 src/Plugins/SharedDirectoryMapper/packages.config delete mode 100644 src/Test/winswTests/Util/TestLogger.cs diff --git a/src/Core/ServiceWrapper/Logging/WrapperServiceEventLogProvider.cs b/src/Core/ServiceWrapper/Logging/WrapperServiceEventLogProvider.cs new file mode 100644 index 0000000..4f44952 --- /dev/null +++ b/src/Core/ServiceWrapper/Logging/WrapperServiceEventLogProvider.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Text; +using winsw.Logging; + +namespace winsw.Logging +{ + /// + /// Implements caching of the WindowsService reference in WinSW. + /// + public class WrapperServiceEventLogProvider : IServiceEventLogProvider + { + public WrapperService service {get; set;} + + public EventLog locate() + { + WrapperService _service = service; + if (_service != null && !_service.IsShuttingDown) + { + return _service.EventLog; + } + + // By default return null + return null; + } + } +} diff --git a/src/Core/ServiceWrapper/Main.cs b/src/Core/ServiceWrapper/Main.cs index e769d4d..e09be8e 100644 --- a/src/Core/ServiceWrapper/Main.cs +++ b/src/Core/ServiceWrapper/Main.cs @@ -20,10 +20,11 @@ using WMI; using ServiceType = WMI.ServiceType; using winsw.Native; using System.Reflection; +using winsw.Logging; namespace winsw { - public class WrapperService : ServiceBase, EventLogger, IEventWriter + public class WrapperService : ServiceBase, EventLogger { private SERVICE_STATUS _wrapperServiceStatus; @@ -34,6 +35,7 @@ namespace winsw internal WinSWExtensionManager ExtensionManager { private set; get; } private static readonly ILog Log = LogManager.GetLogger("WinSW"); + private static readonly WrapperServiceEventLogProvider eventLogProvider = new WrapperServiceEventLogProvider(); /// /// Indicates to the watch dog thread that we are going to terminate the process, @@ -53,6 +55,13 @@ namespace winsw get { return Assembly.GetExecutingAssembly().GetName().Version; } } + /// + /// Indicates that the system is shutting down. + /// + public bool IsShuttingDown { + get { return _systemShuttingdown; } + } + public WrapperService(ServiceDescriptor descriptor) { _descriptor = descriptor; @@ -63,6 +72,9 @@ namespace winsw CanPauseAndContinue = false; AutoLog = true; _systemShuttingdown = false; + + // Register the event log provider + eventLogProvider.service = this; } public WrapperService() : this (new ServiceDescriptor()) @@ -135,7 +147,7 @@ namespace winsw } catch (Exception e) { - WriteEvent("Thread failed unexpectedly",e); + Log.Error("Thread failed unexpectedly",e); } }).Start(); } @@ -171,7 +183,7 @@ namespace winsw } catch (Exception e) { - WriteEvent("Failed to log event in Windows Event Log: " + message + "; Reason: ", e); + Log.Error("Failed to log event in Windows Event Log: " + message + "; Reason: ", e); } } } @@ -190,28 +202,11 @@ namespace winsw } catch (Exception e) { - WriteEvent("Failed to log event in Windows Event Log. Reason: ", e); + Log.Error("Failed to log event in Windows Event Log. Reason: ", e); } } } - private void WriteEvent(Exception exception) - { - //TODO: pass exception to logger - WriteEvent(exception.Message + "\nStacktrace:" + exception.StackTrace, Level.Error); - } - - private void WriteEvent(String message, Exception exception) - { - //TODO: pass exception to logger - WriteEvent(message + "\nMessage:" + exception.Message + "\nStacktrace:" + exception.StackTrace, Level.Error); - } - - private void WriteEvent(String message, Level logLevel = null, Exception ex = null) - { - Log.Logger.Log(GetType(), logLevel ?? Level.Info, message, ex); - } - protected override void OnStart(string[] _) { _envs = _descriptor.EnvironmentVariables; @@ -233,7 +228,7 @@ namespace winsw catch (Exception e) { LogEvent("Failed to download " + d.From + " to " + d.To + "\n" + e.Message); - WriteEvent("Failed to download " + d.From +" to "+d.To, e); + Log.Error("Failed to download " + d.From +" to "+d.To, e); // but just keep going } } @@ -250,23 +245,14 @@ namespace winsw } LogEvent("Starting " + _descriptor.Executable + ' ' + startarguments); - WriteEvent("Starting " + _descriptor.Executable + ' ' + startarguments); + Log.Info("Starting " + _descriptor.Executable + ' ' + startarguments); // Load and start extensions - ExtensionManager.LoadExtensions(this); - try - { - ExtensionManager.OnStart(this); - } - catch (ExtensionException ex) - { - LogEvent("Failed to start extension " + ex.ExtensionId + "\n" + ex.Message, EventLogEntryType.Error); - WriteEvent("Failed to start extension " + ex.ExtensionId, ex); - //TODO: Exit on error? - } + ExtensionManager.LoadExtensions(); + ExtensionManager.FireOnWrapperStarted(); LogEvent("Starting " + _descriptor.Executable + ' ' + startarguments); - WriteEvent("Starting " + _descriptor.Executable + ' ' + startarguments); + Log.Info("Starting " + _descriptor.Executable + ' ' + startarguments); StartProcess(_process, startarguments, _descriptor.Executable); ExtensionManager.FireOnProcessStarted(_process); @@ -288,7 +274,7 @@ namespace winsw } catch (Exception ex) { - WriteEvent("Shutdown exception", ex); + Log.Error("Shutdown exception", ex); } } @@ -302,7 +288,7 @@ namespace winsw } catch (Exception ex) { - WriteEvent("Stop exception", ex); + Log.Error("Cannot stop exception", ex); } } @@ -313,14 +299,14 @@ namespace winsw { string stoparguments = _descriptor.Stoparguments; LogEvent("Stopping " + _descriptor.Id); - WriteEvent("Stopping " + _descriptor.Id); + Log.Info("Stopping " + _descriptor.Id); _orderlyShutdown = true; if (stoparguments == null) { try { - WriteEvent("ProcessKill " + _process.Id); + Log.Debug("ProcessKill " + _process.Id); ProcessHelper.StopProcessAndChildren(_process.Id, _descriptor.StopTimeout, _descriptor.StopParentProcessFirst); ExtensionManager.FireOnProcessTerminated(_process); } @@ -345,29 +331,21 @@ namespace winsw StartProcess(stopProcess, stoparguments, executable); - WriteEvent("WaitForProcessToExit "+_process.Id+"+"+stopProcess.Id); + Log.Debug("WaitForProcessToExit " + _process.Id + "+" + stopProcess.Id); WaitForProcessToExit(_process); WaitForProcessToExit(stopProcess); SignalShutdownComplete(); } // Stop extensions - try - { - ExtensionManager.OnStop(this); - } - catch (ExtensionException ex) - { - LogEvent("Failed to stop extension " + ex.ExtensionId + "\n" + ex.Message, EventLogEntryType.Error); - WriteEvent("Failed to stop extension " + ex.ExtensionId, ex); - } + ExtensionManager.FireBeforeWrapperStopped(); if (_systemShuttingdown && _descriptor.BeepOnShutdown) { Console.Beep(); } - WriteEvent("Finished " + _descriptor.Id); + Log.Info("Finished " + _descriptor.Id); } private void WaitForProcessToExit(Process processoWait) @@ -458,7 +436,7 @@ namespace winsw ps.EnvironmentVariables[WinSWSystem.ENVVAR_NAME_SERVICE_ID.ToLower()] = _descriptor.Id; processToStart.Start(); - WriteEvent("Started " + processToStart.Id); + Log.Info("Started " + processToStart.Id); var priority = _descriptor.Priority; if (priority != ProcessPriorityClass.Normal) @@ -509,6 +487,7 @@ namespace winsw try { Run(args); + Log.Info("Completed. Exit code is 0"); return 0; } catch (WmiException e) @@ -554,7 +533,7 @@ namespace winsw if (isCLIMode) // CLI mode, in-service mode otherwise { - Log.Debug("Starting ServiceWrapper in CLI mode"); + Log.Info("Starting ServiceWrapper in the CLI mode"); // Get service info for the future use Win32Services svc = new WmiRoot().GetCollection(); @@ -761,17 +740,25 @@ namespace winsw throw new Exception("Unknown command: " + args[0]); } + else + { + Log.Info("Starting ServiceWrapper in the service mode"); + } Run(new WrapperService()); } private static void InitLoggers(ServiceDescriptor d, bool enableCLILogging) { + // TODO: Make logging levels configurable Level logLevel = Level.Debug; + Level eventLogLevel = Level.Warn; // Legacy format from winsw-1.x: (DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + " - " + message); PatternLayout pl = new PatternLayout { ConversionPattern = "%d %-5p - %m%n" }; pl.ActivateOptions(); + List appenders = new List(); + // wrapper.log String wrapperLogPath = Path.Combine(d.LogDirectory, d.BaseName + ".wrapper.log"); var wrapperLog = new FileAppender @@ -785,7 +772,7 @@ namespace winsw Layout = pl }; wrapperLog.ActivateOptions(); - BasicConfigurator.Configure(wrapperLog); + appenders.Add(wrapperLog); // Also display logs in CLI if required if (enableCLILogging) @@ -794,11 +781,23 @@ namespace winsw { Name = "Wrapper console log", Threshold = logLevel, - Layout = pl + Layout = pl, }; consoleAppender.ActivateOptions(); - ((Logger)Log.Logger).AddAppender(consoleAppender); + appenders.Add(consoleAppender); } + + // System log + var systemEventLogger = new ServiceEventLogAppender + { + Name = "System event log", + Threshold = eventLogLevel, + provider = eventLogProvider + }; + systemEventLogger.ActivateOptions(); + appenders.Add(systemEventLogger); + + BasicConfigurator.Configure(appenders.ToArray()); } private static string ReadPassword() diff --git a/src/Core/ServiceWrapper/winsw.csproj b/src/Core/ServiceWrapper/winsw.csproj index c24abcf..b922705 100644 --- a/src/Core/ServiceWrapper/winsw.csproj +++ b/src/Core/ServiceWrapper/winsw.csproj @@ -79,6 +79,7 @@ + diff --git a/src/Core/WinSWCore/Extensions/AbstractWinSWExtension.cs b/src/Core/WinSWCore/Extensions/AbstractWinSWExtension.cs index d1b0c40..ba1f8cf 100644 --- a/src/Core/WinSWCore/Extensions/AbstractWinSWExtension.cs +++ b/src/Core/WinSWCore/Extensions/AbstractWinSWExtension.cs @@ -9,17 +9,17 @@ namespace winsw.Extensions public abstract String DisplayName { get; } public WinSWExtensionDescriptor Descriptor { get; set; } - public virtual void Configure(ServiceDescriptor descriptor, XmlNode node, IEventWriter logger) + public virtual void Configure(ServiceDescriptor descriptor, XmlNode node) { // Do nothing } - public virtual void OnStart(IEventWriter eventWriter) + public virtual void OnWrapperStarted() { // Do nothing } - public virtual void OnStop(IEventWriter eventWriter) + public virtual void BeforeWrapperStopped() { // Do nothing } diff --git a/src/Core/WinSWCore/Extensions/IWinSWExtension.cs b/src/Core/WinSWCore/Extensions/IWinSWExtension.cs index f3e1ef1..0f11987 100644 --- a/src/Core/WinSWCore/Extensions/IWinSWExtension.cs +++ b/src/Core/WinSWCore/Extensions/IWinSWExtension.cs @@ -29,14 +29,14 @@ namespace winsw.Extensions /// /// Service descriptor /// Configuration node - void Configure(ServiceDescriptor descriptor, XmlNode node, IEventWriter logger); + void Configure(ServiceDescriptor descriptor, XmlNode node); /// /// Start handler. Called during startup of the service before the child process. /// /// Logger /// Any error during execution - void OnStart(IEventWriter logger); + void OnWrapperStarted(); /// /// Handler, which is being invoked once the child process is started. @@ -59,6 +59,6 @@ namespace winsw.Extensions /// /// Logger /// Any error during execution - void OnStop(IEventWriter logger); + void BeforeWrapperStopped(); } } diff --git a/src/Core/WinSWCore/Extensions/WinSWExtensionManager.cs b/src/Core/WinSWCore/Extensions/WinSWExtensionManager.cs index 2312c77..51cfd1a 100644 --- a/src/Core/WinSWCore/Extensions/WinSWExtensionManager.cs +++ b/src/Core/WinSWCore/Extensions/WinSWExtensionManager.cs @@ -22,26 +22,43 @@ namespace winsw.Extensions } /// - /// Starts all extensions + /// Notifies all extensions that the wrapper is being started. + /// They are supposed to run the initialization logic. + /// If any extensions fails, WinSW startup should be interrupted. /// - /// Start failure - public void OnStart(IEventWriter logger) + /// Start failure + public void FireOnWrapperStarted() { foreach (var ext in Extensions) { - ext.Value.OnStart(logger); + try + { + ext.Value.OnWrapperStarted(); + } + catch (ExtensionException ex) + { + Log.Fatal("onWrapperStarted() handler failed for " + ext.Value.DisplayName, ex); + throw ex; // Propagate error to stop the startup + } } } /// - /// Stops all extensions + /// Notifies all extensions that the wrapper is being stopped. + /// If an error happens, further extensions will be tried /// - /// Stop failure - public void OnStop(IEventWriter logger) + public void FireBeforeWrapperStopped() { foreach (var ext in Extensions) - { - ext.Value.OnStop(logger); + { + try + { + ext.Value.BeforeWrapperStopped(); + } + catch (ExtensionException ex) + { + Log.Error("beforeWrapperStopped() handler failed for " + ext.Value.DisplayName, ex); + } } } @@ -86,17 +103,17 @@ namespace winsw.Extensions //TODO: Implement loading of external extensions. Current version supports internal hack #region Extension load management - /// + /// Loads extensions according to the configuration file. /// /// Logger /// Loading failure - public void LoadExtensions(IEventWriter logger) + public void LoadExtensions() { var extensionIds = ServiceDescriptor.ExtensionIds; foreach (String extensionId in extensionIds) { - LoadExtension(extensionId, logger); + LoadExtension(extensionId); } } @@ -106,7 +123,7 @@ namespace winsw.Extensions /// Extension ID /// Logger /// Loading failure - private void LoadExtension(string id, IEventWriter logger) + private void LoadExtension(string id) { if (Extensions.ContainsKey(id)) { @@ -127,7 +144,7 @@ namespace winsw.Extensions extension.Descriptor = descriptor; try { - extension.Configure(ServiceDescriptor, configNode, logger); + extension.Configure(ServiceDescriptor, configNode); } catch (Exception ex) { // Consider any unexpected exception as fatal @@ -135,11 +152,11 @@ namespace winsw.Extensions throw ex; } Extensions.Add(id, extension); - logger.LogEvent("Extension loaded: "+id, EventLogEntryType.Information); + Log.Info("Extension loaded: " + id); } else { - logger.LogEvent("Extension is disabled: " + id, EventLogEntryType.Warning); + Log.Warn("Extension is disabled: " + id); } } diff --git a/src/Core/WinSWCore/Logging/IServiceEventLogProvider.cs b/src/Core/WinSWCore/Logging/IServiceEventLogProvider.cs new file mode 100644 index 0000000..2c66d03 --- /dev/null +++ b/src/Core/WinSWCore/Logging/IServiceEventLogProvider.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Text; + +namespace winsw.Logging +{ + /// + /// Indicates that the class may reference the event log + /// + public interface IServiceEventLogProvider + { + /// + /// Locates Event Log for the service. + /// + /// Event Log or null if it is not avilable + EventLog locate(); + } +} diff --git a/src/Core/WinSWCore/Logging/ServiceEventLogAppender.cs b/src/Core/WinSWCore/Logging/ServiceEventLogAppender.cs new file mode 100644 index 0000000..a1b864a --- /dev/null +++ b/src/Core/WinSWCore/Logging/ServiceEventLogAppender.cs @@ -0,0 +1,43 @@ +using log4net.Appender; +using log4net.Core; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Text; + +namespace winsw.Logging +{ + /// + /// Implementes service Event log appender for log4j. + /// The implementation presumes that service gets initialized after the logging. + /// + public class ServiceEventLogAppender : AppenderSkeleton + { + public IServiceEventLogProvider provider { get; set; } + + override protected void Append(LoggingEvent loggingEvent) + { + EventLog eventLog = provider.locate(); + if (eventLog != null) + { + // We write the event iff the provider is ready + eventLog.WriteEntry(loggingEvent.RenderedMessage, toEventLogEntryType(loggingEvent.Level)); + } + } + + private static EventLogEntryType toEventLogEntryType(Level level) + { + if (level.Value >= Level.Error.Value) + { + return EventLogEntryType.Error; + } + if (level.Value >= Level.Warn.Value) + { + return EventLogEntryType.Warning; + } + + // All other events will be posted as information + return EventLogEntryType.Information; + } + } +} diff --git a/src/Core/WinSWCore/Util/IEventWriter.cs b/src/Core/WinSWCore/Util/IEventWriter.cs deleted file mode 100644 index 7f49dfa..0000000 --- a/src/Core/WinSWCore/Util/IEventWriter.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; -using System.Diagnostics; - -namespace winsw.Util -{ - public interface IEventWriter - { - void LogEvent(String message); - void LogEvent(String message, EventLogEntryType type); - } -} diff --git a/src/Core/WinSWCore/WinSWCore.csproj b/src/Core/WinSWCore/WinSWCore.csproj index bac7cc4..219e5fe 100644 --- a/src/Core/WinSWCore/WinSWCore.csproj +++ b/src/Core/WinSWCore/WinSWCore.csproj @@ -54,12 +54,13 @@ + + - @@ -71,6 +72,7 @@ + diff --git a/src/Plugins/RunawayProcessKiller/RunawayProcessKillerExtension.cs b/src/Plugins/RunawayProcessKiller/RunawayProcessKillerExtension.cs index 1e436f3..0677549 100644 --- a/src/Plugins/RunawayProcessKiller/RunawayProcessKillerExtension.cs +++ b/src/Plugins/RunawayProcessKiller/RunawayProcessKillerExtension.cs @@ -43,7 +43,7 @@ namespace winsw.Plugins.RunawayProcessKiller this.Pidfile = pidfile; } - public override void Configure(ServiceDescriptor descriptor, XmlNode node, IEventWriter logger) + public override void Configure(ServiceDescriptor descriptor, XmlNode node) { // We expect the upper logic to process any errors // TODO: a better parser API for types would be useful @@ -57,7 +57,7 @@ namespace winsw.Plugins.RunawayProcessKiller /// This method checks if the PID file is stored on the disk and then terminates runaway processes if they exist. /// /// Unused logger - public override void OnStart(IEventWriter logger) + public override void OnWrapperStarted() { // Read PID file from the disk int pid; diff --git a/src/Plugins/SharedDirectoryMapper/SharedDirectoryMapper.cs b/src/Plugins/SharedDirectoryMapper/SharedDirectoryMapper.cs index 97b36a9..c5cf9e9 100644 --- a/src/Plugins/SharedDirectoryMapper/SharedDirectoryMapper.cs +++ b/src/Plugins/SharedDirectoryMapper/SharedDirectoryMapper.cs @@ -4,6 +4,7 @@ using System.Xml; using System.Diagnostics; using winsw.Extensions; using winsw.Util; +using log4net; namespace winsw.Plugins.SharedDirectoryMapper { @@ -14,6 +15,8 @@ namespace winsw.Plugins.SharedDirectoryMapper public override String DisplayName { get { return "Shared Directory Mapper"; } } + private static readonly ILog Logger = LogManager.GetLogger(typeof(SharedDirectoryMapper)); + public SharedDirectoryMapper() { } @@ -24,7 +27,7 @@ namespace winsw.Plugins.SharedDirectoryMapper _entries.Add(config); } - public override void Configure(ServiceDescriptor descriptor, XmlNode node, IEventWriter logger) + public override void Configure(ServiceDescriptor descriptor, XmlNode node) { var nodes = XmlHelper.SingleNode(node, "mapping", false).SelectNodes("map"); if (nodes != null) @@ -41,30 +44,30 @@ namespace winsw.Plugins.SharedDirectoryMapper } } - public override void OnStart(IEventWriter eventWriter) + public override void OnWrapperStarted() { foreach (SharedDirectoryMapperConfig config in _entries) { if (config.EnableMapping) { - eventWriter.LogEvent(DisplayName + ": Mapping shared directory " + config.UNCPath + " to " + config.Label, EventLogEntryType.Information); + Logger.Info(DisplayName + ": Mapping shared directory " + config.UNCPath + " to " + config.Label); try { _mapper.MapDirectory(config.Label, config.UNCPath); } catch (MapperException ex) { - HandleMappingError(config, eventWriter, ex); + HandleMappingError(config, ex); } } else { - eventWriter.LogEvent(DisplayName + ": Mapping of " + config.Label + " is disabled", EventLogEntryType.Warning); + Logger.Warn(DisplayName + ": Mapping of " + config.Label + " is disabled"); } } } - public override void OnStop(IEventWriter eventWriter) + public override void BeforeWrapperStopped() { foreach (SharedDirectoryMapperConfig config in _entries) { @@ -76,18 +79,16 @@ namespace winsw.Plugins.SharedDirectoryMapper } catch (MapperException ex) { - HandleMappingError(config, eventWriter, ex); + HandleMappingError(config, ex); } } } } - private void HandleMappingError(SharedDirectoryMapperConfig config, IEventWriter eventWriter, MapperException ex) { - String prefix = "Mapping of " + config.Label+ " "; - eventWriter.LogEvent(prefix + "STDOUT: " + ex.Process.StandardOutput.ReadToEnd(), EventLogEntryType.Information); - eventWriter.LogEvent(prefix + "STDERR: " + ex.Process.StandardError.ReadToEnd(), EventLogEntryType.Information); - - throw new ExtensionException(Descriptor.Id, DisplayName + ": " + prefix + "failed", ex); + private void HandleMappingError(SharedDirectoryMapperConfig config, MapperException ex) { + Logger.Error("Mapping of " + config.Label + " failed. STDOUT: " + ex.Process.StandardOutput.ReadToEnd() + + " \r\nSTDERR: " + ex.Process.StandardError.ReadToEnd(), ex); + throw new ExtensionException(Descriptor.Id, DisplayName + ": Mapping of " + config.Label + "failed", ex); } } } diff --git a/src/Plugins/SharedDirectoryMapper/SharedDirectoryMapper.csproj b/src/Plugins/SharedDirectoryMapper/SharedDirectoryMapper.csproj index 0b0632c..211e8b1 100644 --- a/src/Plugins/SharedDirectoryMapper/SharedDirectoryMapper.csproj +++ b/src/Plugins/SharedDirectoryMapper/SharedDirectoryMapper.csproj @@ -11,6 +11,8 @@ SharedDirectoryMapper v2.0 512 + ..\..\ + true true @@ -34,6 +36,9 @@ $(SolutionDir)..\winsw_key.snk + + ..\..\packages\log4net.2.0.3\lib\net20-full\log4net.dll + @@ -50,7 +55,17 @@ WinSWCore + + + + + + + This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + +