diff --git a/src/Renci.SshNet/BaseClient.cs b/src/Renci.SshNet/BaseClient.cs
index 56d2145cc..25d178f85 100644
--- a/src/Renci.SshNet/BaseClient.cs
+++ b/src/Renci.SshNet/BaseClient.cs
@@ -1,9 +1,9 @@
 using System;
+using System.Diagnostics;
 using System.Net.Sockets;
 using System.Threading;
 using System.Threading.Tasks;
 
-using Renci.SshNet.Abstractions;
 using Renci.SshNet.Common;
 using Renci.SshNet.Messages.Transport;
 
@@ -317,7 +317,7 @@ public async Task ConnectAsync(CancellationToken cancellationToken)
         /// The method was called after the client was disposed.
         public void Disconnect()
         {
-            DiagnosticAbstraction.Log("Disconnecting client.");
+            Diagnostic.Log("Disconnecting client.", TraceEventType.Verbose);
 
             CheckDisposed();
 
@@ -416,7 +416,7 @@ protected virtual void Dispose(bool disposing)
 
             if (disposing)
             {
-                DiagnosticAbstraction.Log("Disposing client.");
+                Diagnostic.Log("Disposing client.", TraceEventType.Verbose);
 
                 Disconnect();
 
diff --git a/src/Renci.SshNet/Channels/Channel.cs b/src/Renci.SshNet/Channels/Channel.cs
index 25975872d..162f20e73 100644
--- a/src/Renci.SshNet/Channels/Channel.cs
+++ b/src/Renci.SshNet/Channels/Channel.cs
@@ -1,9 +1,9 @@
 using System;
+using System.Diagnostics;
 using System.Globalization;
 using System.Net.Sockets;
 using System.Threading;
 
-using Renci.SshNet.Abstractions;
 using Renci.SshNet.Common;
 using Renci.SshNet.Messages;
 using Renci.SshNet.Messages.Connection;
@@ -556,7 +556,7 @@ protected virtual void Close()
                         var closeWaitResult = _session.TryWait(_channelClosedWaitHandle, ConnectionInfo.ChannelCloseTimeout);
                         if (closeWaitResult != WaitResult.Success)
                         {
-                            DiagnosticAbstraction.Log(string.Format("Wait for channel close not successful: {0:G}.", closeWaitResult));
+                            Diagnostic.Log(string.Format("Wait for channel close not successful: {0:G}.", closeWaitResult), TraceEventType.Warning);
                         }
                     }
                 }
diff --git a/src/Renci.SshNet/Channels/ChannelDirectTcpip.cs b/src/Renci.SshNet/Channels/ChannelDirectTcpip.cs
index 6c521bce2..d60a34885 100644
--- a/src/Renci.SshNet/Channels/ChannelDirectTcpip.cs
+++ b/src/Renci.SshNet/Channels/ChannelDirectTcpip.cs
@@ -1,7 +1,9 @@
 using System;
+using System.Diagnostics;
 using System.Net;
 using System.Net.Sockets;
 using System.Threading;
+
 using Renci.SshNet.Abstractions;
 using Renci.SshNet.Common;
 using Renci.SshNet.Messages.Connection;
@@ -156,8 +158,7 @@ private void ShutdownSocket(SocketShutdown how)
                 }
                 catch (SocketException ex)
                 {
-                    // TODO: log as warning
-                    DiagnosticAbstraction.Log("Failure shutting down socket: " + ex);
+                    Diagnostic.Log("Failure shutting down socket: " + ex, TraceEventType.Warning);
                 }
             }
         }
diff --git a/src/Renci.SshNet/Channels/ChannelForwardedTcpip.cs b/src/Renci.SshNet/Channels/ChannelForwardedTcpip.cs
index a8382015a..416f3f8b8 100644
--- a/src/Renci.SshNet/Channels/ChannelForwardedTcpip.cs
+++ b/src/Renci.SshNet/Channels/ChannelForwardedTcpip.cs
@@ -1,6 +1,8 @@
 using System;
+using System.Diagnostics;
 using System.Net;
 using System.Net.Sockets;
+
 using Renci.SshNet.Abstractions;
 using Renci.SshNet.Common;
 using Renci.SshNet.Messages.Connection;
@@ -138,8 +140,7 @@ private void ShutdownSocket(SocketShutdown how)
                 }
                 catch (SocketException ex)
                 {
-                    // TODO: log as warning
-                    DiagnosticAbstraction.Log("Failure shutting down socket: " + ex);
+                    Diagnostic.Log("Failure shutting down socket: " + ex, TraceEventType.Warning);
                 }
             }
         }
diff --git a/src/Renci.SshNet/Common/Extensions.cs b/src/Renci.SshNet/Common/Extensions.cs
index ae390d604..35ce537c5 100644
--- a/src/Renci.SshNet/Common/Extensions.cs
+++ b/src/Renci.SshNet/Common/Extensions.cs
@@ -315,5 +315,12 @@ internal static bool IsConnected(this Socket socket)
 
             return socket.Connected;
         }
+
+        internal static string Join(this IEnumerable values, string separator)
+        {
+            // A sly way to prevent analyzers asking to "use an overload with a char parameter"
+            // which is not available on all targets.
+            return string.Join(separator, values);
+        }
     }
 }
