diff --git a/src/PowerShellEditorServices.Host/EditorServicesHost.cs b/src/PowerShellEditorServices.Host/EditorServicesHost.cs index fd1ebb761..ccabf9ba8 100644 --- a/src/PowerShellEditorServices.Host/EditorServicesHost.cs +++ b/src/PowerShellEditorServices.Host/EditorServicesHost.cs @@ -34,6 +34,7 @@ public class EditorServicesHost { #region Private Fields + private ILogger logger; private bool enableConsoleRepl; private HostDetails hostDetails; private ProfilePaths profilePaths; @@ -114,7 +115,8 @@ public EditorServicesHost( /// The minimum level of log messages to be written. public void StartLogging(string logFilePath, LogLevel logLevel) { - Logger.Initialize(logFilePath, logLevel); + this.logger = new FileLogger(logFilePath, logLevel); + Logger.Initialize(this.logger); #if CoreCLR FileVersionInfo fileVersionInfo = @@ -134,7 +136,7 @@ public void StartLogging(string logFilePath, LogLevel logLevel) string newLine = Environment.NewLine; - Logger.Write( + this.logger.Write( LogLevel.Normal, string.Format( $"PowerShell Editor Services Host v{fileVersionInfo.FileVersion} starting (pid {Process.GetCurrentProcess().Id})..." + newLine + newLine + @@ -164,12 +166,13 @@ public void StartLanguageService(int languageServicePort, ProfilePaths profilePa this.languageServiceListener = new TcpSocketServerListener( MessageProtocolType.LanguageServer, - languageServicePort); + languageServicePort, + this.logger); this.languageServiceListener.ClientConnect += this.OnLanguageServiceClientConnect; this.languageServiceListener.Start(); - Logger.Write( + this.logger.Write( LogLevel.Normal, string.Format( "Language service started, listening on port {0}", @@ -189,7 +192,8 @@ private async void OnLanguageServiceClientConnect( this.languageServer = new LanguageServer( this.editorSession, - serverChannel); + serverChannel, + this.logger); await this.editorSession.PowerShellContext.ImportCommandsModule( Path.Combine( @@ -211,12 +215,13 @@ public void StartDebugService( this.debugServiceListener = new TcpSocketServerListener( MessageProtocolType.LanguageServer, - debugServicePort); + debugServicePort, + this.logger); this.debugServiceListener.ClientConnect += OnDebugServiceClientConnect; this.debugServiceListener.Start(); - Logger.Write( + this.logger.Write( LogLevel.Normal, string.Format( "Debug service started, listening on port {0}", @@ -231,7 +236,8 @@ private async void OnDebugServiceClientConnect(object sender, TcpSocketServerCha new DebugAdapter( this.editorSession, serverChannel, - false); + false, + this.logger); } else { @@ -245,13 +251,14 @@ private async void OnDebugServiceClientConnect(object sender, TcpSocketServerCha new DebugAdapter( debugSession, serverChannel, - true); + true, + this.logger); } this.debugAdapter.SessionEnded += (obj, args) => { - Logger.Write( + this.logger.Write( LogLevel.Normal, "Previous debug session ended, restarting debug service listener..."); @@ -292,8 +299,8 @@ private EditorSession CreateSession( ProfilePaths profilePaths, bool enableConsoleRepl) { - EditorSession editorSession = new EditorSession(); - PowerShellContext powerShellContext = new PowerShellContext(); + EditorSession editorSession = new EditorSession(this.logger); + PowerShellContext powerShellContext = new PowerShellContext(this.logger); ConsoleServicePSHost psHost = new ConsoleServicePSHost( @@ -316,8 +323,8 @@ private EditorSession CreateDebugSession( ProfilePaths profilePaths, IEditorOperations editorOperations) { - EditorSession editorSession = new EditorSession(); - PowerShellContext powerShellContext = new PowerShellContext(); + EditorSession editorSession = new EditorSession(this.logger); + PowerShellContext powerShellContext = new PowerShellContext(this.logger); ConsoleServicePSHost psHost = new ConsoleServicePSHost( @@ -337,12 +344,12 @@ private EditorSession CreateDebugSession( } #if !CoreCLR - static void CurrentDomain_UnhandledException( + private void CurrentDomain_UnhandledException( object sender, UnhandledExceptionEventArgs e) { // Log the exception - Logger.Write( + this.logger.Write( LogLevel.Error, string.Format( "FATAL UNHANDLED EXCEPTION:\r\n\r\n{0}", diff --git a/src/PowerShellEditorServices.Protocol/Client/DebugAdapterClientBase.cs b/src/PowerShellEditorServices.Protocol/Client/DebugAdapterClientBase.cs index 243543841..aeab98bd8 100644 --- a/src/PowerShellEditorServices.Protocol/Client/DebugAdapterClientBase.cs +++ b/src/PowerShellEditorServices.Protocol/Client/DebugAdapterClientBase.cs @@ -7,16 +7,18 @@ using Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol; using Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol.Channel; using System.Threading.Tasks; +using Microsoft.PowerShell.EditorServices.Utility; namespace Microsoft.PowerShell.EditorServices.Protocol.Client { public class DebugAdapterClient : ProtocolEndpoint { - public DebugAdapterClient(ChannelBase clientChannel) + public DebugAdapterClient(ChannelBase clientChannel, ILogger logger) : base( clientChannel, - new MessageDispatcher(), - MessageProtocolType.DebugAdapter) + new MessageDispatcher(logger), + MessageProtocolType.DebugAdapter, + logger) { } diff --git a/src/PowerShellEditorServices.Protocol/Client/LanguageClientBase.cs b/src/PowerShellEditorServices.Protocol/Client/LanguageClientBase.cs index 7611109aa..e7d780263 100644 --- a/src/PowerShellEditorServices.Protocol/Client/LanguageClientBase.cs +++ b/src/PowerShellEditorServices.Protocol/Client/LanguageClientBase.cs @@ -7,6 +7,7 @@ using Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol; using Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol.Channel; using System.Threading.Tasks; +using Microsoft.PowerShell.EditorServices.Utility; namespace Microsoft.PowerShell.EditorServices.Protocol.Client { @@ -20,11 +21,12 @@ public abstract class LanguageClientBase : ProtocolEndpoint /// specified channel for communication. /// /// The channel to use for communication with the server. - public LanguageClientBase(ChannelBase clientChannel) + public LanguageClientBase(ChannelBase clientChannel, ILogger logger) : base( clientChannel, - new MessageDispatcher(), - MessageProtocolType.LanguageServer) + new MessageDispatcher(logger), + MessageProtocolType.LanguageServer, + logger) { } diff --git a/src/PowerShellEditorServices.Protocol/Client/LanguageServiceClient.cs b/src/PowerShellEditorServices.Protocol/Client/LanguageServiceClient.cs index 6c75eebd5..119fd09f8 100644 --- a/src/PowerShellEditorServices.Protocol/Client/LanguageServiceClient.cs +++ b/src/PowerShellEditorServices.Protocol/Client/LanguageServiceClient.cs @@ -10,6 +10,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using Microsoft.PowerShell.EditorServices.Utility; namespace Microsoft.PowerShell.EditorServices.Protocol.Client { @@ -18,8 +19,8 @@ public class LanguageServiceClient : LanguageClientBase private Dictionary cachedDiagnostics = new Dictionary(); - public LanguageServiceClient(ChannelBase clientChannel) - : base(clientChannel) + public LanguageServiceClient(ChannelBase clientChannel, ILogger logger) + : base(clientChannel, logger) { } diff --git a/src/PowerShellEditorServices.Protocol/MessageProtocol/Channel/NamedPipeClientChannel.cs b/src/PowerShellEditorServices.Protocol/MessageProtocol/Channel/NamedPipeClientChannel.cs index 68fe5387b..5477ec8b6 100644 --- a/src/PowerShellEditorServices.Protocol/MessageProtocol/Channel/NamedPipeClientChannel.cs +++ b/src/PowerShellEditorServices.Protocol/MessageProtocol/Channel/NamedPipeClientChannel.cs @@ -6,16 +6,21 @@ using System; using System.IO.Pipes; using System.Threading.Tasks; +using Microsoft.PowerShell.EditorServices.Utility; namespace Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol.Channel { public class NamedPipeClientChannel : ChannelBase { + private ILogger logger; private NamedPipeClientStream pipeClient; - public NamedPipeClientChannel(NamedPipeClientStream pipeClient) + public NamedPipeClientChannel( + NamedPipeClientStream pipeClient, + ILogger logger) { this.pipeClient = pipeClient; + this.logger = logger; } protected override void Initialize(IMessageSerializer messageSerializer) @@ -23,12 +28,14 @@ protected override void Initialize(IMessageSerializer messageSerializer) this.MessageReader = new MessageReader( this.pipeClient, - messageSerializer); + messageSerializer, + this.logger); this.MessageWriter = new MessageWriter( this.pipeClient, - messageSerializer); + messageSerializer, + this.logger); } protected override void Shutdown() @@ -41,7 +48,8 @@ protected override void Shutdown() public static async Task Connect( string pipeName, - MessageProtocolType messageProtocolType) + MessageProtocolType messageProtocolType, + ILogger logger) { var pipeClient = new NamedPipeClientStream( @@ -68,7 +76,7 @@ public static async Task Connect( } } #endif - var clientChannel = new NamedPipeClientChannel(pipeClient); + var clientChannel = new NamedPipeClientChannel(pipeClient, logger); clientChannel.Start(messageProtocolType); return clientChannel; diff --git a/src/PowerShellEditorServices.Protocol/MessageProtocol/Channel/NamedPipeServerChannel.cs b/src/PowerShellEditorServices.Protocol/MessageProtocol/Channel/NamedPipeServerChannel.cs index 080b835eb..ad4e01de9 100644 --- a/src/PowerShellEditorServices.Protocol/MessageProtocol/Channel/NamedPipeServerChannel.cs +++ b/src/PowerShellEditorServices.Protocol/MessageProtocol/Channel/NamedPipeServerChannel.cs @@ -10,11 +10,15 @@ namespace Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol.Channel { public class NamedPipeServerChannel : ChannelBase { + private ILogger logger; private NamedPipeServerStream pipeServer; - public NamedPipeServerChannel(NamedPipeServerStream pipeServer) + public NamedPipeServerChannel( + NamedPipeServerStream pipeServer, + ILogger logger) { this.pipeServer = pipeServer; + this.logger = logger; } protected override void Initialize(IMessageSerializer messageSerializer) @@ -22,12 +26,14 @@ protected override void Initialize(IMessageSerializer messageSerializer) this.MessageReader = new MessageReader( this.pipeServer, - messageSerializer); + messageSerializer, + this.logger); this.MessageWriter = new MessageWriter( this.pipeServer, - messageSerializer); + messageSerializer, + this.logger); } protected override void Shutdown() diff --git a/src/PowerShellEditorServices.Protocol/MessageProtocol/Channel/NamedPipeServerListener.cs b/src/PowerShellEditorServices.Protocol/MessageProtocol/Channel/NamedPipeServerListener.cs index 99dbb00fb..cf1cb3e4c 100644 --- a/src/PowerShellEditorServices.Protocol/MessageProtocol/Channel/NamedPipeServerListener.cs +++ b/src/PowerShellEditorServices.Protocol/MessageProtocol/Channel/NamedPipeServerListener.cs @@ -13,14 +13,17 @@ namespace Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol.Channel { public class NamedPipeServerListener : ServerListenerBase { + private ILogger logger; private string pipeName; private NamedPipeServerStream pipeServer; public NamedPipeServerListener( MessageProtocolType messageProtocolType, - string pipeName) + string pipeName, + ILogger logger) : base(messageProtocolType) { + this.logger = logger; this.pipeName = pipeName; } @@ -38,7 +41,7 @@ public override void Start() } catch (IOException e) { - Logger.Write( + this.logger.Write( LogLevel.Verbose, "Named pipe server failed to start due to exception:\r\n\r\n" + e.Message); @@ -50,11 +53,11 @@ public override void Stop() { if (this.pipeServer != null) { - Logger.Write(LogLevel.Verbose, "Named pipe server shutting down..."); + this.logger.Write(LogLevel.Verbose, "Named pipe server shutting down..."); this.pipeServer.Dispose(); - Logger.Write(LogLevel.Verbose, "Named pipe server has been disposed."); + this.logger.Write(LogLevel.Verbose, "Named pipe server has been disposed."); } } @@ -69,16 +72,17 @@ private void ListenForConnection() await this.pipeServer.WaitForConnectionAsync(); #else await Task.Factory.FromAsync( - this.pipeServer.BeginWaitForConnection, + this.pipeServer.BeginWaitForConnection, this.pipeServer.EndWaitForConnection, null); #endif this.OnClientConnect( new NamedPipeServerChannel( - this.pipeServer)); + this.pipeServer, + this.logger)); } catch (Exception e) { - Logger.WriteException( + this.logger.WriteException( "An unhandled exception occurred while listening for a named pipe client connection", e); diff --git a/src/PowerShellEditorServices.Protocol/MessageProtocol/Channel/StdioClientChannel.cs b/src/PowerShellEditorServices.Protocol/MessageProtocol/Channel/StdioClientChannel.cs index b262afa57..86fe7ce7a 100644 --- a/src/PowerShellEditorServices.Protocol/MessageProtocol/Channel/StdioClientChannel.cs +++ b/src/PowerShellEditorServices.Protocol/MessageProtocol/Channel/StdioClientChannel.cs @@ -6,6 +6,7 @@ using System.Diagnostics; using System.IO; using System.Text; +using Microsoft.PowerShell.EditorServices.Utility; namespace Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol.Channel { @@ -19,6 +20,7 @@ public class StdioClientChannel : ChannelBase private string serviceProcessPath; private string serviceProcessArguments; + private ILogger logger; private Stream inputStream; private Stream outputStream; private Process serviceProcess; @@ -35,8 +37,10 @@ public class StdioClientChannel : ChannelBase /// Optional arguments to pass to the service process executable. public StdioClientChannel( string serverProcessPath, + ILogger logger, params string[] serverProcessArguments) { + this.logger = logger; this.serviceProcessPath = serverProcessPath; if (serverProcessArguments != null) @@ -48,11 +52,6 @@ public StdioClientChannel( } } - public StdioClientChannel(Process serviceProcess) - { - this.serviceProcess = serviceProcess; - } - protected override void Initialize(IMessageSerializer messageSerializer) { this.serviceProcess = new Process @@ -84,12 +83,14 @@ protected override void Initialize(IMessageSerializer messageSerializer) this.MessageReader = new MessageReader( this.inputStream, - messageSerializer); + messageSerializer, + this.logger); this.MessageWriter = new MessageWriter( this.outputStream, - messageSerializer); + messageSerializer, + this.logger); } protected override void Shutdown() diff --git a/src/PowerShellEditorServices.Protocol/MessageProtocol/Channel/StdioServerChannel.cs b/src/PowerShellEditorServices.Protocol/MessageProtocol/Channel/StdioServerChannel.cs index 9431639cf..119f42110 100644 --- a/src/PowerShellEditorServices.Protocol/MessageProtocol/Channel/StdioServerChannel.cs +++ b/src/PowerShellEditorServices.Protocol/MessageProtocol/Channel/StdioServerChannel.cs @@ -5,6 +5,7 @@ using System.IO; using System.Text; +using Microsoft.PowerShell.EditorServices.Utility; namespace Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol.Channel { @@ -15,9 +16,15 @@ namespace Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol.Channel /// public class StdioServerChannel : ChannelBase { + private ILogger logger; private Stream inputStream; private Stream outputStream; + public StdioServerChannel(ILogger logger) + { + this.logger = logger; + } + protected override void Initialize(IMessageSerializer messageSerializer) { #if !CoreCLR @@ -34,12 +41,14 @@ protected override void Initialize(IMessageSerializer messageSerializer) this.MessageReader = new MessageReader( this.inputStream, - messageSerializer); + messageSerializer, + this.logger); this.MessageWriter = new MessageWriter( this.outputStream, - messageSerializer); + messageSerializer, + this.logger); } protected override void Shutdown() diff --git a/src/PowerShellEditorServices.Protocol/MessageProtocol/Channel/StdioServerListener.cs b/src/PowerShellEditorServices.Protocol/MessageProtocol/Channel/StdioServerListener.cs index 3097c831a..6b850035d 100644 --- a/src/PowerShellEditorServices.Protocol/MessageProtocol/Channel/StdioServerListener.cs +++ b/src/PowerShellEditorServices.Protocol/MessageProtocol/Channel/StdioServerListener.cs @@ -4,21 +4,29 @@ // using System.IO; +using Microsoft.PowerShell.EditorServices.Utility; namespace Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol.Channel { public class StdioServerListener : ServerListenerBase { - public StdioServerListener(MessageProtocolType messageProtocolType) : - base(messageProtocolType) + private ILogger logger; + + public StdioServerListener( + MessageProtocolType messageProtocolType, + ILogger logger) + : base(messageProtocolType) { + this.logger = logger; } public override void Start() { // Client is connected immediately because stdio // will buffer all I/O until we get to it - this.OnClientConnect(new StdioServerChannel()); + this.OnClientConnect( + new StdioServerChannel( + this.logger)); } public override void Stop() diff --git a/src/PowerShellEditorServices.Protocol/MessageProtocol/Channel/TcpSocketClientChannel.cs b/src/PowerShellEditorServices.Protocol/MessageProtocol/Channel/TcpSocketClientChannel.cs index a0baf9e51..f331d6879 100644 --- a/src/PowerShellEditorServices.Protocol/MessageProtocol/Channel/TcpSocketClientChannel.cs +++ b/src/PowerShellEditorServices.Protocol/MessageProtocol/Channel/TcpSocketClientChannel.cs @@ -6,16 +6,21 @@ using System.Net; using System.Net.Sockets; using System.Threading.Tasks; +using Microsoft.PowerShell.EditorServices.Utility; namespace Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol.Channel { public class TcpSocketClientChannel : ChannelBase { + private ILogger logger; private NetworkStream networkStream; - public TcpSocketClientChannel(TcpClient tcpClient) + public TcpSocketClientChannel( + TcpClient tcpClient, + ILogger logger) { this.networkStream = tcpClient.GetStream(); + this.logger = logger; } protected override void Initialize(IMessageSerializer messageSerializer) @@ -23,12 +28,14 @@ protected override void Initialize(IMessageSerializer messageSerializer) this.MessageReader = new MessageReader( this.networkStream, - messageSerializer); + messageSerializer, + this.logger); this.MessageWriter = new MessageWriter( this.networkStream, - messageSerializer); + messageSerializer, + this.logger); } protected override void Shutdown() @@ -42,12 +49,13 @@ protected override void Shutdown() public static async Task Connect( int portNumber, - MessageProtocolType messageProtocolType) + MessageProtocolType messageProtocolType, + ILogger logger) { TcpClient tcpClient = new TcpClient(); await tcpClient.ConnectAsync(IPAddress.Loopback, portNumber); - var clientChannel = new TcpSocketClientChannel(tcpClient); + var clientChannel = new TcpSocketClientChannel(tcpClient, logger); clientChannel.Start(messageProtocolType); return clientChannel; diff --git a/src/PowerShellEditorServices.Protocol/MessageProtocol/Channel/TcpSocketServerChannel.cs b/src/PowerShellEditorServices.Protocol/MessageProtocol/Channel/TcpSocketServerChannel.cs index df702046e..b41deb75b 100755 --- a/src/PowerShellEditorServices.Protocol/MessageProtocol/Channel/TcpSocketServerChannel.cs +++ b/src/PowerShellEditorServices.Protocol/MessageProtocol/Channel/TcpSocketServerChannel.cs @@ -12,13 +12,15 @@ namespace Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol.Channel { public class TcpSocketServerChannel : ChannelBase { + private ILogger logger; private TcpClient tcpClient; private NetworkStream networkStream; - public TcpSocketServerChannel(TcpClient tcpClient) + public TcpSocketServerChannel(TcpClient tcpClient, ILogger logger) { this.tcpClient = tcpClient; this.networkStream = this.tcpClient.GetStream(); + this.logger = logger; } protected override void Initialize(IMessageSerializer messageSerializer) @@ -26,12 +28,14 @@ protected override void Initialize(IMessageSerializer messageSerializer) this.MessageReader = new MessageReader( this.networkStream, - messageSerializer); + messageSerializer, + this.logger); this.MessageWriter = new MessageWriter( this.networkStream, - messageSerializer); + messageSerializer, + this.logger); } protected override void Shutdown() @@ -47,7 +51,7 @@ protected override void Shutdown() #endif this.tcpClient = null; - Logger.Write(LogLevel.Verbose, "TCP client has been closed"); + this.logger.Write(LogLevel.Verbose, "TCP client has been closed"); } } } diff --git a/src/PowerShellEditorServices.Protocol/MessageProtocol/Channel/TcpSocketServerListener.cs b/src/PowerShellEditorServices.Protocol/MessageProtocol/Channel/TcpSocketServerListener.cs index bb5266454..9299b51e2 100644 --- a/src/PowerShellEditorServices.Protocol/MessageProtocol/Channel/TcpSocketServerListener.cs +++ b/src/PowerShellEditorServices.Protocol/MessageProtocol/Channel/TcpSocketServerListener.cs @@ -13,15 +13,18 @@ namespace Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol.Channel { public class TcpSocketServerListener : ServerListenerBase { + private ILogger logger; private int portNumber; private TcpListener tcpListener; public TcpSocketServerListener( MessageProtocolType messageProtocolType, - int portNumber) + int portNumber, + ILogger logger) : base(messageProtocolType) { this.portNumber = portNumber; + this.logger = logger; } public override void Start() @@ -43,7 +46,7 @@ public override void Stop() this.tcpListener.Stop(); this.tcpListener = null; - Logger.Write(LogLevel.Verbose, "TCP listener has been stopped"); + this.logger.Write(LogLevel.Verbose, "TCP listener has been stopped"); } } @@ -57,11 +60,12 @@ private void ListenForConnection() TcpClient tcpClient = await this.tcpListener.AcceptTcpClientAsync(); this.OnClientConnect( new TcpSocketServerChannel( - tcpClient)); + tcpClient, + this.logger)); } catch (Exception e) { - Logger.WriteException( + this.logger.WriteException( "An unhandled exception occurred while listening for a TCP client connection", e); diff --git a/src/PowerShellEditorServices.Protocol/MessageProtocol/MessageDispatcher.cs b/src/PowerShellEditorServices.Protocol/MessageProtocol/MessageDispatcher.cs index 5b04c5f03..4338ca3b3 100644 --- a/src/PowerShellEditorServices.Protocol/MessageProtocol/MessageDispatcher.cs +++ b/src/PowerShellEditorServices.Protocol/MessageProtocol/MessageDispatcher.cs @@ -17,6 +17,8 @@ public class MessageDispatcher { #region Fields + private ILogger logger; + private Dictionary> requestHandlers = new Dictionary>(); @@ -25,6 +27,15 @@ public class MessageDispatcher #endregion + #region Constructors + + public MessageDispatcher(ILogger logger) + { + this.logger = logger; + } + + #endregion + #region Public Methods public void SetRequestHandler( @@ -126,7 +137,7 @@ public async Task DispatchMessage( else { // TODO: Message not supported error - Logger.Write(LogLevel.Error, $"MessageDispatcher: No handler registered for Request type '{messageToDispatch.Method}'"); + this.logger.Write(LogLevel.Error, $"MessageDispatcher: No handler registered for Request type '{messageToDispatch.Method}'"); } } else if (messageToDispatch.MessageType == MessageType.Event) @@ -139,13 +150,13 @@ public async Task DispatchMessage( else { // TODO: Message not supported error - Logger.Write(LogLevel.Error, $"MessageDispatcher: No handler registered for Event type '{messageToDispatch.Method}'"); + this.logger.Write(LogLevel.Error, $"MessageDispatcher: No handler registered for Event type '{messageToDispatch.Method}'"); } } else { // TODO: Return message not supported - Logger.Write(LogLevel.Error, $"MessageDispatcher received unknown message type of method '{messageToDispatch.Method}'"); + this.logger.Write(LogLevel.Error, $"MessageDispatcher received unknown message type of method '{messageToDispatch.Method}'"); } if (handlerToAwait != null) diff --git a/src/PowerShellEditorServices.Protocol/MessageProtocol/MessageReader.cs b/src/PowerShellEditorServices.Protocol/MessageProtocol/MessageReader.cs index 32ec9387f..5323774f8 100644 --- a/src/PowerShellEditorServices.Protocol/MessageProtocol/MessageReader.cs +++ b/src/PowerShellEditorServices.Protocol/MessageProtocol/MessageReader.cs @@ -23,8 +23,9 @@ public class MessageReader private const int CR = 0x0D; private const int LF = 0x0A; - private static string[] NewLineDelimiters = new string[] { Environment.NewLine }; + private static string[] NewLineDelimiters = new string[] { Environment.NewLine }; + private ILogger logger; private Stream inputStream; private IMessageSerializer messageSerializer; private Encoding messageEncoding; @@ -51,11 +52,13 @@ enum ReadState public MessageReader( Stream inputStream, IMessageSerializer messageSerializer, + ILogger logger, Encoding messageEncoding = null) { Validate.IsNotNull("streamReader", inputStream); Validate.IsNotNull("messageSerializer", messageSerializer); + this.logger = logger; this.inputStream = inputStream; this.messageSerializer = messageSerializer; @@ -83,7 +86,7 @@ public async Task ReadMessage() this.needsMoreData = false; // Do we need to look for message headers? - if (this.readState == ReadState.Headers && + if (this.readState == ReadState.Headers && !this.TryReadMessageHeaders()) { // If we don't have enough data to read headers yet, keep reading @@ -92,7 +95,7 @@ public async Task ReadMessage() } // Do we need to look for message content? - if (this.readState == ReadState.Content && + if (this.readState == ReadState.Content && !this.TryReadMessageContent(out messageContent)) { // If we don't have enough data yet to construct the content, keep reading @@ -108,7 +111,7 @@ public async Task ReadMessage() JObject messageObject = JObject.Parse(messageContent); // Load the message - Logger.Write( + this.logger.Write( LogLevel.Verbose, string.Format( "READ MESSAGE:\r\n\r\n{0}", @@ -129,7 +132,7 @@ private async Task ReadNextChunk() { // Double the size of the buffer Array.Resize( - ref this.messageBuffer, + ref this.messageBuffer, this.messageBuffer.Length * 2); } @@ -162,10 +165,10 @@ private bool TryReadMessageHeaders() // Scan for the final double-newline that marks the // end of the header lines - while (scanOffset + 3 < this.bufferEndOffset && - (this.messageBuffer[scanOffset] != CR || - this.messageBuffer[scanOffset + 1] != LF || - this.messageBuffer[scanOffset + 2] != CR || + while (scanOffset + 3 < this.bufferEndOffset && + (this.messageBuffer[scanOffset] != CR || + this.messageBuffer[scanOffset + 1] != LF || + this.messageBuffer[scanOffset + 2] != CR || this.messageBuffer[scanOffset + 3] != LF)) { scanOffset++; @@ -179,7 +182,7 @@ private bool TryReadMessageHeaders() this.messageHeaders = new Dictionary(); - var headers = + var headers = Encoding.ASCII .GetString(this.messageBuffer, this.readOffset, scanOffset) .Split(NewLineDelimiters, StringSplitOptions.RemoveEmptyEntries); @@ -232,19 +235,19 @@ private bool TryReadMessageContent(out string messageContent) } // Convert the message contents to a string using the specified encoding - messageContent = + messageContent = this.messageEncoding.GetString( this.messageBuffer, - this.readOffset, + this.readOffset, this.expectedContentLength); // Move the remaining bytes to the front of the buffer for the next message var remainingByteCount = this.bufferEndOffset - (this.expectedContentLength + this.readOffset); Buffer.BlockCopy( - this.messageBuffer, - this.expectedContentLength + this.readOffset, - this.messageBuffer, - 0, + this.messageBuffer, + this.expectedContentLength + this.readOffset, + this.messageBuffer, + 0, remainingByteCount); // Reset the offsets for the next read diff --git a/src/PowerShellEditorServices.Protocol/MessageProtocol/MessageWriter.cs b/src/PowerShellEditorServices.Protocol/MessageProtocol/MessageWriter.cs index b1936d40e..e196472f0 100644 --- a/src/PowerShellEditorServices.Protocol/MessageProtocol/MessageWriter.cs +++ b/src/PowerShellEditorServices.Protocol/MessageProtocol/MessageWriter.cs @@ -17,6 +17,7 @@ public class MessageWriter { #region Private Fields + private ILogger logger; private Stream outputStream; private IMessageSerializer messageSerializer; private AsyncLock writeLock = new AsyncLock(); @@ -31,11 +32,13 @@ public class MessageWriter public MessageWriter( Stream outputStream, - IMessageSerializer messageSerializer) + IMessageSerializer messageSerializer, + ILogger logger) { Validate.IsNotNull("streamWriter", outputStream); Validate.IsNotNull("messageSerializer", messageSerializer); + this.logger = logger; this.outputStream = outputStream; this.messageSerializer = messageSerializer; } @@ -56,7 +59,7 @@ public async Task WriteMessage(Message messageToWrite) messageToWrite); // Log the JSON representation of the message - Logger.Write( + this.logger.Write( LogLevel.Verbose, string.Format( "WRITE MESSAGE:\r\n\r\n{0}", diff --git a/src/PowerShellEditorServices.Protocol/MessageProtocol/ProtocolEndpoint.cs b/src/PowerShellEditorServices.Protocol/MessageProtocol/ProtocolEndpoint.cs index cbb9f60fd..4a11d4eec 100644 --- a/src/PowerShellEditorServices.Protocol/MessageProtocol/ProtocolEndpoint.cs +++ b/src/PowerShellEditorServices.Protocol/MessageProtocol/ProtocolEndpoint.cs @@ -52,6 +52,8 @@ private bool InMessageLoopThread } } + protected ILogger Logger { get; private set; } + /// /// Gets the MessageDispatcher which allows registration of /// handlers for requests, responses, and events that are @@ -72,12 +74,14 @@ private bool InMessageLoopThread public ProtocolEndpoint( ChannelBase protocolChannel, MessageDispatcher messageDispatcher, - MessageProtocolType messageProtocolType) + MessageProtocolType messageProtocolType, + ILogger logger) { this.protocolChannel = protocolChannel; this.MessageDispatcher = messageDispatcher; this.messageProtocolType = messageProtocolType; this.originalSynchronizationContext = SynchronizationContext.Current; + this.Logger = logger; } /// @@ -94,7 +98,7 @@ public async Task Start() // Listen for unhandled exceptions from the message loop this.UnhandledException += MessageDispatcher_UnhandledException; - // Start the message loop + // Start the message loop this.StartMessageLoop(); // Notify implementation about endpoint start @@ -317,7 +321,9 @@ private void StartMessageLoop() // an independent background thread. this.messageLoopThread = new AsyncContextThread("Message Dispatcher"); this.messageLoopThread - .Run(() => this.ListenForMessages(this.messageLoopCancellationToken.Token)) + .Run( + () => this.ListenForMessages(this.messageLoopCancellationToken.Token), + this.Logger) .ContinueWith(this.OnListenTaskCompleted); } diff --git a/src/PowerShellEditorServices.Protocol/Server/DebugAdapter.cs b/src/PowerShellEditorServices.Protocol/Server/DebugAdapter.cs index 8b48ddeb4..56200d1db 100644 --- a/src/PowerShellEditorServices.Protocol/Server/DebugAdapter.cs +++ b/src/PowerShellEditorServices.Protocol/Server/DebugAdapter.cs @@ -40,8 +40,9 @@ public class DebugAdapter : DebugAdapterBase public DebugAdapter( EditorSession editorSession, ChannelBase serverChannel, - bool ownsEditorSession) - : base(serverChannel, new MessageDispatcher()) + bool ownsEditorSession, + ILogger logger) + : base(serverChannel, new MessageDispatcher(logger), logger) { this.editorSession = editorSession; this.ownsEditorSession = ownsEditorSession; diff --git a/src/PowerShellEditorServices.Protocol/Server/DebugAdapterBase.cs b/src/PowerShellEditorServices.Protocol/Server/DebugAdapterBase.cs index d6c0485c8..b804ed0fe 100644 --- a/src/PowerShellEditorServices.Protocol/Server/DebugAdapterBase.cs +++ b/src/PowerShellEditorServices.Protocol/Server/DebugAdapterBase.cs @@ -7,6 +7,7 @@ using Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol; using Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol.Channel; using System.Threading.Tasks; +using Microsoft.PowerShell.EditorServices.Utility; namespace Microsoft.PowerShell.EditorServices.Protocol.Server { @@ -14,11 +15,13 @@ public abstract class DebugAdapterBase : ProtocolEndpoint { public DebugAdapterBase( ChannelBase serverChannel, - MessageDispatcher messageDispatcher) + MessageDispatcher messageDispatcher, + ILogger logger) : base( serverChannel, messageDispatcher, - MessageProtocolType.DebugAdapter) + MessageProtocolType.DebugAdapter, + logger) { } diff --git a/src/PowerShellEditorServices.Protocol/Server/LanguageServer.cs b/src/PowerShellEditorServices.Protocol/Server/LanguageServer.cs index 3854b8506..a528aad2f 100644 --- a/src/PowerShellEditorServices.Protocol/Server/LanguageServer.cs +++ b/src/PowerShellEditorServices.Protocol/Server/LanguageServer.cs @@ -49,8 +49,9 @@ public IEditorOperations EditorOperations /// public LanguageServer( EditorSession editorSession, - ChannelBase serverChannel) - : base(serverChannel, new MessageDispatcher()) + ChannelBase serverChannel, + ILogger logger) + : base(serverChannel, new MessageDispatcher(logger), logger) { this.editorSession = editorSession; this.editorSession.PowerShellContext.RunspaceChanged += PowerShellContext_RunspaceChanged; @@ -560,7 +561,8 @@ protected async Task HandleDidChangeConfigurationNotification( this.currentSettings.Update( configChangeParams.Settings.Powershell, - this.editorSession.Workspace.WorkspacePath); + this.editorSession.Workspace.WorkspacePath, + this.Logger); if (!this.profilesLoaded && this.currentSettings.EnableProfileLoading && @@ -1342,6 +1344,7 @@ private Task RunScriptDiagnostics( this.codeActionsPerFile, editorSession, eventSender, + this.Logger, existingRequestCancellation.Token), CancellationToken.None, TaskCreationOptions.None, @@ -1357,6 +1360,7 @@ private static async Task DelayThenInvokeDiagnostics( Dictionary> correctionIndex, EditorSession editorSession, EventContext eventContext, + ILogger Logger, CancellationToken cancellationToken) { await DelayThenInvokeDiagnostics( @@ -1366,6 +1370,7 @@ await DelayThenInvokeDiagnostics( correctionIndex, editorSession, eventContext.SendEvent, + Logger, cancellationToken); } @@ -1377,6 +1382,7 @@ private static async Task DelayThenInvokeDiagnostics( Dictionary> correctionIndex, EditorSession editorSession, Func, PublishDiagnosticsNotification, Task> eventSender, + ILogger Logger, CancellationToken cancellationToken) { // First of all, wait for the desired delay period before diff --git a/src/PowerShellEditorServices.Protocol/Server/LanguageServerBase.cs b/src/PowerShellEditorServices.Protocol/Server/LanguageServerBase.cs index 7846b37d7..869895e43 100644 --- a/src/PowerShellEditorServices.Protocol/Server/LanguageServerBase.cs +++ b/src/PowerShellEditorServices.Protocol/Server/LanguageServerBase.cs @@ -6,6 +6,7 @@ using Microsoft.PowerShell.EditorServices.Protocol.LanguageServer; using Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol; using Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol.Channel; +using Microsoft.PowerShell.EditorServices.Utility; using System.Threading.Tasks; namespace Microsoft.PowerShell.EditorServices.Protocol.Server @@ -16,11 +17,13 @@ public abstract class LanguageServerBase : ProtocolEndpoint public LanguageServerBase( ChannelBase serverChannel, - MessageDispatcher messageDispatcher) + MessageDispatcher messageDispatcher, + ILogger logger) : base( serverChannel, messageDispatcher, - MessageProtocolType.LanguageServer) + MessageProtocolType.LanguageServer, + logger) { this.serverChannel = serverChannel; } diff --git a/src/PowerShellEditorServices.Protocol/Server/LanguageServerSettings.cs b/src/PowerShellEditorServices.Protocol/Server/LanguageServerSettings.cs index cb90268cb..55879e8a3 100644 --- a/src/PowerShellEditorServices.Protocol/Server/LanguageServerSettings.cs +++ b/src/PowerShellEditorServices.Protocol/Server/LanguageServerSettings.cs @@ -19,12 +19,18 @@ public LanguageServerSettings() this.ScriptAnalysis = new ScriptAnalysisSettings(); } - public void Update(LanguageServerSettings settings, string workspaceRootPath) + public void Update( + LanguageServerSettings settings, + string workspaceRootPath, + ILogger logger) { if (settings != null) { this.EnableProfileLoading = settings.EnableProfileLoading; - this.ScriptAnalysis.Update(settings.ScriptAnalysis, workspaceRootPath); + this.ScriptAnalysis.Update( + settings.ScriptAnalysis, + workspaceRootPath, + logger); } } } @@ -40,7 +46,10 @@ public ScriptAnalysisSettings() this.Enable = true; } - public void Update(ScriptAnalysisSettings settings, string workspaceRootPath) + public void Update( + ScriptAnalysisSettings settings, + string workspaceRootPath, + ILogger logger) { if (settings != null) { @@ -62,7 +71,7 @@ public void Update(ScriptAnalysisSettings settings, string workspaceRootPath) // In this case we should just log an error and let // the specified settings path go through even though // it will fail to load. - Logger.Write( + logger.Write( LogLevel.Error, "Could not resolve Script Analyzer settings path due to null or empty workspaceRootPath."); } diff --git a/src/PowerShellEditorServices.Protocol/Server/PromptHandlers.cs b/src/PowerShellEditorServices.Protocol/Server/PromptHandlers.cs index ed2433fe1..ff6097c5d 100644 --- a/src/PowerShellEditorServices.Protocol/Server/PromptHandlers.cs +++ b/src/PowerShellEditorServices.Protocol/Server/PromptHandlers.cs @@ -31,7 +31,8 @@ public ChoicePromptHandler GetChoicePromptHandler() { return new ProtocolChoicePromptHandler( this.messageSender, - this.consoleService); + this.consoleService, + Logger.CurrentLogger); } public InputPromptHandler GetInputPromptHandler() @@ -50,8 +51,9 @@ internal class ProtocolChoicePromptHandler : ConsoleChoicePromptHandler public ProtocolChoicePromptHandler( IMessageSender messageSender, - ConsoleService consoleService) - : base(consoleService) + ConsoleService consoleService, + ILogger logger) + : base(consoleService, logger) { this.messageSender = messageSender; this.consoleService = consoleService; @@ -131,7 +133,9 @@ internal class ProtocolInputPromptHandler : ConsoleInputPromptHandler public ProtocolInputPromptHandler( IMessageSender messageSender, ConsoleService consoleService) - : base(consoleService) + : base( + consoleService, + Microsoft.PowerShell.EditorServices.Utility.Logger.CurrentLogger) { this.messageSender = messageSender; this.consoleService = consoleService; diff --git a/src/PowerShellEditorServices/Analysis/AnalysisService.cs b/src/PowerShellEditorServices/Analysis/AnalysisService.cs index 7d14bd4dc..ca6e98f12 100644 --- a/src/PowerShellEditorServices/Analysis/AnalysisService.cs +++ b/src/PowerShellEditorServices/Analysis/AnalysisService.cs @@ -25,6 +25,8 @@ public class AnalysisService : IDisposable #region Private Fields private const int NumRunspaces = 1; + + private ILogger logger; private RunspacePool analysisRunspacePool; private PSModuleInfo scriptAnalyzerModuleInfo; @@ -103,13 +105,16 @@ public string SettingsPath /// Creates an instance of the AnalysisService class. /// /// Path to a PSScriptAnalyzer settings file. - public AnalysisService(string settingsPath = null) + /// An ILogger implementation used for writing log messages. + public AnalysisService(string settingsPath, ILogger logger) { + this.logger = logger; + try { this.SettingsPath = settingsPath; - scriptAnalyzerModuleInfo = FindPSScriptAnalyzerModule(); + scriptAnalyzerModuleInfo = FindPSScriptAnalyzerModule(logger); var sessionState = InitialSessionState.CreateDefault2(); sessionState.ImportPSModulesFromPath(scriptAnalyzerModuleInfo.ModuleBase); @@ -131,7 +136,7 @@ public AnalysisService(string settingsPath = null) var sb = new StringBuilder(); sb.AppendLine("PSScriptAnalyzer cannot be imported, AnalysisService will be disabled."); sb.AppendLine(e.Message); - Logger.Write(LogLevel.Warning, sb.ToString()); + this.logger.Write(LogLevel.Warning, sb.ToString()); } } @@ -295,7 +300,7 @@ private async Task GetSemanticMarkersAsync( } } - private static PSModuleInfo FindPSScriptAnalyzerModule() + private static PSModuleInfo FindPSScriptAnalyzerModule(ILogger logger) { using (var ps = System.Management.Automation.PowerShell.Create()) { @@ -314,7 +319,7 @@ private static PSModuleInfo FindPSScriptAnalyzerModule() var psModuleInfo = modules == null ? null : modules.FirstOrDefault(); if (psModuleInfo != null) { - Logger.Write( + logger.Write( LogLevel.Normal, string.Format( "PSScriptAnalyzer found at {0}", @@ -323,7 +328,7 @@ private static PSModuleInfo FindPSScriptAnalyzerModule() return psModuleInfo; } - Logger.Write( + logger.Write( LogLevel.Normal, "PSScriptAnalyzer module was not found."); return null; @@ -342,7 +347,7 @@ private void EnumeratePSScriptAnalyzerRules() sb.AppendLine(rule); } - Logger.Write(LogLevel.Verbose, sb.ToString()); + this.logger.Write(LogLevel.Verbose, sb.ToString()); } } @@ -380,7 +385,7 @@ private async Task GetDiagnosticRecordsAsync( }); } - Logger.Write( + this.logger.Write( LogLevel.Verbose, String.Format("Found {0} violations", diagnosticRecords.Count())); @@ -410,7 +415,7 @@ private PSObject[] InvokePowerShell(string command, IDictionary // Two main reasons that cause the exception are: // * PSCmdlet.WriteOutput being called from another thread than Begin/Process // * CompositionContainer.ComposeParts complaining that "...Only one batch can be composed at a time" - Logger.Write(LogLevel.Error, ex.Message); + this.logger.Write(LogLevel.Error, ex.Message); } return result; diff --git a/src/PowerShellEditorServices/Console/ChoicePromptHandler.cs b/src/PowerShellEditorServices/Console/ChoicePromptHandler.cs index ea0139141..e279c5570 100644 --- a/src/PowerShellEditorServices/Console/ChoicePromptHandler.cs +++ b/src/PowerShellEditorServices/Console/ChoicePromptHandler.cs @@ -9,6 +9,7 @@ using System.Management.Automation; using System.Threading; using System.Threading.Tasks; +using Microsoft.PowerShell.EditorServices.Utility; namespace Microsoft.PowerShell.EditorServices.Console { @@ -47,6 +48,14 @@ public abstract class ChoicePromptHandler : PromptHandler #endregion + /// + /// + /// + /// An ILogger implementation used for writing log messages. + public ChoicePromptHandler(ILogger logger) : base(logger) + { + } + #region Properties /// diff --git a/src/PowerShellEditorServices/Console/ConsoleChoicePromptHandler.cs b/src/PowerShellEditorServices/Console/ConsoleChoicePromptHandler.cs index 3d740cb86..347b6232b 100644 --- a/src/PowerShellEditorServices/Console/ConsoleChoicePromptHandler.cs +++ b/src/PowerShellEditorServices/Console/ConsoleChoicePromptHandler.cs @@ -6,6 +6,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; +using Microsoft.PowerShell.EditorServices.Utility; namespace Microsoft.PowerShell.EditorServices.Console { @@ -30,7 +31,9 @@ public class ConsoleChoicePromptHandler : ChoicePromptHandler /// The IConsoleHost implementation to use for writing to the /// console. /// - public ConsoleChoicePromptHandler(IConsoleHost consoleHost) + /// An ILogger implementation used for writing log messages. + public ConsoleChoicePromptHandler(IConsoleHost consoleHost, ILogger logger) + : base(logger) { this.consoleHost = consoleHost; } diff --git a/src/PowerShellEditorServices/Console/ConsoleInputPromptHandler.cs b/src/PowerShellEditorServices/Console/ConsoleInputPromptHandler.cs index 15d3120e7..672c227a5 100644 --- a/src/PowerShellEditorServices/Console/ConsoleInputPromptHandler.cs +++ b/src/PowerShellEditorServices/Console/ConsoleInputPromptHandler.cs @@ -7,6 +7,7 @@ using System.Security; using System.Threading; using System.Threading.Tasks; +using Microsoft.PowerShell.EditorServices.Utility; namespace Microsoft.PowerShell.EditorServices.Console { @@ -31,7 +32,9 @@ public class ConsoleInputPromptHandler : InputPromptHandler /// The IConsoleHost implementation to use for writing to the /// console. /// - public ConsoleInputPromptHandler(IConsoleHost consoleHost) + /// An ILogger implementation used for writing log messages. + public ConsoleInputPromptHandler(IConsoleHost consoleHost, ILogger logger) + : base(logger) { this.consoleHost = consoleHost; } diff --git a/src/PowerShellEditorServices/Console/ConsolePromptHandlerContext.cs b/src/PowerShellEditorServices/Console/ConsolePromptHandlerContext.cs index 4b18f3fb3..f1c1a4808 100644 --- a/src/PowerShellEditorServices/Console/ConsolePromptHandlerContext.cs +++ b/src/PowerShellEditorServices/Console/ConsolePromptHandlerContext.cs @@ -4,6 +4,7 @@ // using System; +using Microsoft.PowerShell.EditorServices.Utility; namespace Microsoft.PowerShell.EditorServices.Console { @@ -15,6 +16,7 @@ public class ConsolePromptHandlerContext : IPromptHandlerContext { #region Private Fields + private ILogger logger; private IConsoleHost consoleHost; #endregion @@ -29,9 +31,13 @@ public class ConsolePromptHandlerContext : IPromptHandlerContext /// The IConsoleHost implementation to use for writing to the /// console. /// - public ConsolePromptHandlerContext(IConsoleHost consoleHost) + /// An ILogger implementation used for writing log messages. + public ConsolePromptHandlerContext( + IConsoleHost consoleHost, + ILogger logger) { this.consoleHost = consoleHost; + this.logger = logger; } #endregion @@ -47,7 +53,7 @@ public ConsolePromptHandlerContext(IConsoleHost consoleHost) /// public ChoicePromptHandler GetChoicePromptHandler() { - return new ConsoleChoicePromptHandler(this.consoleHost); + return new ConsoleChoicePromptHandler(this.consoleHost, this.logger); } /// @@ -59,7 +65,7 @@ public ChoicePromptHandler GetChoicePromptHandler() /// public InputPromptHandler GetInputPromptHandler() { - return new ConsoleInputPromptHandler(this.consoleHost); + return new ConsoleInputPromptHandler(this.consoleHost, this.logger); } #endregion diff --git a/src/PowerShellEditorServices/Console/ConsoleService.cs b/src/PowerShellEditorServices/Console/ConsoleService.cs index 06f5df236..60184f93b 100644 --- a/src/PowerShellEditorServices/Console/ConsoleService.cs +++ b/src/PowerShellEditorServices/Console/ConsoleService.cs @@ -87,7 +87,7 @@ public ConsoleService( if (defaultPromptHandlerContext == null) { defaultPromptHandlerContext = - new ConsolePromptHandlerContext(this); + new ConsolePromptHandlerContext(this, Logger.CurrentLogger); } this.promptHandlerContextStack.Push( @@ -288,7 +288,7 @@ private void InnerStartReadLoop() } else { - Logger.Write(LogLevel.Verbose, "InnerStartReadLoop called while read loop is already running"); + Logger.CurrentLogger.Write(LogLevel.Verbose, "InnerStartReadLoop called while read loop is already running"); } } } @@ -339,7 +339,7 @@ await this.consoleReadLine.ReadCommandLine( true, OutputType.Error); - Logger.WriteException("Caught exception while reading command line", e); + Logger.CurrentLogger.WriteException("Caught exception while reading command line", e); } if (commandString != null) @@ -434,7 +434,7 @@ private TPromptHandler GetPromptHandler( { if (this.activePromptHandler != null) { - Logger.Write( + Logger.CurrentLogger.Write( LogLevel.Error, "Prompt handler requested while another prompt is already active."); } diff --git a/src/PowerShellEditorServices/Console/FieldDetails.cs b/src/PowerShellEditorServices/Console/FieldDetails.cs index 9a7dc0de3..ffec536b3 100644 --- a/src/PowerShellEditorServices/Console/FieldDetails.cs +++ b/src/PowerShellEditorServices/Console/FieldDetails.cs @@ -120,7 +120,7 @@ public virtual void SetValue(object fieldValue, bool hasValue) /// complete. /// /// The field's final value. - public object GetValue() + public object GetValue(ILogger logger) { object fieldValue = this.OnGetValue(); @@ -133,7 +133,7 @@ public object GetValue() else { // This "shoudln't" happen, so log in case it does - Logger.Write( + logger.Write( LogLevel.Error, $"Cannot retrieve value for field {this.Label}"); } @@ -170,9 +170,14 @@ public virtual FieldDetails GetNextField() #region Internal Methods - internal static FieldDetails Create(FieldDescription fieldDescription) + internal static FieldDetails Create( + FieldDescription fieldDescription, + ILogger logger) { - Type fieldType = GetFieldTypeFromTypeName(fieldDescription.ParameterAssemblyFullName); + Type fieldType = + GetFieldTypeFromTypeName( + fieldDescription.ParameterAssemblyFullName, + logger); if (typeof(IList).GetTypeInfo().IsAssignableFrom(fieldType.GetTypeInfo())) { @@ -203,15 +208,17 @@ internal static FieldDetails Create(FieldDescription fieldDescription) } } - private static Type GetFieldTypeFromTypeName(string assemblyFullName) + private static Type GetFieldTypeFromTypeName( + string assemblyFullName, + ILogger logger) { - Type fieldType = typeof(string); + Type fieldType = typeof(string); if (!string.IsNullOrEmpty(assemblyFullName)) { if (!LanguagePrimitives.TryConvertTo(assemblyFullName, out fieldType)) { - Logger.Write( + logger.Write( LogLevel.Warning, string.Format( "Could not resolve type of field: {0}", diff --git a/src/PowerShellEditorServices/Console/InputPromptHandler.cs b/src/PowerShellEditorServices/Console/InputPromptHandler.cs index d59860e1a..5acade505 100644 --- a/src/PowerShellEditorServices/Console/InputPromptHandler.cs +++ b/src/PowerShellEditorServices/Console/InputPromptHandler.cs @@ -10,6 +10,7 @@ using System.Security; using System.Threading; using System.Threading.Tasks; +using Microsoft.PowerShell.EditorServices.Utility; namespace Microsoft.PowerShell.EditorServices.Console { @@ -31,6 +32,14 @@ public abstract class InputPromptHandler : PromptHandler #endregion + /// + /// + /// + /// An ILogger implementation used for writing log messages. + public InputPromptHandler(ILogger logger) : base(logger) + { + } + #region Properties /// @@ -312,7 +321,7 @@ private Dictionary GetFieldValues() foreach (FieldDetails field in this.Fields) { - fieldValues.Add(field.OriginalName, field.GetValue()); + fieldValues.Add(field.OriginalName, field.GetValue(this.Logger)); } return fieldValues; @@ -321,4 +330,3 @@ private Dictionary GetFieldValues() #endregion } } - diff --git a/src/PowerShellEditorServices/Console/PromptHandler.cs b/src/PowerShellEditorServices/Console/PromptHandler.cs index a0971561f..e32928fae 100644 --- a/src/PowerShellEditorServices/Console/PromptHandler.cs +++ b/src/PowerShellEditorServices/Console/PromptHandler.cs @@ -4,6 +4,7 @@ // using System; +using Microsoft.PowerShell.EditorServices.Utility; namespace Microsoft.PowerShell.EditorServices.Console { @@ -12,6 +13,20 @@ namespace Microsoft.PowerShell.EditorServices.Console /// public abstract class PromptHandler { + /// + /// Gets the ILogger used for this instance. + /// + protected ILogger Logger { get; private set; } + + /// + /// + /// + /// An ILogger implementation used for writing log messages. + public PromptHandler(ILogger logger) + { + this.Logger = logger; + } + /// /// Called when the active prompt should be cancelled. /// diff --git a/src/PowerShellEditorServices/Debugging/DebugService.cs b/src/PowerShellEditorServices/Debugging/DebugService.cs index 6d4c92510..0953e239c 100644 --- a/src/PowerShellEditorServices/Debugging/DebugService.cs +++ b/src/PowerShellEditorServices/Debugging/DebugService.cs @@ -29,6 +29,7 @@ public class DebugService private const string PsesGlobalVariableNamePrefix = "__psEditorServices_"; private const string TemporaryScriptFileName = "Script Listing.ps1"; + private ILogger logger; private PowerShellContext powerShellContext; private RemoteFileManager remoteFileManager; @@ -79,8 +80,9 @@ public class DebugService /// /// The PowerShellContext to use for all debugging operations. /// - public DebugService(PowerShellContext powerShellContext) - : this(powerShellContext, null) + /// An ILogger implementation used for writing log messages. + public DebugService(PowerShellContext powerShellContext, ILogger logger) + : this(powerShellContext, null, logger) { } @@ -94,12 +96,15 @@ public DebugService(PowerShellContext powerShellContext) /// /// A RemoteFileManager instance to use for accessing files in remote sessions. /// + /// An ILogger implementation used for writing log messages. public DebugService( PowerShellContext powerShellContext, - RemoteFileManager remoteFileManager) + RemoteFileManager remoteFileManager, + ILogger logger) { Validate.IsNotNull(nameof(powerShellContext), powerShellContext); + this.logger = logger; this.powerShellContext = powerShellContext; this.powerShellContext.DebuggerStop += this.OnDebuggerStop; this.powerShellContext.DebuggerResumed += this.OnDebuggerResumed; @@ -145,7 +150,7 @@ public async Task SetLineBreakpoints( { if (!this.remoteFileManager.IsUnderRemoteTempPath(scriptPath)) { - Logger.Write( + this.logger.Write( LogLevel.Verbose, $"Could not set breakpoints for local path '{scriptPath}' in a remote session."); @@ -163,7 +168,7 @@ public async Task SetLineBreakpoints( this.temporaryScriptListingPath != null && this.temporaryScriptListingPath.Equals(scriptPath, StringComparison.CurrentCultureIgnoreCase)) { - Logger.Write( + this.logger.Write( LogLevel.Verbose, $"Could not set breakpoint on temporary script listing path '{scriptPath}'."); @@ -361,7 +366,7 @@ public VariableDetailsBase[] GetVariables(int variableReferenceId) VariableDetailsBase parentVariable = this.variables[variableReferenceId]; if (parentVariable.IsExpandable) { - childVariables = parentVariable.GetChildren(); + childVariables = parentVariable.GetChildren(this.logger); foreach (var child in childVariables) { // Only add child if it hasn't already been added. @@ -439,7 +444,7 @@ public async Task SetVariable(int variableContainerReferenceId, string n Validate.IsNotNull(nameof(name), name); Validate.IsNotNull(nameof(value), value); - Logger.Write(LogLevel.Verbose, $"SetVariableRequest for '{name}' to value string (pre-quote processing): '{value}'"); + this.logger.Write(LogLevel.Verbose, $"SetVariableRequest for '{name}' to value string (pre-quote processing): '{value}'"); // An empty or whitespace only value is not a valid expression for SetVariable. if (value.Trim().Length == 0) @@ -552,7 +557,7 @@ await this.powerShellContext.ExecuteCommand( EngineIntrinsics executionContext = getExecContextResults.OfType().FirstOrDefault(); var msg = $"Setting variable '{name}' using conversion to value: {psobject ?? ""}"; - Logger.Write(LogLevel.Verbose, msg); + this.logger.Write(LogLevel.Verbose, msg); psVariable.Value = argTypeConverterAttr.Transform(executionContext, psobject); } @@ -560,14 +565,14 @@ await this.powerShellContext.ExecuteCommand( { // PSVariable is *not* strongly typed. In this case, whack the old value with the new value. var msg = $"Setting variable '{name}' directly to value: {psobject ?? ""} - previous type was {psVariable.Value?.GetType().Name ?? ""}"; - Logger.Write(LogLevel.Verbose, msg); + this.logger.Write(LogLevel.Verbose, msg); psVariable.Value = psobject; } // Use the VariableDetails.ValueString functionality to get the string representation for client debugger. // This makes the returned string consistent with the strings normally displayed for variables in the debugger. var tempVariable = new VariableDetails(psVariable); - Logger.Write(LogLevel.Verbose, $"Set variable '{name}' to: {tempVariable.ValueString ?? ""}"); + this.logger.Write(LogLevel.Verbose, $"Set variable '{name}' to: {tempVariable.ValueString ?? ""}"); return tempVariable.ValueString; } @@ -763,7 +768,7 @@ private bool AddToAutoVariables(PSObject psvariable, string scope) optionsProperty.Value as string, out variableScope)) { - Logger.Write( + this.logger.Write( LogLevel.Warning, $"Could not parse a variable's ScopedItemOptions value of '{optionsProperty.Value}'"); } @@ -949,7 +954,7 @@ private ScriptBlock GetBreakpointActionScriptBlock( { // Shouldn't get here unless someone called this with no condition and no hit count. actionScriptBlock = ScriptBlock.Create("break"); - Logger.Write(LogLevel.Warning, "No condition and no hit count specified by caller."); + this.logger.Write(LogLevel.Warning, "No condition and no hit count specified by caller."); } return actionScriptBlock; @@ -1112,7 +1117,7 @@ await this.powerShellContext.ExecuteCommand( } else { - Logger.Write( + this.logger.Write( LogLevel.Warning, $"Could not load script context"); } @@ -1195,7 +1200,7 @@ private void OnBreakpointUpdated(object sender, BreakpointUpdatedEventArgs e) if (mappedPath == null) { - Logger.Write( + this.logger.Write( LogLevel.Error, $"Could not map remote path '{scriptPath}' to a local path."); diff --git a/src/PowerShellEditorServices/Debugging/VariableContainerDetails.cs b/src/PowerShellEditorServices/Debugging/VariableContainerDetails.cs index 8f60dccfe..2c90d8891 100644 --- a/src/PowerShellEditorServices/Debugging/VariableContainerDetails.cs +++ b/src/PowerShellEditorServices/Debugging/VariableContainerDetails.cs @@ -71,7 +71,7 @@ public IDictionary Children /// Returns the details of the variable container's children. If empty, returns an empty array. /// /// - public override VariableDetailsBase[] GetChildren() + public override VariableDetailsBase[] GetChildren(ILogger logger) { var variablesArray = new VariableDetailsBase[this.children.Count]; this.children.Values.CopyTo(variablesArray, 0); diff --git a/src/PowerShellEditorServices/Debugging/VariableDetails.cs b/src/PowerShellEditorServices/Debugging/VariableDetails.cs index a4a3014f7..342f045a5 100644 --- a/src/PowerShellEditorServices/Debugging/VariableDetails.cs +++ b/src/PowerShellEditorServices/Debugging/VariableDetails.cs @@ -15,7 +15,7 @@ namespace Microsoft.PowerShell.EditorServices { /// - /// Contains details pertaining to a variable in the current + /// Contains details pertaining to a variable in the current /// debugging session. /// [DebuggerDisplay("Name = {Name}, Id = {Id}, Value = {ValueString}")] @@ -99,7 +99,7 @@ public VariableDetails(string name, object value) /// details of its children. Otherwise it returns an empty array. /// /// - public override VariableDetailsBase[] GetChildren() + public override VariableDetailsBase[] GetChildren(ILogger logger) { VariableDetails[] childVariables = null; @@ -107,7 +107,7 @@ public override VariableDetailsBase[] GetChildren() { if (this.cachedChildren == null) { - this.cachedChildren = GetChildren(this.valueObject); + this.cachedChildren = GetChildren(this.valueObject, logger); } return this.cachedChildren; @@ -126,7 +126,7 @@ public override VariableDetailsBase[] GetChildren() private static bool GetIsExpandable(object valueObject) { - if (valueObject == null) + if (valueObject == null) { return false; } @@ -138,9 +138,9 @@ private static bool GetIsExpandable(object valueObject) valueObject = psobject.BaseObject; } - Type valueType = - valueObject != null ? - valueObject.GetType() : + Type valueType = + valueObject != null ? + valueObject.GetType() : null; TypeInfo valueTypeInfo = valueType.GetTypeInfo(); @@ -172,7 +172,7 @@ private static string GetValueString(object value, bool isExpandable) { Type objType = value.GetType(); - // Get the "value" for an expandable object. + // Get the "value" for an expandable object. if (value is DictionaryEntry) { // For DictionaryEntry - display the key/value as the value. @@ -189,7 +189,7 @@ private static string GetValueString(object value, bool isExpandable) if (valueToString.Equals(objType.ToString())) { - // If the ToString() matches the type name, then display the type + // If the ToString() matches the type name, then display the type // name in PowerShell format. string shortTypeName = objType.Name; @@ -254,7 +254,7 @@ private static string InsertDimensionSize(string value, int dimensionSize) return result; } - private VariableDetails[] GetChildren(object obj) + private VariableDetails[] GetChildren(object obj, ILogger logger) { List childVariables = new List(); @@ -267,7 +267,7 @@ private VariableDetails[] GetChildren(object obj) { PSObject psObject = obj as PSObject; - if ((psObject != null) && + if ((psObject != null) && (psObject.TypeNames[0] == typeof(PSCustomObject).ToString())) { // PowerShell PSCustomObject's properties are completely defined by the ETS type system. @@ -276,7 +276,7 @@ private VariableDetails[] GetChildren(object obj) .Properties .Select(p => new VariableDetails(p))); } - else + else { // If a PSObject other than a PSCustomObject, unwrap it. if (psObject != null) @@ -296,21 +296,21 @@ private VariableDetails[] GetChildren(object obj) // We're in the realm of regular, unwrapped .NET objects if (dictionary != null) - { + { // Buckle up kids, this is a bit weird. We could not use the LINQ // operator OfType. Even though R# will squiggle the // "foreach" keyword below and offer to convert to a LINQ-expression - DON'T DO IT! // The reason is that LINQ extension methods work with objects of type // IEnumerable. Objects of type Dictionary<,>, respond to iteration via - // IEnumerable by returning KeyValuePair<,> objects. Unfortunately non-generic + // IEnumerable by returning KeyValuePair<,> objects. Unfortunately non-generic // dictionaries like HashTable return DictionaryEntry objects. // It turns out that iteration via C#'s foreach loop, operates on the variable's // type which in this case is IDictionary. IDictionary was designed to always // return DictionaryEntry objects upon iteration and the Dictionary<,> implementation // honors that when the object is reintepreted as an IDictionary object. // FYI, a test case for this is to open $PSBoundParameters when debugging a - // function that defines parameters and has been passed parameters. - // If you open the $PSBoundParameters variable node in this scenario and see nothing, + // function that defines parameters and has been passed parameters. + // If you open the $PSBoundParameters variable node in this scenario and see nothing, // this code is broken. int i = 0; foreach (DictionaryEntry entry in dictionary) @@ -343,7 +343,7 @@ private VariableDetails[] GetChildren(object obj) // we aren't loading children on the pipeline thread so // this causes an exception to be raised. In this case, // just return an empty list of children. - Logger.Write(LogLevel.Warning, $"Failed to get properties of variable {this.Name}, value invocation was attempted: {ex.Message}"); + logger.Write(LogLevel.Warning, $"Failed to get properties of variable {this.Name}, value invocation was attempted: {ex.Message}"); } return childVariables.ToArray(); diff --git a/src/PowerShellEditorServices/Debugging/VariableDetailsBase.cs b/src/PowerShellEditorServices/Debugging/VariableDetailsBase.cs index 11d466750..eb1f27c8f 100644 --- a/src/PowerShellEditorServices/Debugging/VariableDetailsBase.cs +++ b/src/PowerShellEditorServices/Debugging/VariableDetailsBase.cs @@ -3,10 +3,12 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. // +using Microsoft.PowerShell.EditorServices.Utility; + namespace Microsoft.PowerShell.EditorServices { /// - /// Defines the common details between a variable and a variable container such as a scope + /// Defines the common details between a variable and a variable container such as a scope /// in the current debugging session. /// public abstract class VariableDetailsBase @@ -47,6 +49,6 @@ public abstract class VariableDetailsBase /// details of its children. Otherwise it returns an empty array. /// /// - public abstract VariableDetailsBase[] GetChildren(); + public abstract VariableDetailsBase[] GetChildren(ILogger logger); } -} \ No newline at end of file +} diff --git a/src/PowerShellEditorServices/Language/AstOperations.cs b/src/PowerShellEditorServices/Language/AstOperations.cs index da45431bd..36a79a298 100644 --- a/src/PowerShellEditorServices/Language/AstOperations.cs +++ b/src/PowerShellEditorServices/Language/AstOperations.cs @@ -39,6 +39,7 @@ internal static class AstOperations /// /// The PowerShellContext to use for gathering completions. /// + /// An ILogger implementation used for writing log messages. /// /// A CancellationToken to cancel completion requests. /// @@ -51,6 +52,7 @@ static public async Task GetCompletions( Token[] currentTokens, int fileOffset, PowerShellContext powerShellContext, + ILogger logger, CancellationToken cancellationToken) { var type = scriptAst.Extent.StartScriptPosition.GetType(); @@ -72,7 +74,7 @@ static public async Task GetCompletions( scriptAst.Extent.StartScriptPosition, new object[] { fileOffset }); - Logger.Write( + logger.Write( LogLevel.Verbose, string.Format( "Getting completions at offset {0} (line: {1}, column: {2})", @@ -99,7 +101,7 @@ static public async Task GetCompletions( ErrorRecord errorRecord = outputObject.BaseObject as ErrorRecord; if (errorRecord != null) { - Logger.WriteException( + logger.WriteException( "Encountered an error while invoking TabExpansion2 in the debugger", errorRecord.Exception); } @@ -130,7 +132,7 @@ static public async Task GetCompletions( stopwatch.Stop(); - Logger.Write(LogLevel.Verbose, $"IntelliSense completed in {stopwatch.ElapsedMilliseconds}ms."); + logger.Write(LogLevel.Verbose, $"IntelliSense completed in {stopwatch.ElapsedMilliseconds}ms."); } } diff --git a/src/PowerShellEditorServices/Language/LanguageService.cs b/src/PowerShellEditorServices/Language/LanguageService.cs index f808a2a1d..b0a375cce 100644 --- a/src/PowerShellEditorServices/Language/LanguageService.cs +++ b/src/PowerShellEditorServices/Language/LanguageService.cs @@ -24,6 +24,7 @@ public class LanguageService { #region Private Fields + private ILogger logger; private bool areAliasesLoaded; private PowerShellContext powerShellContext; private CompletionResults mostRecentCompletions; @@ -46,11 +47,15 @@ public class LanguageService /// /// The PowerShellContext in which language service operations will be executed. /// - public LanguageService(PowerShellContext powerShellContext) + /// An ILogger implementation used for writing log messages. + public LanguageService( + PowerShellContext powerShellContext, + ILogger logger) { Validate.IsNotNull("powerShellContext", powerShellContext); this.powerShellContext = powerShellContext; + this.logger = logger; this.CmdletToAliasDictionary = new Dictionary>(StringComparer.OrdinalIgnoreCase); this.AliasToCmdletDictionary = new Dictionary(StringComparer.OrdinalIgnoreCase); @@ -96,6 +101,7 @@ await AstOperations.GetCompletions( scriptFile.ScriptTokens, fileOffset, this.powerShellContext, + this.logger, new CancellationTokenSource(DefaultWaitTimeoutMilliseconds).Token); if (commandCompletion != null) @@ -119,7 +125,7 @@ await AstOperations.GetCompletions( { // Bad completion results could return an invalid // replacement range, catch that here - Logger.Write( + this.logger.Write( LogLevel.Error, $"Caught exception while trying to create CompletionResults:\n\n{e.ToString()}"); } @@ -486,7 +492,7 @@ await CommandHelpers.GetCommandInfo( // A RuntimeException will be thrown when an invalid attribute is // on a parameter binding block and then that command/script has // its signatures resolved by typing it into a script. - Logger.WriteException("RuntimeException encountered while accessing command parameter sets", e); + this.logger.WriteException("RuntimeException encountered while accessing command parameter sets", e); return null; } diff --git a/src/PowerShellEditorServices/Session/Capabilities/DscBreakpointCapability.cs b/src/PowerShellEditorServices/Session/Capabilities/DscBreakpointCapability.cs index 175030565..bdbcb6f7b 100644 --- a/src/PowerShellEditorServices/Session/Capabilities/DscBreakpointCapability.cs +++ b/src/PowerShellEditorServices/Session/Capabilities/DscBreakpointCapability.cs @@ -79,7 +79,8 @@ public bool IsDscResourcePath(string scriptPath) public static DscBreakpointCapability CheckForCapability( RunspaceDetails runspaceDetails, - PowerShellContext powerShellContext) + PowerShellContext powerShellContext, + ILogger logger) { DscBreakpointCapability capability = null; @@ -103,12 +104,12 @@ public static DscBreakpointCapability CheckForCapability( } catch (CmdletInvocationException e) { - Logger.WriteException("Could not load the DSC module!", e); + logger.WriteException("Could not load the DSC module!", e); } if (moduleInfo != null) { - Logger.Write(LogLevel.Verbose, "Side-by-side DSC module found, gathering DSC resource paths..."); + logger.Write(LogLevel.Verbose, "Side-by-side DSC module found, gathering DSC resource paths..."); // The module was loaded, add the breakpoint capability capability = new DscBreakpointCapability(); @@ -132,7 +133,7 @@ public static DscBreakpointCapability CheckForCapability( } catch (CmdletInvocationException e) { - Logger.WriteException("Get-DscResource failed!", e); + logger.WriteException("Get-DscResource failed!", e); } if (resourcePaths != null) @@ -142,16 +143,16 @@ public static DscBreakpointCapability CheckForCapability( .Select(o => (string)o.BaseObject) .ToArray(); - Logger.Write(LogLevel.Verbose, $"DSC resources found: {resourcePaths.Count}"); + logger.Write(LogLevel.Verbose, $"DSC resources found: {resourcePaths.Count}"); } else { - Logger.Write(LogLevel.Verbose, $"No DSC resources found."); + logger.Write(LogLevel.Verbose, $"No DSC resources found."); } } else { - Logger.Write(LogLevel.Verbose, $"Side-by-side DSC module was not found."); + logger.Write(LogLevel.Verbose, $"Side-by-side DSC module was not found."); } } } diff --git a/src/PowerShellEditorServices/Session/EditorSession.cs b/src/PowerShellEditorServices/Session/EditorSession.cs index f1f16365b..e72e26588 100644 --- a/src/PowerShellEditorServices/Session/EditorSession.cs +++ b/src/PowerShellEditorServices/Session/EditorSession.cs @@ -18,6 +18,12 @@ namespace Microsoft.PowerShell.EditorServices /// public class EditorSession { + #region Private Fields + + private ILogger logger; + + #endregion + #region Properties /// @@ -73,6 +79,19 @@ public class EditorSession #endregion + #region Constructors + + /// + /// + /// + /// An ILogger implementation used for writing log messages. + public EditorSession(ILogger logger) + { + this.logger = logger; + } + + #endregion + #region Public Methods /// @@ -90,14 +109,14 @@ public void StartSession( this.ConsoleService = consoleService; this.UsesConsoleHost = this.ConsoleService.EnableConsoleRepl; - this.LanguageService = new LanguageService(this.PowerShellContext); + this.LanguageService = new LanguageService(this.PowerShellContext, this.logger); this.ExtensionService = new ExtensionService(this.PowerShellContext); - this.TemplateService = new TemplateService(this.PowerShellContext); + this.TemplateService = new TemplateService(this.PowerShellContext, this.logger); this.InstantiateAnalysisService(); // Create a workspace to contain open files - this.Workspace = new Workspace(this.PowerShellContext.LocalPowerShellVersion.Version); + this.Workspace = new Workspace(this.PowerShellContext.LocalPowerShellVersion.Version, this.logger); } /// @@ -118,11 +137,11 @@ public void StartDebugSession( this.PowerShellContext = powerShellContext; this.ConsoleService = consoleService; - this.RemoteFileManager = new RemoteFileManager(this.PowerShellContext, editorOperations); - this.DebugService = new DebugService(this.PowerShellContext, this.RemoteFileManager); + this.RemoteFileManager = new RemoteFileManager(this.PowerShellContext, editorOperations, logger); + this.DebugService = new DebugService(this.PowerShellContext, this.RemoteFileManager, logger); // Create a workspace to contain open files - this.Workspace = new Workspace(this.PowerShellContext.LocalPowerShellVersion.Version); + this.Workspace = new Workspace(this.PowerShellContext.LocalPowerShellVersion.Version, this.logger); } /// @@ -135,8 +154,8 @@ public void StartDebugService(IEditorOperations editorOperations) { if (this.DebugService == null) { - this.RemoteFileManager = new RemoteFileManager(this.PowerShellContext, editorOperations); - this.DebugService = new DebugService(this.PowerShellContext, this.RemoteFileManager); + this.RemoteFileManager = new RemoteFileManager(this.PowerShellContext, editorOperations, logger); + this.DebugService = new DebugService(this.PowerShellContext, this.RemoteFileManager, logger); } } @@ -146,11 +165,11 @@ internal void InstantiateAnalysisService(string settingsPath = null) // Script Analyzer binaries are not included. try { - this.AnalysisService = new AnalysisService(settingsPath); + this.AnalysisService = new AnalysisService(settingsPath, this.logger); } catch (FileNotFoundException) { - Logger.Write( + this.logger.Write( LogLevel.Warning, "Script Analyzer binaries not found, AnalysisService will be disabled."); } diff --git a/src/PowerShellEditorServices/Session/PowerShellContext.cs b/src/PowerShellEditorServices/Session/PowerShellContext.cs index 7a87c2363..a0c962b80 100644 --- a/src/PowerShellEditorServices/Session/PowerShellContext.cs +++ b/src/PowerShellEditorServices/Session/PowerShellContext.cs @@ -33,6 +33,7 @@ public class PowerShellContext : IDisposable, IHostSupportsInteractiveSession { #region Fields + private ILogger logger; private PowerShell powerShell; private bool ownsInitialRunspace; private RunspaceDetails initialRunspace; @@ -106,6 +107,15 @@ public RunspaceDetails CurrentRunspace #region Constructors + /// + /// + /// + /// An ILogger implementation used for writing log messages. + public PowerShellContext(ILogger logger) + { + this.logger = logger; + } + /// /// /// @@ -180,7 +190,8 @@ public void Initialize( // Get the PowerShell runtime version this.LocalPowerShellVersion = PowerShellVersionDetails.GetVersionDetails( - initialRunspace); + initialRunspace, + this.logger); this.powerShell = PowerShell.Create(); this.powerShell.Runspace = initialRunspace; @@ -196,7 +207,7 @@ public void Initialize( this.CurrentRunspace = this.initialRunspace; // Write out the PowerShell version for tracking purposes - Logger.Write( + this.logger.Write( LogLevel.Normal, string.Format( "PowerShell runtime version: {0}, edition: {1}", @@ -422,7 +433,7 @@ public async Task> ExecuteCommand( if (Thread.CurrentThread.ManagedThreadId != this.pipelineThreadId && this.pipelineExecutionTask != null) { - Logger.Write(LogLevel.Verbose, "Passing command execution to pipeline thread."); + this.logger.Write(LogLevel.Verbose, "Passing command execution to pipeline thread."); PipelineExecutionRequest executionRequest = new PipelineExecutionRequest( @@ -467,7 +478,7 @@ public async Task> ExecuteCommand( } else { - Logger.Write( + this.logger.Write( LogLevel.Verbose, string.Format( "Attempting to execute command(s):\r\n\r\n{0}", @@ -521,13 +532,13 @@ await Task.Factory.StartNew>( } errorMessages?.Append(errorMessage); - Logger.Write(LogLevel.Error, errorMessage); + this.logger.Write(LogLevel.Error, errorMessage); hadErrors = true; } else { - Logger.Write( + this.logger.Write( LogLevel.Verbose, "Execution completed successfully."); } @@ -535,7 +546,7 @@ await Task.Factory.StartNew>( } catch (PipelineStoppedException e) { - Logger.Write( + this.logger.Write( LogLevel.Error, "Pipeline stopped while executing command:\r\n\r\n" + e.ToString()); @@ -543,7 +554,7 @@ await Task.Factory.StartNew>( } catch (RuntimeException e) { - Logger.Write( + this.logger.Write( LogLevel.Error, "Runtime exception occurred while executing command:\r\n\r\n" + e.ToString()); @@ -744,7 +755,7 @@ public async Task ExecuteScriptWithArgs(string script, string arguments = null, } catch (System.Management.Automation.DriveNotFoundException e) { - Logger.Write( + this.logger.Write( LogLevel.Error, "Could not determine current filesystem location:\r\n\r\n" + e.ToString()); } @@ -858,7 +869,7 @@ public void AbortExecution() if (this.SessionState != PowerShellContextState.Aborting && this.SessionState != PowerShellContextState.Disposed) { - Logger.Write(LogLevel.Verbose, "Execution abort requested..."); + this.logger.Write(LogLevel.Verbose, "Execution abort requested..."); // Clean up the debugger if (this.IsDebuggerStopped) @@ -880,7 +891,7 @@ public void AbortExecution() } else { - Logger.Write( + this.logger.Write( LogLevel.Verbose, string.Format( $"Execution abort requested when already aborted (SessionState = {this.SessionState})")); @@ -894,7 +905,7 @@ public void AbortExecution() /// internal void BreakExecution() { - Logger.Write(LogLevel.Verbose, "Debugger break requested..."); + this.logger.Write(LogLevel.Verbose, "Debugger break requested..."); // Pause the debugger this.versionSpecificOperations.PauseDebugger( @@ -909,14 +920,14 @@ internal void ResumeDebugger(DebuggerResumeAction resumeAction) // The execution thread will clean up the task. if (!this.debuggerStoppedTask.TrySetResult(resumeAction)) { - Logger.Write( + this.logger.Write( LogLevel.Error, $"Tried to resume debugger with action {resumeAction} but the task was already completed."); } } else { - Logger.Write( + this.logger.Write( LogLevel.Error, $"Tried to resume debugger with action {resumeAction} but there was no debuggerStoppedTask."); } @@ -1025,7 +1036,7 @@ private void CloseRunspace(RunspaceDetails runspaceDetails) if (exitException != null) { - Logger.Write( + this.logger.Write( LogLevel.Error, $"Caught {exitException.GetType().Name} while exiting {runspaceDetails.Location} runspace:\r\n{exitException.ToString()}"); } @@ -1044,7 +1055,7 @@ internal void ReleaseRunspaceHandle(RunspaceHandle runspaceHandle) else { // Write the situation to the log since this shouldn't happen - Logger.Write( + this.logger.Write( LogLevel.Error, "The PowerShellContext.runspaceWaitQueue has more than one item"); } @@ -1111,7 +1122,7 @@ private void OnSessionStateChanged(object sender, SessionStateChangedEventArgs e { if (this.SessionState != PowerShellContextState.Disposed) { - Logger.Write( + this.logger.Write( LogLevel.Verbose, string.Format( "Session state changed --\r\n\r\n Old state: {0}\r\n New state: {1}\r\n Result: {2}", @@ -1124,7 +1135,7 @@ private void OnSessionStateChanged(object sender, SessionStateChangedEventArgs e } else { - Logger.Write( + this.logger.Write( LogLevel.Warning, $"Received session state change to {e.NewSessionState} when already disposed"); } @@ -1164,7 +1175,7 @@ private void OnExecutionStatusChanged( private IEnumerable ExecuteCommandInDebugger(PSCommand psCommand, bool sendOutputToHost) { - Logger.Write( + this.logger.Write( LogLevel.Verbose, string.Format( "Attempting to execute command(s) in the debugger:\r\n\r\n{0}", @@ -1387,7 +1398,7 @@ private void SetExecutionPolicy(ExecutionPolicy desiredExecutionPolicy) desiredExecutionPolicy == ExecutionPolicy.Bypass || currentPolicy == ExecutionPolicy.Undefined) { - Logger.Write( + this.logger.Write( LogLevel.Verbose, string.Format( "Setting execution policy:\r\n Current = ExecutionPolicy.{0}\r\n Desired = ExecutionPolicy.{1}", @@ -1407,7 +1418,7 @@ private void SetExecutionPolicy(ExecutionPolicy desiredExecutionPolicy) } catch (CmdletInvocationException e) { - Logger.WriteException( + this.logger.WriteException( $"An error occurred while calling Set-ExecutionPolicy, the desired policy of {desiredExecutionPolicy} may not be set.", e); } @@ -1416,7 +1427,7 @@ private void SetExecutionPolicy(ExecutionPolicy desiredExecutionPolicy) } else { - Logger.Write( + this.logger.Write( LogLevel.Verbose, string.Format( "Current execution policy: ExecutionPolicy.{0}", @@ -1438,7 +1449,7 @@ private SessionDetails GetSessionDetails(Func invokeAction) } catch (RuntimeException e) { - Logger.Write( + this.logger.Write( LogLevel.Verbose, "Runtime exception occurred while gathering runspace info:\r\n\r\n" + e.ToString()); } @@ -1534,7 +1545,7 @@ private void SetProfileVariableInCurrentRunspace(ProfilePaths profilePaths) nameof(profilePaths.CurrentUserCurrentHost), profilePaths.CurrentUserCurrentHost)); - Logger.Write( + this.logger.Write( LogLevel.Verbose, string.Format( "Setting $profile variable in runspace. Current user host profile path: {0}", @@ -1584,7 +1595,7 @@ private void HandleRunspaceStateChanged(object sender, RunspaceStateEventArgs ar private void OnDebuggerStop(object sender, DebuggerStopEventArgs e) { - Logger.Write(LogLevel.Verbose, "Debugger stopped execution."); + this.logger.Write(LogLevel.Verbose, "Debugger stopped execution."); // Set the task so a result can be set this.debuggerStoppedTask = @@ -1616,7 +1627,7 @@ private void OnDebuggerStop(object sender, DebuggerStopEventArgs e) // Raise the event for the debugger service this.DebuggerStop?.Invoke(sender, e); - Logger.Write(LogLevel.Verbose, "Starting pipeline thread message loop..."); + this.logger.Write(LogLevel.Verbose, "Starting pipeline thread message loop..."); while (true) { @@ -1631,7 +1642,7 @@ private void OnDebuggerStop(object sender, DebuggerStopEventArgs e) this.WriteOutput("", true); e.ResumeAction = localDebuggerStoppedTask.Result; - Logger.Write(LogLevel.Verbose, "Received debugger resume action " + e.ResumeAction.ToString()); + this.logger.Write(LogLevel.Verbose, "Received debugger resume action " + e.ResumeAction.ToString()); // Notify listeners that the debugger has resumed this.DebuggerResumed?.Invoke(this, e.ResumeAction); @@ -1658,7 +1669,7 @@ private void OnDebuggerStop(object sender, DebuggerStopEventArgs e) } else if (taskIndex == 1) { - Logger.Write(LogLevel.Verbose, "Received pipeline thread execution request."); + this.logger.Write(LogLevel.Verbose, "Received pipeline thread execution request."); IPipelineExecutionRequest executionRequest = localPipelineExecutionTask.Result; @@ -1667,7 +1678,7 @@ private void OnDebuggerStop(object sender, DebuggerStopEventArgs e) executionRequest.Execute().Wait(); - Logger.Write(LogLevel.Verbose, "Pipeline thread execution completed."); + this.logger.Write(LogLevel.Verbose, "Pipeline thread execution completed."); if (this.CurrentRunspace.Runspace.RunspaceAvailability == RunspaceAvailability.Available) { @@ -1761,12 +1772,12 @@ await this.powerShellContext.ExecuteCommand( private void ConfigureRunspaceCapabilities(RunspaceDetails runspaceDetails) { - DscBreakpointCapability.CheckForCapability(this.CurrentRunspace, this); + DscBreakpointCapability.CheckForCapability(this.CurrentRunspace, this, this.logger); } private void PushRunspace(RunspaceDetails newRunspaceDetails) { - Logger.Write( + this.logger.Write( LogLevel.Verbose, $"Pushing {this.CurrentRunspace.Location} ({this.CurrentRunspace.Context}), new runspace is {newRunspaceDetails.Location} ({newRunspaceDetails.Context}), connection: {newRunspaceDetails.ConnectionString}"); @@ -1869,7 +1880,7 @@ private void PopRunspace() RunspaceDetails previousRunspace = this.CurrentRunspace; this.CurrentRunspace = this.runspaceStack.Pop(); - Logger.Write( + this.logger.Write( LogLevel.Verbose, $"Popping {previousRunspace.Location} ({previousRunspace.Context}), new runspace is {this.CurrentRunspace.Location} ({this.CurrentRunspace.Context}), connection: {this.CurrentRunspace.ConnectionString}"); @@ -1896,7 +1907,7 @@ private void PopRunspace() } else { - Logger.Write( + this.logger.Write( LogLevel.Error, "Caller attempted to pop a runspace when no runspaces are on the stack."); } @@ -1931,7 +1942,8 @@ void IHostSupportsInteractiveSession.PushRunspace(Runspace runspace) this.PushRunspace( RunspaceDetails.CreateFromRunspace( runspace, - sessionDetails)); + sessionDetails, + this.logger)); } void IHostSupportsInteractiveSession.PopRunspace() diff --git a/src/PowerShellEditorServices/Session/PowerShellVersionDetails.cs b/src/PowerShellEditorServices/Session/PowerShellVersionDetails.cs index 8a3a54a4f..efd5e5feb 100644 --- a/src/PowerShellEditorServices/Session/PowerShellVersionDetails.cs +++ b/src/PowerShellEditorServices/Session/PowerShellVersionDetails.cs @@ -42,7 +42,7 @@ public class PowerShellVersionDetails /// Gets the version of the PowerShell runtime. /// public Version Version { get; private set; } - + /// /// Gets the full version string, either the ToString of the Version /// property or the GitCommitId for open-source PowerShell releases. @@ -90,8 +90,9 @@ public PowerShellVersionDetails( /// Gets the PowerShell version details for the given runspace. /// /// The runspace for which version details will be gathered. + /// An ILogger implementation used for writing log messages. /// A new PowerShellVersionDetails instance. - public static PowerShellVersionDetails GetVersionDetails(Runspace runspace) + public static PowerShellVersionDetails GetVersionDetails(Runspace runspace, ILogger logger) { Version powerShellVersion = new Version(5, 0); string versionString = null; @@ -149,7 +150,7 @@ public static PowerShellVersionDetails GetVersionDetails(Runspace runspace) } catch (Exception ex) { - Logger.Write( + logger.Write( LogLevel.Warning, "Failed to look up PowerShell version, defaulting to version 5.\r\n\r\n" + ex.ToString()); } diff --git a/src/PowerShellEditorServices/Session/RemoteFileManager.cs b/src/PowerShellEditorServices/Session/RemoteFileManager.cs index e43921b26..81e4d8f40 100644 --- a/src/PowerShellEditorServices/Session/RemoteFileManager.cs +++ b/src/PowerShellEditorServices/Session/RemoteFileManager.cs @@ -25,6 +25,7 @@ public class RemoteFileManager { #region Fields + private ILogger logger; private string remoteFilesPath; private string processTempPath; private PowerShellContext powerShellContext; @@ -100,12 +101,15 @@ public class RemoteFileManager /// /// The IEditorOperations instance to use for opening/closing files in the editor. /// + /// An ILogger implementation used for writing log messages. public RemoteFileManager( PowerShellContext powerShellContext, - IEditorOperations editorOperations) + IEditorOperations editorOperations, + ILogger logger) { Validate.IsNotNull(nameof(powerShellContext), powerShellContext); + this.logger = logger; this.powerShellContext = powerShellContext; this.powerShellContext.RunspaceChanged += HandleRunspaceChanged; @@ -176,7 +180,7 @@ public async Task FetchRemoteFile( } else { - Logger.Write( + this.logger.Write( LogLevel.Warning, $"Could not load contents of remote file '{remoteFilePath}'"); } @@ -185,7 +189,7 @@ public async Task FetchRemoteFile( } catch (IOException e) { - Logger.Write( + this.logger.Write( LogLevel.Error, $"Caught {e.GetType().Name} while attempting to get remote file at path '{remoteFilePath}'\r\n\r\n{e.ToString()}"); } @@ -211,7 +215,7 @@ public async Task SaveRemoteFile(string localFilePath) localFilePath, this.powerShellContext.CurrentRunspace); - Logger.Write( + this.logger.Write( LogLevel.Verbose, $"Saving remote file {remoteFilePath} (local path: {localFilePath})"); @@ -222,7 +226,7 @@ public async Task SaveRemoteFile(string localFilePath) } catch (IOException e) { - Logger.WriteException( + this.logger.WriteException( "Failed to read contents of local copy of remote file", e); @@ -245,7 +249,7 @@ await this.powerShellContext.ExecuteCommand( if (errorMessages.Length > 0) { - Logger.Write(LogLevel.Error, $"Remote file save failed due to an error:\r\n\r\n{errorMessages}"); + this.logger.Write(LogLevel.Error, $"Remote file save failed due to an error:\r\n\r\n{errorMessages}"); } } @@ -276,7 +280,7 @@ public string CreateTemporaryFile(string fileName, string fileContents, Runspace } catch (IOException e) { - Logger.Write( + this.logger.Write( LogLevel.Error, $"Caught {e.GetType().Name} while attempting to write temporary file at path '{temporaryFilePath}'\r\n\r\n{e.ToString()}"); @@ -432,7 +436,7 @@ private void HandlePSEventReceived(object sender, PSEventArgs args) } catch (NullReferenceException e) { - Logger.WriteException("Could not store null remote file content", e); + this.logger.WriteException("Could not store null remote file content", e); } } } @@ -474,7 +478,7 @@ private void RegisterPSEditFunction(RunspaceDetails runspaceDetails) } catch (RemoteException e) { - Logger.WriteException("Could not create psedit function.", e); + this.logger.WriteException("Could not create psedit function.", e); } } } @@ -503,7 +507,7 @@ private void RemovePSEditFunction(RunspaceDetails runspaceDetails) } catch (Exception e) when (e is RemoteException || e is PSInvalidOperationException) { - Logger.WriteException("Could not remove psedit function.", e); + this.logger.WriteException("Could not remove psedit function.", e); } } } @@ -521,7 +525,7 @@ private void TryDeleteTemporaryPath() } catch (IOException e) { - Logger.WriteException( + this.logger.WriteException( $"Could not delete temporary folder for current process: {this.processTempPath}", e); } } diff --git a/src/PowerShellEditorServices/Session/RunspaceDetails.cs b/src/PowerShellEditorServices/Session/RunspaceDetails.cs index 7a399db1e..072129598 100644 --- a/src/PowerShellEditorServices/Session/RunspaceDetails.cs +++ b/src/PowerShellEditorServices/Session/RunspaceDetails.cs @@ -183,10 +183,12 @@ internal bool HasCapability() /// /// The SessionDetails for the runspace. /// + /// An ILogger implementation used for writing log messages. /// A new RunspaceDetails instance. internal static RunspaceDetails CreateFromRunspace( Runspace runspace, - SessionDetails sessionDetails) + SessionDetails sessionDetails, + ILogger logger) { Validate.IsNotNull(nameof(runspace), runspace); Validate.IsNotNull(nameof(sessionDetails), sessionDetails); @@ -194,7 +196,7 @@ internal static RunspaceDetails CreateFromRunspace( var runspaceId = runspace.InstanceId; var runspaceLocation = RunspaceLocation.Local; var runspaceContext = RunspaceContext.Original; - var versionDetails = PowerShellVersionDetails.GetVersionDetails(runspace); + var versionDetails = PowerShellVersionDetails.GetVersionDetails(runspace, logger); string connectionString = null; diff --git a/src/PowerShellEditorServices/Session/SessionPSHost.cs b/src/PowerShellEditorServices/Session/SessionPSHost.cs index 41e4cec7a..cc54438ec 100644 --- a/src/PowerShellEditorServices/Session/SessionPSHost.cs +++ b/src/PowerShellEditorServices/Session/SessionPSHost.cs @@ -151,7 +151,7 @@ public override PSHostUserInterface UI /// public override void EnterNestedPrompt() { - Logger.Write(LogLevel.Verbose, "EnterNestedPrompt() called."); + Logger.CurrentLogger.Write(LogLevel.Verbose, "EnterNestedPrompt() called."); } /// @@ -159,7 +159,7 @@ public override void EnterNestedPrompt() /// public override void ExitNestedPrompt() { - Logger.Write(LogLevel.Verbose, "ExitNestedPrompt() called."); + Logger.CurrentLogger.Write(LogLevel.Verbose, "ExitNestedPrompt() called."); } /// @@ -167,7 +167,7 @@ public override void ExitNestedPrompt() /// public override void NotifyBeginApplication() { - Logger.Write(LogLevel.Verbose, "NotifyBeginApplication() called."); + Logger.CurrentLogger.Write(LogLevel.Verbose, "NotifyBeginApplication() called."); this.isNativeApplicationRunning = true; } @@ -176,7 +176,7 @@ public override void NotifyBeginApplication() /// public override void NotifyEndApplication() { - Logger.Write(LogLevel.Verbose, "NotifyEndApplication() called."); + Logger.CurrentLogger.Write(LogLevel.Verbose, "NotifyEndApplication() called."); this.isNativeApplicationRunning = false; } diff --git a/src/PowerShellEditorServices/Session/SessionPSHostRawUserInterface.cs b/src/PowerShellEditorServices/Session/SessionPSHostRawUserInterface.cs index 07caf165d..6f4d5bfae 100644 --- a/src/PowerShellEditorServices/Session/SessionPSHostRawUserInterface.cs +++ b/src/PowerShellEditorServices/Session/SessionPSHostRawUserInterface.cs @@ -168,7 +168,7 @@ public override Size MaxWindowSize /// A KeyInfo struct with details about the current keypress. public override KeyInfo ReadKey(ReadKeyOptions options) { - Logger.Write( + Logger.CurrentLogger.Write( LogLevel.Warning, "PSHostRawUserInterface.ReadKey was called"); @@ -180,7 +180,7 @@ public override KeyInfo ReadKey(ReadKeyOptions options) /// public override void FlushInputBuffer() { - Logger.Write( + Logger.CurrentLogger.Write( LogLevel.Warning, "PSHostRawUserInterface.FlushInputBuffer was called"); } @@ -192,7 +192,7 @@ public override void FlushInputBuffer() /// A BufferCell array with the requested buffer contents. public override BufferCell[,] GetBufferContents(Rectangle rectangle) { - Logger.Write( + Logger.CurrentLogger.Write( LogLevel.Warning, "PSHostRawUserInterface.GetBufferContents was called"); @@ -212,7 +212,7 @@ public override void ScrollBufferContents( Rectangle clip, BufferCell fill) { - Logger.Write( + Logger.CurrentLogger.Write( LogLevel.Warning, "PSHostRawUserInterface.ScrollBufferContents was called"); } @@ -236,7 +236,7 @@ public override void SetBufferContents( } else { - Logger.Write( + Logger.CurrentLogger.Write( LogLevel.Warning, "PSHostRawUserInterface.SetBufferContents was called with a specific region"); } @@ -251,7 +251,7 @@ public override void SetBufferContents( Coordinates origin, BufferCell[,] contents) { - Logger.Write( + Logger.CurrentLogger.Write( LogLevel.Warning, "PSHostRawUserInterface.SetBufferContents was called"); } diff --git a/src/PowerShellEditorServices/Session/SessionPSHostUserInterface.cs b/src/PowerShellEditorServices/Session/SessionPSHostUserInterface.cs index 5a50ab127..968a98110 100644 --- a/src/PowerShellEditorServices/Session/SessionPSHostUserInterface.cs +++ b/src/PowerShellEditorServices/Session/SessionPSHostUserInterface.cs @@ -13,6 +13,7 @@ using System.Threading.Tasks; using Microsoft.PowerShell.EditorServices.Console; using System.Threading; +using Microsoft.PowerShell.EditorServices.Utility; namespace Microsoft.PowerShell.EditorServices { @@ -89,7 +90,7 @@ public override Dictionary Prompt( { FieldDetails[] fields = fieldDescriptions - .Select(FieldDetails.Create) + .Select(f => { return FieldDetails.Create(f, Logger.CurrentLogger); }) .ToArray(); CancellationTokenSource cancellationToken = new CancellationTokenSource(); diff --git a/src/PowerShellEditorServices/Session/SimplePSHostRawUserInterface.cs b/src/PowerShellEditorServices/Session/SimplePSHostRawUserInterface.cs index fbb3057a2..85993107f 100644 --- a/src/PowerShellEditorServices/Session/SimplePSHostRawUserInterface.cs +++ b/src/PowerShellEditorServices/Session/SimplePSHostRawUserInterface.cs @@ -159,7 +159,7 @@ public override Size MaxWindowSize /// A KeyInfo struct with details about the current keypress. public override KeyInfo ReadKey(ReadKeyOptions options) { - Logger.Write( + Logger.CurrentLogger.Write( LogLevel.Warning, "PSHostRawUserInterface.ReadKey was called"); @@ -171,7 +171,7 @@ public override KeyInfo ReadKey(ReadKeyOptions options) /// public override void FlushInputBuffer() { - Logger.Write( + Logger.CurrentLogger.Write( LogLevel.Warning, "PSHostRawUserInterface.FlushInputBuffer was called"); } @@ -183,7 +183,7 @@ public override void FlushInputBuffer() /// A BufferCell array with the requested buffer contents. public override BufferCell[,] GetBufferContents(Rectangle rectangle) { - Logger.Write( + Logger.CurrentLogger.Write( LogLevel.Warning, "PSHostRawUserInterface.GetBufferContents was called"); @@ -203,7 +203,7 @@ public override void ScrollBufferContents( Rectangle clip, BufferCell fill) { - Logger.Write( + Logger.CurrentLogger.Write( LogLevel.Warning, "PSHostRawUserInterface.ScrollBufferContents was called"); } @@ -217,7 +217,7 @@ public override void SetBufferContents( Rectangle rectangle, BufferCell fill) { - Logger.Write( + Logger.CurrentLogger.Write( LogLevel.Warning, "PSHostRawUserInterface.SetBufferContents was called"); } @@ -231,7 +231,7 @@ public override void SetBufferContents( Coordinates origin, BufferCell[,] contents) { - Logger.Write( + Logger.CurrentLogger.Write( LogLevel.Warning, "PSHostRawUserInterface.SetBufferContents was called"); } diff --git a/src/PowerShellEditorServices/Templates/TemplateService.cs b/src/PowerShellEditorServices/Templates/TemplateService.cs index fe3a39af2..8ee3da496 100644 --- a/src/PowerShellEditorServices/Templates/TemplateService.cs +++ b/src/PowerShellEditorServices/Templates/TemplateService.cs @@ -20,6 +20,7 @@ public class TemplateService { #region Private Fields + private ILogger logger; private bool isPlasterLoaded; private bool? isPlasterInstalled; private PowerShellContext powerShellContext; @@ -32,10 +33,12 @@ public class TemplateService /// Creates a new instance of the TemplateService class. /// /// The PowerShellContext to use for this service. - public TemplateService(PowerShellContext powerShellContext) + /// An ILogger implementation used for writing log messages. + public TemplateService(PowerShellContext powerShellContext, ILogger logger) { Validate.IsNotNull(nameof(powerShellContext), powerShellContext); + this.logger = logger; this.powerShellContext = powerShellContext; } @@ -67,7 +70,7 @@ public async Task ImportPlasterIfInstalled() .AddCommand("Select-Object") .AddParameter("First", 1); - Logger.Write(LogLevel.Verbose, "Checking if Plaster is installed..."); + this.logger.Write(LogLevel.Verbose, "Checking if Plaster is installed..."); var getResult = await this.powerShellContext.ExecuteCommand( @@ -75,18 +78,18 @@ await this.powerShellContext.ExecuteCommand( PSObject moduleObject = getResult.First(); this.isPlasterInstalled = moduleObject != null; - string installedQualifier = + string installedQualifier = this.isPlasterInstalled.Value ? string.Empty : "not "; - Logger.Write( + this.logger.Write( LogLevel.Verbose, $"Plaster is {installedQualifier}installed!"); // Attempt to load plaster if (this.isPlasterInstalled.Value && this.isPlasterLoaded == false) { - Logger.Write(LogLevel.Verbose, "Loading Plaster..."); + this.logger.Write(LogLevel.Verbose, "Loading Plaster..."); psCommand = new PSCommand(); psCommand @@ -103,7 +106,7 @@ await this.powerShellContext.ExecuteCommand( this.isPlasterInstalled.Value ? "was" : "could not be"; - Logger.Write( + this.logger.Write( LogLevel.Verbose, $"Plaster {loadedQualifier} loaded successfully!"); } @@ -141,7 +144,7 @@ public async Task GetAvailableTemplates( await this.powerShellContext.ExecuteCommand( psCommand, false, false); - Logger.Write( + this.logger.Write( LogLevel.Verbose, $"Found {templateObjects.Count()} Plaster templates"); @@ -163,7 +166,7 @@ public async Task CreateFromTemplate( string templatePath, string destinationPath) { - Logger.Write( + this.logger.Write( LogLevel.Verbose, $"Invoking Plaster...\n\n TemplatePath: {templatePath}\n DestinationPath: {destinationPath}"); diff --git a/src/PowerShellEditorServices/Utility/AsyncContext.cs b/src/PowerShellEditorServices/Utility/AsyncContext.cs index 421ca3d96..ece383173 100644 --- a/src/PowerShellEditorServices/Utility/AsyncContext.cs +++ b/src/PowerShellEditorServices/Utility/AsyncContext.cs @@ -23,7 +23,8 @@ public static class AsyncContext /// The Task-returning Func which represents the "main" function /// for the thread. /// - public static void Start(Func asyncMainFunc) + /// An ILogger implementation used for writing log messages. + public static void Start(Func asyncMainFunc, ILogger logger) { // Is there already a synchronization context? if (SynchronizationContext.Current != null) @@ -33,7 +34,7 @@ public static void Start(Func asyncMainFunc) } // Create and register a synchronization context for this thread - var threadSyncContext = new ThreadSynchronizationContext(); + var threadSyncContext = new ThreadSynchronizationContext(logger); SynchronizationContext.SetSynchronizationContext(threadSyncContext); // Get the main task and act on its completion diff --git a/src/PowerShellEditorServices/Utility/AsyncContextThread.cs b/src/PowerShellEditorServices/Utility/AsyncContextThread.cs index 92629437a..c5ca20039 100644 --- a/src/PowerShellEditorServices/Utility/AsyncContextThread.cs +++ b/src/PowerShellEditorServices/Utility/AsyncContextThread.cs @@ -47,10 +47,11 @@ public AsyncContextThread(string threadName) /// /// A Func which returns the task to be run on the thread. /// + /// An ILogger implementation used for writing log messages. /// /// A Task which can be used to monitor the thread for completion. /// - public Task Run(Func taskReturningFunc) + public Task Run(Func taskReturningFunc, ILogger logger) { // Start up a long-running task with the action as the // main entry point for the thread @@ -62,7 +63,7 @@ public Task Run(Func taskReturningFunc) Thread.CurrentThread.Name = "AsyncContextThread: " + this.threadName; // Set up an AsyncContext to run the task - AsyncContext.Start(taskReturningFunc); + AsyncContext.Start(taskReturningFunc, logger); }, this.threadCancellationToken.Token, TaskCreationOptions.LongRunning, diff --git a/src/PowerShellEditorServices/Utility/FileLogger.cs b/src/PowerShellEditorServices/Utility/FileLogger.cs new file mode 100644 index 000000000..731d56c0e --- /dev/null +++ b/src/PowerShellEditorServices/Utility/FileLogger.cs @@ -0,0 +1,174 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +using System; +using System.IO; +using System.Runtime.CompilerServices; +using System.Text; +using System.Threading; + +namespace Microsoft.PowerShell.EditorServices.Utility +{ + /// + /// Provides an implementation of ILogger for writing messages to + /// a log file on disk. + /// + public class FileLogger : ILogger, IDisposable + { + private TextWriter textWriter; + private LogLevel minimumLogLevel = LogLevel.Verbose; + + /// + /// Creates an ILogger implementation that writes to the specified file. + /// + /// + /// Specifies the path at which log messages will be written. + /// + /// + /// Specifies the minimum log message level to write to the log file. + /// + public FileLogger(string logFilePath, LogLevel minimumLogLevel) + { + this.minimumLogLevel = minimumLogLevel; + + // Ensure that we have a usable log file path + if (!Path.IsPathRooted(logFilePath)) + { + logFilePath = + Path.Combine( +#if CoreCLR + AppContext.BaseDirectory, +#else + AppDomain.CurrentDomain.BaseDirectory, +#endif + logFilePath); + } + + if (!this.TryOpenLogFile(logFilePath)) + { + // If the log file couldn't be opened at this location, + // try opening it in a more reliable path + this.TryOpenLogFile( + Path.Combine( +#if CoreCLR + Environment.GetEnvironmentVariable("TEMP"), +#else + Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), +#endif + Path.GetFileName(logFilePath))); + } + } + + /// + /// Writes a message to the log file. + /// + /// The level at which the message will be written. + /// The message text to be written. + /// The name of the calling method. + /// The source file path where the calling method exists. + /// The line number of the calling method. + public void Write( + LogLevel logLevel, + string logMessage, + string callerName = null, + string callerSourceFile = null, + int callerLineNumber = 0) + { + if (this.textWriter != null && + logLevel >= this.minimumLogLevel) + { + // Print the timestamp and log level + this.textWriter.WriteLine( + "{0} [{1}] - Method \"{2}\" at line {3} of {4}\r\n", + DateTime.Now, + logLevel.ToString().ToUpper(), + callerName, + callerLineNumber, + callerSourceFile); + + // Print out indented message lines + foreach (var messageLine in logMessage.Split('\n')) + { + this.textWriter.WriteLine(" " + messageLine.TrimEnd()); + } + + // Finish with a newline and flush the writer + this.textWriter.WriteLine(); + this.textWriter.Flush(); + } + } + + /// + /// Writes an error message and exception to the log file. + /// + /// The error message text to be written. + /// The exception to be written.. + /// The name of the calling method. + /// The source file path where the calling method exists. + /// The line number of the calling method. + public void WriteException( + string errorMessage, + Exception errorException, + [CallerMemberName] string callerName = null, + [CallerFilePath] string callerSourceFile = null, + [CallerLineNumber] int callerLineNumber = 0) + { + this.Write( + LogLevel.Error, + $"{errorMessage}\r\n\r\n{errorException.ToString()}", + callerName, + callerSourceFile, + callerLineNumber); + } + + /// + /// Flushes any remaining log write and closes the log file. + /// + public void Dispose() + { + if (this.textWriter != null) + { + this.textWriter.Flush(); + this.textWriter.Dispose(); + this.textWriter = null; + } + } + + private bool TryOpenLogFile(string logFilePath) + { + try + { + // Make sure the log directory exists + Directory.CreateDirectory( + Path.GetDirectoryName( + logFilePath)); + + // Open the log file for writing with UTF8 encoding + this.textWriter = + new StreamWriter( + new FileStream( + logFilePath, + FileMode.Create), + Encoding.UTF8); + + return true; + } + catch (Exception e) + { + if (e is UnauthorizedAccessException || + e is IOException) + { + // This exception is thrown when we can't open the file + // at the path in logFilePath. Return false to indicate + // that the log file couldn't be created. + return false; + } + + // Unexpected exception, rethrow it + throw; + } + } + } +} \ No newline at end of file diff --git a/src/PowerShellEditorServices/Utility/ILogger.cs b/src/PowerShellEditorServices/Utility/ILogger.cs new file mode 100644 index 000000000..e5c91afd4 --- /dev/null +++ b/src/PowerShellEditorServices/Utility/ILogger.cs @@ -0,0 +1,75 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +using System; +using System.IO; +using System.Runtime.CompilerServices; +using System.Text; +using System.Threading; + +namespace Microsoft.PowerShell.EditorServices.Utility +{ + /// + /// Defines the level indicators for log messages. + /// + public enum LogLevel + { + /// + /// Indicates a verbose log message. + /// + Verbose, + + /// + /// Indicates a normal, non-verbose log message. + /// + Normal, + + /// + /// Indicates a warning message. + /// + Warning, + + /// + /// Indicates an error message. + /// + Error + } + + /// + /// Defines an interface for writing messages to a logging implementation. + /// + public interface ILogger : IDisposable + { + /// + /// Writes a message to the log file. + /// + /// The level at which the message will be written. + /// The message text to be written. + /// The name of the calling method. + /// The source file path where the calling method exists. + /// The line number of the calling method. + void Write( + LogLevel logLevel, + string logMessage, + [CallerMemberName] string callerName = null, + [CallerFilePath] string callerSourceFile = null, + [CallerLineNumber] int callerLineNumber = 0); + + /// + /// Writes an error message and exception to the log file. + /// + /// The error message text to be written. + /// The exception to be written.. + /// The name of the calling method. + /// The source file path where the calling method exists. + /// The line number of the calling method. + void WriteException( + string errorMessage, + Exception errorException, + [CallerMemberName] string callerName = null, + [CallerFilePath] string callerSourceFile = null, + [CallerLineNumber] int callerLineNumber = 0); + } +} \ No newline at end of file diff --git a/src/PowerShellEditorServices/Utility/Logger.cs b/src/PowerShellEditorServices/Utility/Logger.cs index fd072a7ad..930fcc369 100644 --- a/src/PowerShellEditorServices/Utility/Logger.cs +++ b/src/PowerShellEditorServices/Utility/Logger.cs @@ -3,71 +3,34 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. // -using System; -using System.IO; -using System.Runtime.CompilerServices; -using System.Text; - namespace Microsoft.PowerShell.EditorServices.Utility { - /// - /// Defines the level indicators for log messages. - /// - public enum LogLevel - { - /// - /// Indicates a verbose log message. - /// - Verbose, - - /// - /// Indicates a normal, non-verbose log message. - /// - Normal, - - /// - /// Indicates a warning message. - /// - Warning, - - /// - /// Indicates an error message. - /// - Error - } - /// /// Provides a simple logging interface. May be replaced with a /// more robust solution at a later date. /// public static class Logger { - private static LogWriter logWriter; + /// + /// Gets the current static ILogger instance. This property + /// is temporary and will be removed in an upcoming commit. + /// + public static ILogger CurrentLogger { get; private set; } /// /// Initializes the Logger for the current session. /// - /// - /// Optional. Specifies the path at which log messages will be written. + /// + /// Specifies the ILogger implementation to use for the static interface. /// - /// - /// Optional. Specifies the minimum log message level to write to the log file. - /// - public static void Initialize( - string logFilePath = "EditorServices.log", - LogLevel minimumLogLevel = LogLevel.Normal) + public static void Initialize(ILogger logger) { - if (logWriter != null) + if (CurrentLogger != null) { - logWriter.Dispose(); + CurrentLogger.Dispose(); } - // TODO: Parameterize this - logWriter = - new LogWriter( - minimumLogLevel, - logFilePath, - true); + CurrentLogger = logger; } /// @@ -75,217 +38,9 @@ public static void Initialize( /// public static void Close() { - if (logWriter != null) - { - logWriter.Dispose(); - } - } - - /// - /// Writes a message to the log file. - /// - /// The level at which the message will be written. - /// The message text to be written. - /// The name of the calling method. - /// The source file path where the calling method exists. - /// The line number of the calling method. - public static void Write( - LogLevel logLevel, - string logMessage, - [CallerMemberName] string callerName = null, - [CallerFilePath] string callerSourceFile = null, - [CallerLineNumber] int callerLineNumber = 0) - { - InnerWrite( - logLevel, - logMessage, - callerName, - callerSourceFile, - callerLineNumber); - } - - /// - /// Writes an error message and exception to the log file. - /// - /// The error message text to be written. - /// The exception to be written.. - /// The name of the calling method. - /// The source file path where the calling method exists. - /// The line number of the calling method. - public static void WriteException( - string errorMessage, - Exception errorException, - [CallerMemberName] string callerName = null, - [CallerFilePath] string callerSourceFile = null, - [CallerLineNumber] int callerLineNumber = 0) - { - InnerWrite( - LogLevel.Error, - $"{errorMessage}\r\n\r\n{errorException.ToString()}", - callerName, - callerSourceFile, - callerLineNumber); - } - - /// - /// Writes an error message and exception to the log file. - /// - /// The level at which the message will be written. - /// The error message text to be written. - /// The exception to be written.. - /// The name of the calling method. - /// The source file path where the calling method exists. - /// The line number of the calling method. - public static void WriteException( - LogLevel logLevel, - string errorMessage, - Exception errorException, - [CallerMemberName] string callerName = null, - [CallerFilePath] string callerSourceFile = null, - [CallerLineNumber] int callerLineNumber = 0) - { - InnerWrite( - logLevel, - $"{errorMessage}\r\n\r\n{errorException.ToString()}", - callerName, - callerSourceFile, - callerLineNumber); - } - - private static void InnerWrite( - LogLevel logLevel, - string logMessage, - string callerName, - string callerSourceFile, - int callerLineNumber) - { - if (logWriter != null) + if (CurrentLogger != null) { - logWriter.Write( - logLevel, - logMessage, - callerName, - callerSourceFile, - callerLineNumber); - } - } - } - - internal class LogWriter : IDisposable - { - private TextWriter textWriter; - private LogLevel minimumLogLevel = LogLevel.Verbose; - - public LogWriter(LogLevel minimumLogLevel, string logFilePath, bool deleteExisting) - { - this.minimumLogLevel = minimumLogLevel; - - // Ensure that we have a usable log file path - if (!Path.IsPathRooted(logFilePath)) - { - logFilePath = - Path.Combine( -#if CoreCLR - AppContext.BaseDirectory, -#else - AppDomain.CurrentDomain.BaseDirectory, -#endif - logFilePath); - } - - if (!this.TryOpenLogFile(logFilePath, deleteExisting)) - { - // If the log file couldn't be opened at this location, - // try opening it in a more reliable path - this.TryOpenLogFile( - Path.Combine( -#if CoreCLR - Environment.GetEnvironmentVariable("TEMP"), -#else - Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), -#endif - Path.GetFileName(logFilePath)), - deleteExisting); - } - } - - public void Write( - LogLevel logLevel, - string logMessage, - string callerName = null, - string callerSourceFile = null, - int callerLineNumber = 0) - { - if (this.textWriter != null && - logLevel >= this.minimumLogLevel) - { - // Print the timestamp and log level - this.textWriter.WriteLine( - "{0} [{1}] - Method \"{2}\" at line {3} of {4}\r\n", - DateTime.Now, - logLevel.ToString().ToUpper(), - callerName, - callerLineNumber, - callerSourceFile); - - // Print out indented message lines - foreach (var messageLine in logMessage.Split('\n')) - { - this.textWriter.WriteLine(" " + messageLine.TrimEnd()); - } - - // Finish with a newline and flush the writer - this.textWriter.WriteLine(); - this.textWriter.Flush(); - } - } - - public void Dispose() - { - if (this.textWriter != null) - { - this.textWriter.Flush(); - this.textWriter.Dispose(); - this.textWriter = null; - } - } - - private bool TryOpenLogFile( - string logFilePath, - bool deleteExisting) - { - try - { - // Make sure the log directory exists - Directory.CreateDirectory( - Path.GetDirectoryName( - logFilePath)); - - // Open the log file for writing with UTF8 encoding - this.textWriter = - new StreamWriter( - new FileStream( - logFilePath, - deleteExisting ? - FileMode.Create : - FileMode.Append), - Encoding.UTF8); - - return true; - } - catch (Exception e) - { - if (e is UnauthorizedAccessException || - e is IOException) - { - // This exception is thrown when we can't open the file - // at the path in logFilePath. Return false to indicate - // that the log file couldn't be created. - return false; - } - - // Unexpected exception, rethrow it - throw; + CurrentLogger.Dispose(); } } } diff --git a/src/PowerShellEditorServices/Utility/NullLogger.cs b/src/PowerShellEditorServices/Utility/NullLogger.cs new file mode 100644 index 000000000..d94528e5d --- /dev/null +++ b/src/PowerShellEditorServices/Utility/NullLogger.cs @@ -0,0 +1,61 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +using System; +using System.IO; +using System.Runtime.CompilerServices; +using System.Text; +using System.Threading; + +namespace Microsoft.PowerShell.EditorServices.Utility +{ + /// + /// Provides an implementation of ILogger that throws away all log messages, + /// typically used when logging isn't needed. + /// + public class NullLogger : ILogger, IDisposable + { + /// + /// Writes a message to the log file. + /// + /// The level at which the message will be written. + /// The message text to be written. + /// The name of the calling method. + /// The source file path where the calling method exists. + /// The line number of the calling method. + public void Write( + LogLevel logLevel, + string logMessage, + string callerName = null, + string callerSourceFile = null, + int callerLineNumber = 0) + { + } + + /// + /// Writes an error message and exception to the log file. + /// + /// The error message text to be written. + /// The exception to be written.. + /// The name of the calling method. + /// The source file path where the calling method exists. + /// The line number of the calling method. + public void WriteException( + string errorMessage, + Exception errorException, + [CallerMemberName] string callerName = null, + [CallerFilePath] string callerSourceFile = null, + [CallerLineNumber] int callerLineNumber = 0) + { + } + + /// + /// Flushes any remaining log write and closes the log file. + /// + public void Dispose() + { + } + } +} \ No newline at end of file diff --git a/src/PowerShellEditorServices/Utility/ThreadSynchronizationContext.cs b/src/PowerShellEditorServices/Utility/ThreadSynchronizationContext.cs index 8acda7091..77db1d113 100644 --- a/src/PowerShellEditorServices/Utility/ThreadSynchronizationContext.cs +++ b/src/PowerShellEditorServices/Utility/ThreadSynchronizationContext.cs @@ -18,6 +18,7 @@ public class ThreadSynchronizationContext : SynchronizationContext { #region Private Fields + private ILogger logger; private BlockingCollection> requestQueue = new BlockingCollection>(); @@ -25,6 +26,19 @@ public class ThreadSynchronizationContext : SynchronizationContext #region Constructors + /// + /// + /// + /// An ILogger implementation used for writing log messages. + public ThreadSynchronizationContext(ILogger logger) + { + this.logger = logger; + } + + #endregion + + #region Public Methods + /// /// Posts a request for execution to the SynchronizationContext. /// This will be executed on the SynchronizationContext's thread. @@ -47,7 +61,7 @@ public override void Post(SendOrPostCallback callback, object state) } else { - Logger.Write( + this.logger.Write( LogLevel.Verbose, "Attempted to post message to synchronization context after it's already completed"); } diff --git a/src/PowerShellEditorServices/Workspace/Workspace.cs b/src/PowerShellEditorServices/Workspace/Workspace.cs index 283831d17..30bce240c 100644 --- a/src/PowerShellEditorServices/Workspace/Workspace.cs +++ b/src/PowerShellEditorServices/Workspace/Workspace.cs @@ -21,6 +21,7 @@ public class Workspace { #region Private Fields + private ILogger logger; private Version powerShellVersion; private Dictionary workspaceFiles = new Dictionary(); @@ -41,9 +42,11 @@ public class Workspace /// Creates a new instance of the Workspace class. /// /// The version of PowerShell for which scripts will be parsed. - public Workspace(Version powerShellVersion) + /// An ILogger implementation used for writing log messages. + public Workspace(Version powerShellVersion, ILogger logger) { this.powerShellVersion = powerShellVersion; + this.logger = logger; } #endregion @@ -88,7 +91,7 @@ public ScriptFile GetFile(string filePath) this.workspaceFiles.Add(keyName, scriptFile); } - Logger.Write(LogLevel.Verbose, "Opened file on disk: " + resolvedFilePath); + this.logger.Write(LogLevel.Verbose, "Opened file on disk: " + resolvedFilePath); } return scriptFile; @@ -132,7 +135,7 @@ public ScriptFile GetFileBuffer(string filePath, string initialBuffer) this.workspaceFiles.Add(keyName, scriptFile); - Logger.Write(LogLevel.Verbose, "Opened file as in-memory buffer: " + resolvedFilePath); + this.logger.Write(LogLevel.Verbose, "Opened file as in-memory buffer: " + resolvedFilePath); } return scriptFile; @@ -248,7 +251,7 @@ private IEnumerable RecursivelyEnumerateFiles(string folderPath) } catch (UnauthorizedAccessException e) { - Logger.WriteException( + this.logger.WriteException( $"Could not enumerate files in the path '{folderPath}' due to the path not being accessible", e); } @@ -265,7 +268,7 @@ private IEnumerable RecursivelyEnumerateFiles(string folderPath) } catch (UnauthorizedAccessException e) { - Logger.WriteException( + this.logger.WriteException( $"Could not enumerate files in the path '{folderPath}' due to a file not being accessible", e); } @@ -303,7 +306,7 @@ private void RecursivelyFindReferences( continue; } - Logger.Write( + this.logger.Write( LogLevel.Verbose, string.Format( "Resolved relative path '{0}' to '{1}'", @@ -348,7 +351,7 @@ private string ResolveFilePath(string filePath) filePath = Path.GetFullPath(filePath); } - Logger.Write(LogLevel.Verbose, "Resolved path: " + filePath); + this.logger.Write(LogLevel.Verbose, "Resolved path: " + filePath); return filePath; } @@ -428,7 +431,7 @@ private string ResolveRelativeScriptPath(string baseFilePath, string relativePat if (resolveException != null) { - Logger.Write( + this.logger.Write( LogLevel.Error, $"Could not resolve relative script path\r\n" + $" baseFilePath = {baseFilePath}\r\n " + diff --git a/test/PowerShellEditorServices.Test.Host/DebugAdapterTests.cs b/test/PowerShellEditorServices.Test.Host/DebugAdapterTests.cs index 66220b52b..071a776d0 100644 --- a/test/PowerShellEditorServices.Test.Host/DebugAdapterTests.cs +++ b/test/PowerShellEditorServices.Test.Host/DebugAdapterTests.cs @@ -17,6 +17,7 @@ namespace Microsoft.PowerShell.EditorServices.Test.Host { public class DebugAdapterTests : ServerTestsBase, IAsyncLifetime { + private ILogger logger; private DebugAdapterClient debugAdapterClient; private string DebugScriptPath = Path.GetFullPath(@"..\..\..\..\PowerShellEditorServices.Test.Shared\Debugging\DebugTest.ps1"); @@ -34,9 +35,12 @@ public async Task InitializeAsync() this.GetType().Name, Guid.NewGuid().ToString().Substring(0, 8)); - Logger.Initialize( - testLogPath + "-client.log", - LogLevel.Verbose); + this.logger = + new FileLogger( + testLogPath + "-client.log", + LogLevel.Verbose); + + Logger.Initialize(this.logger); testLogPath += "-server.log"; System.Console.WriteLine(" Output log at path: {0}", testLogPath); @@ -52,7 +56,9 @@ await this.LaunchService( new DebugAdapterClient( await TcpSocketClientChannel.Connect( portNumbers.Item2, - MessageProtocolType.DebugAdapter)); + MessageProtocolType.DebugAdapter, + this.logger), + this.logger); await this.debugAdapterClient.Start(); } diff --git a/test/PowerShellEditorServices.Test.Host/LanguageServerTests.cs b/test/PowerShellEditorServices.Test.Host/LanguageServerTests.cs index 64d682e05..ee32eb63f 100644 --- a/test/PowerShellEditorServices.Test.Host/LanguageServerTests.cs +++ b/test/PowerShellEditorServices.Test.Host/LanguageServerTests.cs @@ -22,6 +22,7 @@ namespace Microsoft.PowerShell.EditorServices.Test.Host { public class LanguageServerTests : ServerTestsBase, IAsyncLifetime { + private ILogger logger; private LanguageServiceClient languageServiceClient; public async Task InitializeAsync() @@ -37,9 +38,12 @@ public async Task InitializeAsync() this.GetType().Name, Guid.NewGuid().ToString().Substring(0, 8)); - Logger.Initialize( - testLogPath + "-client.log", - LogLevel.Verbose); + this.logger = + new FileLogger( + testLogPath + "-client.log", + LogLevel.Verbose); + + Logger.Initialize(this.logger); testLogPath += "-server.log"; System.Console.WriteLine(" Output log at path: {0}", testLogPath); @@ -55,7 +59,9 @@ await this.LaunchService( new LanguageServiceClient( await TcpSocketClientChannel.Connect( portNumbers.Item1, - MessageProtocolType.LanguageServer)); + MessageProtocolType.LanguageServer, + this.logger), + this.logger); await this.languageServiceClient.Start(); } diff --git a/test/PowerShellEditorServices.Test.Protocol/Message/MessageReaderWriterTests.cs b/test/PowerShellEditorServices.Test.Protocol/Message/MessageReaderWriterTests.cs index a82df2991..42ab1ea41 100644 --- a/test/PowerShellEditorServices.Test.Protocol/Message/MessageReaderWriterTests.cs +++ b/test/PowerShellEditorServices.Test.Protocol/Message/MessageReaderWriterTests.cs @@ -10,6 +10,7 @@ using System.Text; using System.Threading.Tasks; using Xunit; +using Microsoft.PowerShell.EditorServices.Utility; namespace Microsoft.PowerShell.EditorServices.Test.Protocol.MessageProtocol { @@ -19,10 +20,12 @@ public class MessageReaderWriterTests const string TestEventFormatString = "{{\"event\":\"testEvent\",\"body\":{{\"someString\":\"{0}\"}},\"seq\":0,\"type\":\"event\"}}"; readonly int ExpectedMessageByteCount = Encoding.UTF8.GetByteCount(TestEventString); + private ILogger logger; private IMessageSerializer messageSerializer; public MessageReaderWriterTests() { + this.logger = new NullLogger(); this.messageSerializer = new V8MessageSerializer(); } @@ -31,10 +34,11 @@ public async Task WritesMessage() { MemoryStream outputStream = new MemoryStream(); - MessageWriter messageWriter = + MessageWriter messageWriter = new MessageWriter( outputStream, - this.messageSerializer); + this.messageSerializer, + this.logger); // Write the message and then roll back the stream to be read // TODO: This will need to be redone! @@ -69,8 +73,9 @@ public void ReadsMessage() MemoryStream inputStream = new MemoryStream(); MessageReader messageReader = new MessageReader( - inputStream, - this.messageSerializer); + inputStream, + this.messageSerializer, + this.logger); // Write a message to the stream byte[] messageBuffer = this.GetMessageBytes(TestEventString); @@ -94,8 +99,9 @@ public void ReadsManyBufferedMessages() MemoryStream inputStream = new MemoryStream(); MessageReader messageReader = new MessageReader( - inputStream, - this.messageSerializer); + inputStream, + this.messageSerializer, + this.logger); // Get a message to use for writing to the stream byte[] messageBuffer = this.GetMessageBytes(TestEventString); @@ -130,12 +136,13 @@ public void ReaderResizesBufferForLargeMessages() MemoryStream inputStream = new MemoryStream(); MessageReader messageReader = new MessageReader( - inputStream, - this.messageSerializer); + inputStream, + this.messageSerializer, + this.logger); // Get a message with content so large that the buffer will need // to be resized to fit it all. - byte[] messageBuffer = + byte[] messageBuffer = this.GetMessageBytes( string.Format( TestEventFormatString, @@ -159,7 +166,7 @@ private byte[] GetMessageBytes(string messageString, Encoding encoding = null) } byte[] messageBytes = Encoding.UTF8.GetBytes(messageString); - byte[] headerBytes = + byte[] headerBytes = Encoding.ASCII.GetBytes( string.Format( Constants.ContentLengthFormatString, diff --git a/test/PowerShellEditorServices.Test/Console/ChoicePromptHandlerTests.cs b/test/PowerShellEditorServices.Test/Console/ChoicePromptHandlerTests.cs index 684b39ca4..5034d5f94 100644 --- a/test/PowerShellEditorServices.Test/Console/ChoicePromptHandlerTests.cs +++ b/test/PowerShellEditorServices.Test/Console/ChoicePromptHandlerTests.cs @@ -7,6 +7,7 @@ using System.Threading; using System.Threading.Tasks; using Xunit; +using Microsoft.PowerShell.EditorServices.Utility; namespace Microsoft.PowerShell.EditorServices.Test.Console { @@ -70,7 +71,7 @@ public void ChoicePromptReturnsCorrectIdForHotKey() [Fact] public void ChoicePromptRepromptsOnInvalidInput() { - TestChoicePromptHandler choicePromptHandler = + TestChoicePromptHandler choicePromptHandler = new TestChoicePromptHandler(); Task promptTask = @@ -95,6 +96,10 @@ internal class TestChoicePromptHandler : ChoicePromptHandler public int TimesPrompted { get; private set; } + public TestChoicePromptHandler() : base(new NullLogger()) + { + } + public void ReturnInputString(string inputString) { this.linePromptTask.SetResult(inputString); diff --git a/test/PowerShellEditorServices.Test/Console/ConsoleServiceTests.cs b/test/PowerShellEditorServices.Test/Console/ConsoleServiceTests.cs index babd4e7c1..37356d5fc 100644 --- a/test/PowerShellEditorServices.Test/Console/ConsoleServiceTests.cs +++ b/test/PowerShellEditorServices.Test/Console/ConsoleServiceTests.cs @@ -49,7 +49,7 @@ public class ConsoleServiceTests : IDisposable public ConsoleServiceTests() { - this.powerShellContext = new PowerShellContext(); + this.powerShellContext = new PowerShellContext(new NullLogger()); ConsoleServicePSHost psHost = new ConsoleServicePSHost( powerShellContext, @@ -560,7 +560,7 @@ internal class TestConsoleChoicePromptHandler : ConsoleChoicePromptHandler public TestConsoleChoicePromptHandler( IConsoleHost consoleHost, TaskCompletionSource promptShownTask) - : base(consoleHost) + : base(consoleHost, new NullLogger()) { this.consoleHost = consoleHost; this.promptShownTask = promptShownTask; @@ -622,7 +622,7 @@ internal class TestConsoleInputPromptHandler : ConsoleInputPromptHandler public TestConsoleInputPromptHandler( IConsoleHost consoleHost, TaskCompletionSource promptShownTask) - : base(consoleHost) + : base(consoleHost, new NullLogger()) { this.consoleHost = consoleHost; this.promptShownTask = promptShownTask; diff --git a/test/PowerShellEditorServices.Test/Console/InputPromptHandlerTests.cs b/test/PowerShellEditorServices.Test/Console/InputPromptHandlerTests.cs index 50602aafb..1e2e1ad14 100644 --- a/test/PowerShellEditorServices.Test/Console/InputPromptHandlerTests.cs +++ b/test/PowerShellEditorServices.Test/Console/InputPromptHandlerTests.cs @@ -11,6 +11,7 @@ using System; using System.Threading; using System.Security; +using Microsoft.PowerShell.EditorServices.Utility; namespace Microsoft.PowerShell.EditorServices.Test.Console { @@ -131,6 +132,10 @@ internal class TestInputPromptHandler : InputPromptHandler public Exception LastError { get; private set; } + public TestInputPromptHandler() : base(new NullLogger()) + { + } + public void ReturnInputString(string inputString) { this.linePromptTask.SetResult(inputString); diff --git a/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs b/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs index 18bf1f8c4..7c3978722 100644 --- a/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs +++ b/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs @@ -31,10 +31,12 @@ public class DebugServiceTests : IDisposable public DebugServiceTests() { - this.powerShellContext = PowerShellContextFactory.Create(); + var logger = new NullLogger(); + + this.powerShellContext = PowerShellContextFactory.Create(logger); this.powerShellContext.SessionStateChanged += powerShellContext_SessionStateChanged; - this.workspace = new Workspace(this.powerShellContext.LocalPowerShellVersion.Version); + this.workspace = new Workspace(this.powerShellContext.LocalPowerShellVersion.Version, logger); // Load the test debug file this.debugScriptFile = @@ -45,7 +47,7 @@ public DebugServiceTests() this.workspace.GetFile( @"..\..\..\..\PowerShellEditorServices.Test.Shared\Debugging\VariableTest.ps1"); - this.debugService = new DebugService(this.powerShellContext); + this.debugService = new DebugService(this.powerShellContext, logger); this.debugService.DebuggerStopped += debugService_DebuggerStopped; this.debugService.BreakpointUpdated += debugService_BreakpointUpdated; this.runnerContext = SynchronizationContext.Current; diff --git a/test/PowerShellEditorServices.Test/Extensions/ExtensionServiceTests.cs b/test/PowerShellEditorServices.Test/Extensions/ExtensionServiceTests.cs index ecfe7f7be..1d6a6ef25 100644 --- a/test/PowerShellEditorServices.Test/Extensions/ExtensionServiceTests.cs +++ b/test/PowerShellEditorServices.Test/Extensions/ExtensionServiceTests.cs @@ -35,7 +35,8 @@ private enum EventType public async Task InitializeAsync() { - this.powerShellContext = PowerShellContextFactory.Create(); + var logger = new NullLogger(); + this.powerShellContext = PowerShellContextFactory.Create(logger); await this.powerShellContext.ImportCommandsModule(@"..\..\..\..\..\module\PowerShellEditorServices\Commands"); this.extensionService = new ExtensionService(this.powerShellContext); diff --git a/test/PowerShellEditorServices.Test/Language/LanguageServiceTests.cs b/test/PowerShellEditorServices.Test/Language/LanguageServiceTests.cs index c9be58fcb..4096ca6ad 100644 --- a/test/PowerShellEditorServices.Test/Language/LanguageServiceTests.cs +++ b/test/PowerShellEditorServices.Test/Language/LanguageServiceTests.cs @@ -15,6 +15,7 @@ using System.Linq; using System.Threading.Tasks; using Xunit; +using Microsoft.PowerShell.EditorServices.Utility; namespace Microsoft.PowerShell.EditorServices.Test.Language { @@ -25,12 +26,12 @@ public class LanguageServiceTests : IDisposable private PowerShellContext powerShellContext; private const string baseSharedScriptPath = @"..\..\..\..\PowerShellEditorServices.Test.Shared\"; - public LanguageServiceTests() { - this.powerShellContext = PowerShellContextFactory.Create(); - this.workspace = new Workspace(this.powerShellContext.LocalPowerShellVersion.Version); - this.languageService = new LanguageService(this.powerShellContext); + var logger = new NullLogger(); + this.powerShellContext = PowerShellContextFactory.Create(logger); + this.workspace = new Workspace(this.powerShellContext.LocalPowerShellVersion.Version, logger); + this.languageService = new LanguageService(this.powerShellContext, logger); } public void Dispose() @@ -163,7 +164,7 @@ public async Task LanguageServiceFindsFunctionDefinitionInWorkspace() var definitionResult = await this.GetDefinition( FindsFunctionDefinitionInWorkspace.SourceDetails, - new Workspace(this.powerShellContext.LocalPowerShellVersion.Version) + new Workspace(this.powerShellContext.LocalPowerShellVersion.Version, new NullLogger()) { WorkspacePath = Path.Combine(baseSharedScriptPath, @"References") }); diff --git a/test/PowerShellEditorServices.Test/PowerShellContextFactory.cs b/test/PowerShellEditorServices.Test/PowerShellContextFactory.cs index 043585864..61d429a91 100644 --- a/test/PowerShellEditorServices.Test/PowerShellContextFactory.cs +++ b/test/PowerShellEditorServices.Test/PowerShellContextFactory.cs @@ -7,15 +7,16 @@ using Microsoft.PowerShell.EditorServices.Test.Console; using System; using System.IO; +using Microsoft.PowerShell.EditorServices.Utility; namespace Microsoft.PowerShell.EditorServices.Test { internal static class PowerShellContextFactory { - public static PowerShellContext Create() + public static PowerShellContext Create(ILogger logger) { - PowerShellContext powerShellContext = new PowerShellContext(); + PowerShellContext powerShellContext = new PowerShellContext(logger); powerShellContext.Initialize( PowerShellContextTests.TestProfilePaths, PowerShellContext.CreateRunspace(PowerShellContextTests.TestHostDetails, powerShellContext, false), diff --git a/test/PowerShellEditorServices.Test/Session/PowerShellContextTests.cs b/test/PowerShellEditorServices.Test/Session/PowerShellContextTests.cs index ff2001f6b..73ae260ab 100644 --- a/test/PowerShellEditorServices.Test/Session/PowerShellContextTests.cs +++ b/test/PowerShellEditorServices.Test/Session/PowerShellContextTests.cs @@ -42,7 +42,7 @@ public class PowerShellContextTests : IDisposable public PowerShellContextTests() { - this.powerShellContext = PowerShellContextFactory.Create(); + this.powerShellContext = PowerShellContextFactory.Create(new NullLogger()); this.powerShellContext.SessionStateChanged += OnSessionStateChanged; this.stateChangeQueue = new AsyncQueue(); } diff --git a/test/PowerShellEditorServices.Test/Session/WorkspaceTests.cs b/test/PowerShellEditorServices.Test/Session/WorkspaceTests.cs index 16b58987f..4dfdabdc9 100644 --- a/test/PowerShellEditorServices.Test/Session/WorkspaceTests.cs +++ b/test/PowerShellEditorServices.Test/Session/WorkspaceTests.cs @@ -8,12 +8,13 @@ using System.IO; using System.Linq; using Xunit; +using Microsoft.PowerShell.EditorServices.Utility; namespace Microsoft.PowerShell.EditorServices.Test.Session { public class WorkspaceTests { - private static readonly Version PowerShellVersion = new Version("5.0"); + private static readonly Version PowerShellVersion = new Version("5.0"); [Fact] public void CanResolveWorkspaceRelativePath() @@ -23,7 +24,7 @@ public void CanResolveWorkspaceRelativePath() string testPathOutside = @"c:\Test\PeerPath\FilePath.ps1"; string testPathAnotherDrive = @"z:\TryAndFindMe\FilePath.ps1"; - Workspace workspace = new Workspace(PowerShellVersion); + Workspace workspace = new Workspace(PowerShellVersion, new NullLogger()); // Test without a workspace path Assert.Equal(testPathOutside, workspace.GetRelativePath(testPathOutside)); diff --git a/test/PowerShellEditorServices.Test/Utility/LoggerTests.cs b/test/PowerShellEditorServices.Test/Utility/LoggerTests.cs index bb667cbac..a343ad4a9 100644 --- a/test/PowerShellEditorServices.Test/Utility/LoggerTests.cs +++ b/test/PowerShellEditorServices.Test/Utility/LoggerTests.cs @@ -21,7 +21,7 @@ public class LoggerTests AppContext.BaseDirectory, #else AppDomain.CurrentDomain.BaseDirectory, -#endif +#endif "Test.log"); [Fact] @@ -71,8 +71,11 @@ public void CanExcludeMessagesBelowErrorLevel() private void AssertWritesMessageAtLevel(LogLevel logLevel) { // Write a message at the desired level - Logger.Initialize(logFilePath, LogLevel.Verbose); - Logger.Write(logLevel, testMessage); + var logger = new FileLogger(logFilePath, LogLevel.Verbose); + logger.Write(logLevel, testMessage); + + // Dispose of the logger + logger.Dispose(); // Read the contents and verify that it's there string logContents = this.ReadLogContents(); @@ -82,7 +85,7 @@ private void AssertWritesMessageAtLevel(LogLevel logLevel) private void AssertExcludesMessageBelowLevel(LogLevel minimumLogLevel) { - Logger.Initialize(logFilePath, minimumLogLevel); + var logger = new FileLogger(logFilePath, minimumLogLevel); // Get all possible log levels LogLevel[] allLogLevels = @@ -93,9 +96,12 @@ private void AssertExcludesMessageBelowLevel(LogLevel minimumLogLevel) // Write a message at each log level foreach (var logLevel in allLogLevels) { - Logger.Write((LogLevel)logLevel, testMessage); + logger.Write((LogLevel)logLevel, testMessage); } + // Dispose of the logger + logger.Dispose(); + // Make sure all excluded log levels aren't in the contents string logContents = this.ReadLogContents(); for (int i = 0; i < (int)minimumLogLevel; i++) @@ -122,6 +128,44 @@ private string ReadLogContents() Encoding.UTF8)); } + private class LogPathHelper : IDisposable + { + private readonly string logFilePathTemplate = + Path.Combine( + #if CoreCLR + AppContext.BaseDirectory, + #else + AppDomain.CurrentDomain.BaseDirectory, + #endif + "Test-{0}.log"); + + public string LogFilePath { get; private set; } + + public LogPathHelper() + { + this.LogFilePath = + string.Format( + logFilePathTemplate, + "2"); + //Guid.NewGuid().ToString()); + } + + public void Dispose() + { + // Delete the created file + try + { + if (this.LogFilePath != null) + { + File.Delete(this.LogFilePath); + } + } + catch (Exception) + { + } + } + } + #endregion } }