diff --git a/src/Renci.SshNet/Connection/ConnectorBase.cs b/src/Renci.SshNet/Connection/ConnectorBase.cs
index 384091b18..0d8949e04 100644
--- a/src/Renci.SshNet/Connection/ConnectorBase.cs
+++ b/src/Renci.SshNet/Connection/ConnectorBase.cs
@@ -1,4 +1,5 @@
 using System;
+using System.Diagnostics;
 using System.Net;
 using System.Net.Sockets;
 using System.Threading;
@@ -41,7 +42,7 @@ protected Socket SocketConnect(string host, int port, TimeSpan timeout)
             var ipAddress = Dns.GetHostAddresses(host)[0];
             var ep = new IPEndPoint(ipAddress, port);
 
-            DiagnosticAbstraction.Log(string.Format("Initiating connection to '{0}:{1}'.", host, port));
+            Diagnostic.Log(string.Format("Initiating connection to '{0}:{1}'.", host, port), TraceEventType.Information);
 
             var socket = SocketFactory.Create(ep.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
 
@@ -81,7 +82,7 @@ protected async Task SocketConnectAsync(string host, int port, Cancellat
 
             var ep = new IPEndPoint(ipAddress, port);
 
-            DiagnosticAbstraction.Log(string.Format("Initiating connection to '{0}:{1}'.", host, port));
+            Diagnostic.Log(string.Format("Initiating connection to '{0}:{1}'.", host, port), TraceEventType.Information);
 
             var socket = SocketFactory.Create(ep.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
             try
diff --git a/src/Renci.SshNet/Abstractions/DiagnosticAbstraction.cs b/src/Renci.SshNet/Diagnostic.cs
similarity index 61%
rename from src/Renci.SshNet/Abstractions/DiagnosticAbstraction.cs
rename to src/Renci.SshNet/Diagnostic.cs
index bc1248dc0..e8584981c 100644
--- a/src/Renci.SshNet/Abstractions/DiagnosticAbstraction.cs
+++ b/src/Renci.SshNet/Diagnostic.cs
@@ -1,28 +1,24 @@
-using System.ComponentModel;
+using System;
 using System.Diagnostics;
 
-namespace Renci.SshNet.Abstractions
+namespace Renci.SshNet
 {
     /// 
     /// Provides access to the  internals of SSH.NET.
     /// 
-    [EditorBrowsable(EditorBrowsableState.Never)]
-    public static class DiagnosticAbstraction
+    public static class Diagnostic
     {
         /// 
         /// The  instance used by SSH.NET.
         /// 
         /// 
         /// 
-        /// Currently, the library only traces events when compiled in Debug mode.
-        /// 
-        /// 
         /// Configuration on .NET Core must be done programmatically, e.g.
         /// 
-        /// DiagnosticAbstraction.Source.Switch = new SourceSwitch("sourceSwitch", "Verbose");
-        /// DiagnosticAbstraction.Source.Listeners.Remove("Default");
-        /// DiagnosticAbstraction.Source.Listeners.Add(new ConsoleTraceListener());
-        /// DiagnosticAbstraction.Source.Listeners.Add(new TextWriterTraceListener("trace.log"));
+        /// Diagnostics.Source.Switch = new SourceSwitch("sourceSwitch", nameof(SourceLevels.Verbose));
+        /// Diagnostics.Source.Listeners.Remove("Default");
+        /// Diagnostics.Source.Listeners.Add(new ConsoleTraceListener());
+        /// Diagnostics.Source.Listeners.Add(new TextWriterTraceListener("SshNetTrace.log"));
         /// 
         /// 
         /// 
@@ -53,17 +49,19 @@ public static class DiagnosticAbstraction
         public static readonly TraceSource Source = new TraceSource("SshNet.Logging");
 
         /// 
-        /// Logs a message to  at the 
-        /// level.
+        /// Logs a message to  with the specified event type.
         /// 
         /// The message to log.
-        /// The trace event type.
-        [Conditional("DEBUG")]
-        public static void Log(string text, TraceEventType type = TraceEventType.Verbose)
+        /// The trace event type.
+        public static void Log(string text, TraceEventType eventType)
+        {
+            Source.TraceEvent(eventType, Environment.CurrentManagedThreadId, text);
+        }
+
+        /// 
+        public static bool IsEnabled(TraceEventType eventType)
         {
-            Source.TraceEvent(type,
-                              System.Environment.CurrentManagedThreadId,
-                              text);
+            return Source.Switch.ShouldTrace(eventType);
         }
     }
 }
diff --git a/src/Renci.SshNet/ForwardedPortDynamic.cs b/src/Renci.SshNet/ForwardedPortDynamic.cs
index 0c6e417a8..2d354850d 100644
--- a/src/Renci.SshNet/ForwardedPortDynamic.cs
+++ b/src/Renci.SshNet/ForwardedPortDynamic.cs
@@ -1,4 +1,5 @@
 using System;
+using System.Diagnostics;
 using System.Globalization;
 using System.Linq;
 using System.Net;
@@ -413,8 +414,7 @@ private void InternalStop(TimeSpan timeout)
 
             if (!_pendingChannelCountdown.Wait(timeout))
             {
-                // TODO: log as warning
-                DiagnosticAbstraction.Log("Timeout waiting for pending channels in dynamic forwarded port to close.");
+                Diagnostic.Log("Timeout waiting for pending channels in dynamic forwarded port to close.", TraceEventType.Warning);
             }
         }
 
diff --git a/src/Renci.SshNet/ForwardedPortLocal.cs b/src/Renci.SshNet/ForwardedPortLocal.cs
index fce8f7fd7..e51d205a1 100644
--- a/src/Renci.SshNet/ForwardedPortLocal.cs
+++ b/src/Renci.SshNet/ForwardedPortLocal.cs
@@ -1,9 +1,9 @@
 using System;
+using System.Diagnostics;
 using System.Net;
 using System.Net.Sockets;
 using System.Threading;
 
-using Renci.SshNet.Abstractions;
 using Renci.SshNet.Common;
 
 namespace Renci.SshNet
@@ -399,8 +399,7 @@ private void InternalStop(TimeSpan timeout)
 
             if (!_pendingChannelCountdown.Wait(timeout))
             {
-                // TODO: log as warning
-                DiagnosticAbstraction.Log("Timeout waiting for pending channels in local forwarded port to close.");
+                Diagnostic.Log("Timeout waiting for pending channels in local forwarded port to close.", TraceEventType.Warning);
             }
         }
 
diff --git a/src/Renci.SshNet/ForwardedPortRemote.cs b/src/Renci.SshNet/ForwardedPortRemote.cs
index 10430abc3..112193eef 100644
--- a/src/Renci.SshNet/ForwardedPortRemote.cs
+++ b/src/Renci.SshNet/ForwardedPortRemote.cs
@@ -1,4 +1,5 @@
 using System;
+using System.Diagnostics;
 using System.Globalization;
 using System.Net;
 using System.Threading;
@@ -213,8 +214,7 @@ protected override void StopPort(TimeSpan timeout)
 
             if (!_pendingChannelCountdown.Wait(timeout))
             {
-                // TODO: log as warning
-                DiagnosticAbstraction.Log("Timeout waiting for pending channels in remote forwarded port to close.");
+                Diagnostic.Log("Timeout waiting for pending channels in remote forwarded port to close.", TraceEventType.Warning);
             }
 
             _status = ForwardedPortStatus.Stopped;
diff --git a/src/Renci.SshNet/Messages/Transport/DisconnectMessage.cs b/src/Renci.SshNet/Messages/Transport/DisconnectMessage.cs
index 88ad5a01a..eef36bf70 100644
--- a/src/Renci.SshNet/Messages/Transport/DisconnectMessage.cs
+++ b/src/Renci.SshNet/Messages/Transport/DisconnectMessage.cs
@@ -112,5 +112,11 @@ internal override void Process(Session session)
         {
             session.OnDisconnectReceived(this);
         }
+
+        /// 
+        public override string ToString()
+        {
+            return $"SSH_MSG_DISCONNECT ({ReasonCode}) {Description}";
+        }
     }
 }
diff --git a/src/Renci.SshNet/Security/KeyExchange.cs b/src/Renci.SshNet/Security/KeyExchange.cs
index f24dcafe1..279fb242d 100644
--- a/src/Renci.SshNet/Security/KeyExchange.cs
+++ b/src/Renci.SshNet/Security/KeyExchange.cs
@@ -1,9 +1,9 @@
 using System;
 using System.Collections.Generic;
+using System.Diagnostics;
 using System.Linq;
 using System.Security.Cryptography;
 
-using Renci.SshNet.Abstractions;
 using Renci.SshNet.Common;
 using Renci.SshNet.Compression;
 using Renci.SshNet.Messages;
@@ -71,12 +71,20 @@ public virtual void Start(Session session, KeyExchangeInitMessage message, bool
                 SendMessage(session.ClientInitMessage);
             }
 
-            // Determine encryption algorithm
+            var sessionId = Session.ToHex(Session.SessionId);
+
+            // Determine client encryption algorithm
             var clientEncryptionAlgorithmName = (from b in session.ConnectionInfo.Encryptions.Keys
                                                  from a in message.EncryptionAlgorithmsClientToServer
                                                  where a == b
                                                  select a).FirstOrDefault();
 
+            if (Diagnostic.IsEnabled(TraceEventType.Verbose))
+            {
+                Diagnostic.Log($"[{sessionId}] Encryption client to server: we offer {session.ConnectionInfo.Encryptions.Keys.Join(",")}", TraceEventType.Verbose);
+                Diagnostic.Log($"[{sessionId}] Encryption client to server: they offer {message.EncryptionAlgorithmsClientToServer.Join(",")}", TraceEventType.Verbose);
+            }
+
             if (string.IsNullOrEmpty(clientEncryptionAlgorithmName))
             {
                 throw new SshConnectionException("Client encryption algorithm not found", DisconnectReason.KeyExchangeFailed);
@@ -84,11 +92,18 @@ from a in message.EncryptionAlgorithmsClientToServer
 
             session.ConnectionInfo.CurrentClientEncryption = clientEncryptionAlgorithmName;
 
-            // Determine encryption algorithm
+            // Determine server encryption algorithm
             var serverDecryptionAlgorithmName = (from b in session.ConnectionInfo.Encryptions.Keys
                                                  from a in message.EncryptionAlgorithmsServerToClient
                                                  where a == b
                                                  select a).FirstOrDefault();
+
+            if (Diagnostic.IsEnabled(TraceEventType.Verbose))
+            {
+                Diagnostic.Log($"[{sessionId}] Encryption server to client: we offer {session.ConnectionInfo.Encryptions.Keys.Join(",")}", TraceEventType.Verbose);
+                Diagnostic.Log($"[{sessionId}] Encryption server to client: they offer {message.EncryptionAlgorithmsServerToClient.Join(",")}", TraceEventType.Verbose);
+            }
+
             if (string.IsNullOrEmpty(serverDecryptionAlgorithmName))
             {
                 throw new SshConnectionException("Server decryption algorithm not found", DisconnectReason.KeyExchangeFailed);
@@ -101,6 +116,13 @@ from a in message.EncryptionAlgorithmsServerToClient
                                            from a in message.MacAlgorithmsClientToServer
                                            where a == b
                                            select a).FirstOrDefault();
+
+            if (Diagnostic.IsEnabled(TraceEventType.Verbose))
+            {
+                Diagnostic.Log($"[{sessionId}] MAC client to server: we offer {session.ConnectionInfo.HmacAlgorithms.Keys.Join(",")}", TraceEventType.Verbose);
+                Diagnostic.Log($"[{sessionId}] MAC client to server: they offer {message.MacAlgorithmsClientToServer.Join(",")}", TraceEventType.Verbose);
+            }
+
             if (string.IsNullOrEmpty(clientHmacAlgorithmName))
             {
                 throw new SshConnectionException("Server HMAC algorithm not found", DisconnectReason.KeyExchangeFailed);
@@ -113,6 +135,13 @@ from a in message.MacAlgorithmsClientToServer
                                            from a in message.MacAlgorithmsServerToClient
                                            where a == b
                                            select a).FirstOrDefault();
+
+            if (Diagnostic.IsEnabled(TraceEventType.Verbose))
+            {
+                Diagnostic.Log($"[{sessionId}] MAC server to client: we offer {session.ConnectionInfo.HmacAlgorithms.Keys.Join(",")}", TraceEventType.Verbose);
+                Diagnostic.Log($"[{sessionId}] MAC server to client: they offer {message.MacAlgorithmsServerToClient.Join(",")}", TraceEventType.Verbose);
+            }
+
             if (string.IsNullOrEmpty(serverHmacAlgorithmName))
             {
                 throw new SshConnectionException("Server HMAC algorithm not found", DisconnectReason.KeyExchangeFailed);
@@ -124,7 +153,14 @@ from a in message.MacAlgorithmsServerToClient
             var compressionAlgorithmName = (from b in session.ConnectionInfo.CompressionAlgorithms.Keys
                                             from a in message.CompressionAlgorithmsClientToServer
                                             where a == b
-                                            select a).LastOrDefault();
+                                            select a).FirstOrDefault();
+
+            if (Diagnostic.IsEnabled(TraceEventType.Verbose))
+            {
+                Diagnostic.Log($"[{sessionId}] Compression client to server: we offer {session.ConnectionInfo.CompressionAlgorithms.Keys.Join(",")}", TraceEventType.Verbose);
+                Diagnostic.Log($"[{sessionId}] Compression client to server: they offer {message.CompressionAlgorithmsClientToServer.Join(",")}", TraceEventType.Verbose);
+            }
+
             if (string.IsNullOrEmpty(compressionAlgorithmName))
             {
                 throw new SshConnectionException("Compression algorithm not found", DisconnectReason.KeyExchangeFailed);
@@ -136,7 +172,14 @@ from a in message.CompressionAlgorithmsClientToServer
             var decompressionAlgorithmName = (from b in session.ConnectionInfo.CompressionAlgorithms.Keys
                                               from a in message.CompressionAlgorithmsServerToClient
                                               where a == b
-                                              select a).LastOrDefault();
+                                              select a).FirstOrDefault();
+
+            if (Diagnostic.IsEnabled(TraceEventType.Verbose))
+            {
+                Diagnostic.Log($"[{sessionId}] Compression server to client: we offer {session.ConnectionInfo.CompressionAlgorithms.Keys.Join(",")}", TraceEventType.Verbose);
+                Diagnostic.Log($"[{sessionId}] Compression server to client: they offer {message.CompressionAlgorithmsClientToServer.Join(",")}", TraceEventType.Verbose);
+            }
+
             if (string.IsNullOrEmpty(decompressionAlgorithmName))
             {
                 throw new SshConnectionException("Decompression algorithm not found", DisconnectReason.KeyExchangeFailed);
@@ -182,9 +225,13 @@ public Cipher CreateServerCipher()
 
             serverKey = GenerateSessionKey(SharedKey, ExchangeHash, serverKey, _serverCipherInfo.KeySize / 8);
 
-            DiagnosticAbstraction.Log(string.Format("[{0}] Creating {1} server cipher.",
+            if (Diagnostic.IsEnabled(TraceEventType.Information))
+            {
+                Diagnostic.Log(string.Format("[{0}] Creating {1} server cipher.",
                                                     Session.ToHex(Session.SessionId),
-                                                    Session.ConnectionInfo.CurrentServerEncryption));
+                                                    Session.ConnectionInfo.CurrentServerEncryption),
+                                                    TraceEventType.Information);
+            }
 
             // Create server cipher
             return _serverCipherInfo.Cipher(serverKey, serverVector);
@@ -207,9 +254,13 @@ public Cipher CreateClientCipher()
 
             clientKey = GenerateSessionKey(SharedKey, ExchangeHash, clientKey, _clientCipherInfo.KeySize / 8);
 
-            DiagnosticAbstraction.Log(string.Format("[{0}] Creating {1} client cipher.",
+            if (Diagnostic.IsEnabled(TraceEventType.Information))
+            {
+                Diagnostic.Log(string.Format("[{0}] Creating {1} client cipher.",
                                                     Session.ToHex(Session.SessionId),
-                                                    Session.ConnectionInfo.CurrentClientEncryption));
+                                                    Session.ConnectionInfo.CurrentClientEncryption),
+                                                    TraceEventType.Information);
+            }
 
             // Create client cipher
             return _clientCipherInfo.Cipher(clientKey, clientVector);
@@ -231,9 +282,13 @@ public HashAlgorithm CreateServerHash()
                                                Hash(GenerateSessionKey(SharedKey, ExchangeHash, 'F', sessionId)),
                                                _serverHashInfo.KeySize / 8);
 
-            DiagnosticAbstraction.Log(string.Format("[{0}] Creating {1} server hmac algorithm.",
+            if (Diagnostic.IsEnabled(TraceEventType.Information))
+            {
+                Diagnostic.Log(string.Format("[{0}] Creating {1} server hmac algorithm.",
                                                     Session.ToHex(Session.SessionId),
-                                                    Session.ConnectionInfo.CurrentServerHmacAlgorithm));
+                                                    Session.ConnectionInfo.CurrentServerHmacAlgorithm),
+                                                    TraceEventType.Information);
+            }
 
             return _serverHashInfo.HashAlgorithm(serverKey);
         }
@@ -254,9 +309,13 @@ public HashAlgorithm CreateClientHash()
                                                Hash(GenerateSessionKey(SharedKey, ExchangeHash, 'E', sessionId)),
                                                _clientHashInfo.KeySize / 8);
 
-            DiagnosticAbstraction.Log(string.Format("[{0}] Creating {1} client hmac algorithm.",
+            if (Diagnostic.IsEnabled(TraceEventType.Information))
+            {
+                Diagnostic.Log(string.Format("[{0}] Creating {1} client hmac algorithm.",
                                                     Session.ToHex(Session.SessionId),
-                                                    Session.ConnectionInfo.CurrentClientHmacAlgorithm));
+                                                    Session.ConnectionInfo.CurrentClientHmacAlgorithm),
+                                                    TraceEventType.Information);
+            }
 
             return _clientHashInfo.HashAlgorithm(clientKey);
         }
@@ -274,9 +333,13 @@ public Compressor CreateCompressor()
                 return null;
             }
 
-            DiagnosticAbstraction.Log(string.Format("[{0}] Creating {1} client compressor.",
+            if (Diagnostic.IsEnabled(TraceEventType.Information))
+            {
+                Diagnostic.Log(string.Format("[{0}] Creating {1} client compressor.",
                                                     Session.ToHex(Session.SessionId),
-                                                    Session.ConnectionInfo.CurrentClientCompressionAlgorithm));
+                                                    Session.ConnectionInfo.CurrentClientCompressionAlgorithm),
+                                                    TraceEventType.Information);
+            }
 
             var compressor = _compressorFactory();
 
@@ -298,9 +361,13 @@ public Compressor CreateDecompressor()
                 return null;
             }
 
-            DiagnosticAbstraction.Log(string.Format("[{0}] Creating {1} server decompressor.",
+            if (Diagnostic.IsEnabled(TraceEventType.Information))
+            {
+                Diagnostic.Log(string.Format("[{0}] Creating {1} server decompressor.",
                                                     Session.ToHex(Session.SessionId),
-                                                    Session.ConnectionInfo.CurrentServerCompressionAlgorithm));
+                                                    Session.ConnectionInfo.CurrentServerCompressionAlgorithm),
+                                                    TraceEventType.Information);
+            }
 
             var decompressor = _decompressorFactory();
 
diff --git a/src/Renci.SshNet/ServiceFactory.cs b/src/Renci.SshNet/ServiceFactory.cs
index ece94505b..89263ab7c 100644
--- a/src/Renci.SshNet/ServiceFactory.cs
+++ b/src/Renci.SshNet/ServiceFactory.cs
@@ -1,10 +1,10 @@
 using System;
 using System.Collections.Generic;
+using System.Diagnostics;
 using System.Linq;
 using System.Net.Sockets;
 using System.Text;
 
-using Renci.SshNet.Abstractions;
 using Renci.SshNet.Common;
 using Renci.SshNet.Connection;
 using Renci.SshNet.Messages.Transport;
@@ -93,17 +93,25 @@ public IKeyExchange CreateKeyExchange(IDictionary> cl
             }
 
             // find an algorithm that is supported by both client and server
-            var keyExchangeAlgorithmFactory = (from c in clientAlgorithms
+            var keyExchangeAlgorithm = (from c in clientAlgorithms
                                             from s in serverAlgorithms
                                             where s == c.Key
-                                            select c.Value).FirstOrDefault();
+                                            select c).FirstOrDefault();
 
-            if (keyExchangeAlgorithmFactory is null)
+            if (Diagnostic.IsEnabled(TraceEventType.Verbose))
+            {
+                Diagnostic.Log($"Key exchange algorithm: we offer {clientAlgorithms.Keys.Join(",")}", TraceEventType.Verbose);
+                Diagnostic.Log($"Key exchange algorithm: they offer {serverAlgorithms.Join(",")}", TraceEventType.Verbose);
+            }
+
+            Diagnostic.Log($"Key exchange algorithm: using {keyExchangeAlgorithm.Key}", TraceEventType.Information);
+
+            if (keyExchangeAlgorithm.Value is null)
             {
                 throw new SshConnectionException("Failed to negotiate key exchange algorithm.", DisconnectReason.KeyExchangeFailed);
             }
 
-            return keyExchangeAlgorithmFactory();
+            return keyExchangeAlgorithm.Value();
         }
 
         /// 
@@ -159,7 +167,7 @@ public ISftpFileReader CreateSftpFileReader(string fileName, ISftpSession sftpSe
                 fileSize = null;
                 maxPendingReads = defaultMaxPendingReads;
 
-                DiagnosticAbstraction.Log(string.Format("Failed to obtain size of file. Allowing maximum {0} pending reads: {1}", maxPendingReads, ex));
+                Diagnostic.Log(string.Format("Failed to obtain size of file. Allowing maximum {0} pending reads: {1}", maxPendingReads, ex), TraceEventType.Error);
             }
 
             return sftpSession.CreateFileReader(handle, sftpSession, chunkSize, maxPendingReads, fileSize);
diff --git a/src/Renci.SshNet/Session.cs b/src/Renci.SshNet/Session.cs
index 122e28d3d..4cf2e75e4 100644
--- a/src/Renci.SshNet/Session.cs
+++ b/src/Renci.SshNet/Session.cs
@@ -1,9 +1,12 @@
 using System;
+using System.Diagnostics;
 using System.Globalization;
 using System.Linq;
 using System.Net.Sockets;
 using System.Security.Cryptography;
+#if !NET
 using System.Text;
+#endif
 using System.Threading;
 using System.Threading.Tasks;
 
@@ -616,7 +619,11 @@ public void Connect()
                     ServerVersion = ConnectionInfo.ServerVersion = serverIdentification.ToString();
                     ConnectionInfo.ClientVersion = ClientVersion;
 
-                    DiagnosticAbstraction.Log(string.Format("Server version '{0}'.", serverIdentification));
+                    if (Diagnostic.IsEnabled(TraceEventType.Information))
+                    {
+                        Diagnostic.Log($"Our identification: '{ClientVersion}'.", TraceEventType.Information);
+                        Diagnostic.Log($"Their identification: '{serverIdentification}'.", TraceEventType.Information);
+                    }
 
                     if (!(serverIdentification.ProtocolVersion.Equals("2.0") || serverIdentification.ProtocolVersion.Equals("1.99")))
                     {
@@ -735,7 +742,11 @@ public async Task ConnectAsync(CancellationToken cancellationToken)
             ServerVersion = ConnectionInfo.ServerVersion = serverIdentification.ToString();
             ConnectionInfo.ClientVersion = ClientVersion;
 
-            DiagnosticAbstraction.Log(string.Format("Server version '{0}'.", serverIdentification));
+            if (Diagnostic.IsEnabled(TraceEventType.Information))
+            {
+                Diagnostic.Log($"Our identification: '{ClientVersion}'.", TraceEventType.Information);
+                Diagnostic.Log($"Their identification: '{serverIdentification}'.", TraceEventType.Information);
+            }
 
             if (!(serverIdentification.ProtocolVersion.Equals("2.0") || serverIdentification.ProtocolVersion.Equals("1.99")))
             {
@@ -821,7 +832,10 @@ public async Task ConnectAsync(CancellationToken cancellationToken)
         /// 
         public void Disconnect()
         {
-            DiagnosticAbstraction.Log(string.Format("[{0}] Disconnecting session.", ToHex(SessionId)));
+            if (Diagnostic.IsEnabled(TraceEventType.Information))
+            {
+                Diagnostic.Log(string.Format("[{0}] Disconnecting session.", ToHex(SessionId)), TraceEventType.Information);
+            }
 
             // send SSH_MSG_DISCONNECT message, clear socket read buffer and dispose it
             Disconnect(DisconnectReason.ByApplication, "Connection terminated by the client.");
@@ -1057,7 +1071,10 @@ internal void SendMessage(Message message)
                 WaitOnHandle(_keyExchangeCompletedWaitHandle.WaitHandle);
             }
 
-            DiagnosticAbstraction.Log(string.Format("[{0}] Sending message '{1}' to server: '{2}'.", ToHex(SessionId), message.GetType().Name, message));
+            if (Diagnostic.IsEnabled(TraceEventType.Verbose))
+            {
+                Diagnostic.Log(string.Format("[{0}] Sending message '{1}' to server: '{2}'.", ToHex(SessionId), message.GetType().Name, message), TraceEventType.Verbose);
+            }
 
             var paddingMultiplier = _clientCipher is null ? (byte) 8 : Math.Max((byte) 8, _serverCipher.MinimumSize);
             var packetData = message.GetPacket(paddingMultiplier, _clientCompression);
@@ -1168,15 +1185,16 @@ private bool TrySendMessage(Message message)
                 SendMessage(message);
                 return true;
             }
-            catch (SshException ex)
-            {
-                DiagnosticAbstraction.Log(string.Format("Failure sending message '{0}' to server: '{1}' => {2}", message.GetType().Name, message, ex));
-                return false;
-            }
-            catch (SocketException ex)
+            catch (Exception ex)
             {
-                DiagnosticAbstraction.Log(string.Format("Failure sending message '{0}' to server: '{1}' => {2}", message.GetType().Name, message, ex));
-                return false;
+                Diagnostic.Log(string.Format("Failure sending message '{0}' to server: '{1}' => {2}", message.GetType().Name, message, ex), TraceEventType.Error);
+
+                if (ex is SshException or SocketException)
+                {
+                    return false;
+                }
+
+                throw;
             }
         }
 
@@ -1324,7 +1342,10 @@ private void TrySendDisconnect(DisconnectReason reasonCode, string message)
         ///  message.
         internal void OnDisconnectReceived(DisconnectMessage message)
         {
-            DiagnosticAbstraction.Log(string.Format("[{0}] Disconnect received: {1} {2}.", ToHex(SessionId), message.ReasonCode, message.Description));
+            if (Diagnostic.IsEnabled(TraceEventType.Information))
+            {
+                Diagnostic.Log(string.Format("[{0}] Disconnect received: {1} {2}.", ToHex(SessionId), message.ReasonCode, message.Description), TraceEventType.Information);
+            }
 
             // transition to disconnecting state to avoid throwing exceptions while cleaning up, and to
             // ensure any exceptions that are raised do not overwrite the SshConnectionException that we
@@ -1423,7 +1444,10 @@ internal void OnKeyExchangeInitReceived(KeyExchangeInitMessage message)
 
             ConnectionInfo.CurrentKeyExchangeAlgorithm = _keyExchange.Name;
 
-            DiagnosticAbstraction.Log(string.Format("[{0}] Performing {1} key exchange.", ToHex(SessionId), ConnectionInfo.CurrentKeyExchangeAlgorithm));
+            if (Diagnostic.IsEnabled(TraceEventType.Information))
+            {
+                Diagnostic.Log(string.Format("[{0}] Performing {1} key exchange.", ToHex(SessionId), ConnectionInfo.CurrentKeyExchangeAlgorithm), TraceEventType.Information);
+            }
 
             _keyExchange.HostKeyReceived += KeyExchange_HostKeyReceived;
 
@@ -1726,34 +1750,34 @@ private Message LoadMessage(byte[] data, int offset, int count)
             var message = _sshMessageFactory.Create(messageType);
             message.Load(data, offset + 1, count - 1);
 
-            DiagnosticAbstraction.Log(string.Format("[{0}] Received message '{1}' from server: '{2}'.", ToHex(SessionId), message.GetType().Name, message));
+            if (Diagnostic.IsEnabled(TraceEventType.Verbose))
+            {
+                Diagnostic.Log(string.Format("[{0}] Received message '{1}' from server: '{2}'.", ToHex(SessionId), message.GetType().Name, message), TraceEventType.Verbose);
+            }
 
             return message;
         }
 
-        private static string ToHex(byte[] bytes, int offset)
+        internal static string ToHex(byte[] bytes)
         {
-            var byteCount = bytes.Length - offset;
+            if (bytes is null)
+            {
+                return null;
+            }
 
+#if NET
+            return Convert.ToHexString(bytes);
+#else
             var builder = new StringBuilder(bytes.Length * 2);
 
-            for (var i = offset; i < byteCount; i++)
+            for (var i = 0; i < bytes.Length; i++)
             {
                 var b = bytes[i];
                 _ = builder.Append(b.ToString("X2"));
             }
 
             return builder.ToString();
-        }
-
-        internal static string ToHex(byte[] bytes)
-        {
-            if (bytes is null)
-            {
-                return null;
-            }
-
-            return ToHex(bytes, 0);
+#endif
         }
 
         /// 
@@ -1870,7 +1894,7 @@ private void SocketDisconnectAndDispose()
                         {
                             try
                             {
-                                DiagnosticAbstraction.Log(string.Format("[{0}] Shutting down socket.", ToHex(SessionId)));
+                                Diagnostic.Log(string.Format("[{0}] Shutting down socket.", ToHex(SessionId)), TraceEventType.Verbose);
 
                                 // Interrupt any pending reads; should be done outside of socket read lock as we
                                 // actually want shutdown the socket to make sure blocking reads are interrupted.
@@ -1882,14 +1906,15 @@ private void SocketDisconnectAndDispose()
                             }
                             catch (SocketException ex)
                             {
-                                // TODO: log as warning
-                                DiagnosticAbstraction.Log("Failure shutting down socket: " + ex);
+                                Diagnostic.Log("Failure shutting down socket: " + ex, TraceEventType.Warning);
                             }
                         }
 
-                        DiagnosticAbstraction.Log(string.Format("[{0}] Disposing socket.", ToHex(SessionId)));
+                        Diagnostic.Log(string.Format("[{0}] Disposing socket.", ToHex(SessionId)), TraceEventType.Verbose);
+
                         _socket.Dispose();
-                        DiagnosticAbstraction.Log(string.Format("[{0}] Disposed socket.", ToHex(SessionId)));
+                        Diagnostic.Log(string.Format("[{0}] Disposed socket.", ToHex(SessionId)), TraceEventType.Verbose);
+
                         _socket = null;
                     }
                 }
@@ -1973,7 +1998,7 @@ private void RaiseError(Exception exp)
         {
             var connectionException = exp as SshConnectionException;
 
-            DiagnosticAbstraction.Log(string.Format("[{0}] Raised exception: {1}", ToHex(SessionId), exp));
+            Diagnostic.Log(string.Format("[{0}] Raised exception: {1}", ToHex(SessionId), exp), TraceEventType.Warning);
 
             if (_isDisconnecting)
             {
@@ -2000,7 +2025,8 @@ private void RaiseError(Exception exp)
 
             if (connectionException != null)
             {
-                DiagnosticAbstraction.Log(string.Format("[{0}] Disconnecting after exception: {1}", ToHex(SessionId), exp));
+                Diagnostic.Log(string.Format("[{0}] Disconnecting after exception: {1}", ToHex(SessionId), exp), TraceEventType.Information);
+
                 Disconnect(connectionException.DisconnectReason, exp.ToString());
             }
         }
@@ -2052,7 +2078,7 @@ protected virtual void Dispose(bool disposing)
 
             if (disposing)
             {
-                DiagnosticAbstraction.Log(string.Format("[{0}] Disposing session.", ToHex(SessionId)));
+                Diagnostic.Log(string.Format("[{0}] Disposing session.", ToHex(SessionId)), TraceEventType.Verbose);
 
                 Disconnect();
 
diff --git a/src/Renci.SshNet/Sftp/SftpFileReader.cs b/src/Renci.SshNet/Sftp/SftpFileReader.cs
index 8d3ef211f..88fdf5af4 100644
--- a/src/Renci.SshNet/Sftp/SftpFileReader.cs
+++ b/src/Renci.SshNet/Sftp/SftpFileReader.cs
@@ -1,5 +1,6 @@
 using System;
 using System.Collections.Generic;
+using System.Diagnostics;
 using System.Globalization;
 using System.Runtime.ExceptionServices;
 using System.Threading;
@@ -278,7 +279,7 @@ private void Dispose(bool disposing)
                     }
                     catch (Exception ex)
                     {
-                        DiagnosticAbstraction.Log("Failure closing handle: " + ex);
+                        Diagnostic.Log("Failure closing handle: " + ex, TraceEventType.Error);
                     }
                 }
             }
diff --git a/src/Renci.SshNet/SubsystemSession.cs b/src/Renci.SshNet/SubsystemSession.cs
index 70385d903..2c754a18e 100644
--- a/src/Renci.SshNet/SubsystemSession.cs
+++ b/src/Renci.SshNet/SubsystemSession.cs
@@ -1,9 +1,9 @@
 using System;
+using System.Diagnostics;
 using System.Globalization;
 using System.Runtime.ExceptionServices;
 using System.Threading;
 
-using Renci.SshNet.Abstractions;
 using Renci.SshNet.Channels;
 using Renci.SshNet.Common;
 
@@ -191,7 +191,7 @@ protected void RaiseError(Exception error)
         {
             _exception = error;
 
-            DiagnosticAbstraction.Log("Raised exception: " + error);
+            Diagnostic.Log("Raised exception: " + error, TraceEventType.Warning);
 
             _ = _errorOccuredWaitHandle?.Set();
 
diff --git a/test/Renci.SshNet.IntegrationTests/TestsFixtures/IntegrationTestBase.cs b/test/Renci.SshNet.IntegrationTests/TestsFixtures/IntegrationTestBase.cs
index d08578e4e..dfe8f18bb 100644
--- a/test/Renci.SshNet.IntegrationTests/TestsFixtures/IntegrationTestBase.cs
+++ b/test/Renci.SshNet.IntegrationTests/TestsFixtures/IntegrationTestBase.cs
@@ -1,7 +1,5 @@
 using System.Diagnostics;
 
-using Renci.SshNet.Abstractions;
-
 namespace Renci.SshNet.IntegrationTests.TestsFixtures
 {
     /// 
@@ -88,15 +86,15 @@ protected void CreateTestFile(string fileName, int size)
 
         protected void EnableTracing()
         {
-            DiagnosticAbstraction.Source.Switch = new SourceSwitch("sourceSwitch", nameof(SourceLevels.Verbose));
-            DiagnosticAbstraction.Source.Listeners.Remove("Default");
-            DiagnosticAbstraction.Source.Listeners.Add(new ConsoleTraceListener() { Name = "TestConsoleLogger" });
+            Diagnostic.Source.Switch = new SourceSwitch("sourceSwitch", nameof(SourceLevels.Verbose));
+            Diagnostic.Source.Listeners.Remove("Default");
+            Diagnostic.Source.Listeners.Add(new ConsoleTraceListener() { Name = "TestConsoleLogger" });
         }
 
         protected void DisableTracing()
         {
-            DiagnosticAbstraction.Source.Switch = new SourceSwitch("sourceSwitch", nameof(SourceLevels.Off));
-            DiagnosticAbstraction.Source.Listeners.Remove("TestConsoleLogger");
+            Diagnostic.Source.Switch = new SourceSwitch("sourceSwitch", nameof(SourceLevels.Off));
+            Diagnostic.Source.Listeners.Remove("TestConsoleLogger");
         }
     }
 }