Skip to content

Commit 70c1246

Browse files
mus65Rob-Hague
andauthored
Replace DiagnosticAbstration with Microsoft.Extensions.Logging.Abstractions (#1509)
* Replace DiagnosticAbstrations with Microsoft.Extensions.Logging.Abstractions * add documentation * reduce allocations by SessionId hex conversion generate the hex string once instead of every log call and optimize ToHex(). * Update docfx/logging.md Co-authored-by: Rob Hague <[email protected]> * reduce log levels * hook up testcontainers logging * drop packet logs further down to trace * add kex traces --------- Co-authored-by: Rob Hague <[email protected]>
1 parent f50fdcc commit 70c1246

25 files changed

+300
-181
lines changed

.editorconfig

+3
Original file line numberDiff line numberDiff line change
@@ -704,6 +704,9 @@ dotnet_code_quality.CA1828.api_surface = all
704704
# Similar to MA0053, but does not support public types and types that define (new) virtual members.
705705
dotnet_diagnostic.CA1852.severity = none
706706

707+
# CA1848: don't enforce LoggerMessage pattern
708+
dotnet_diagnostic.CA1848.severity = suggestion
709+
707710
# CA1859: Change return type for improved performance
708711
#
709712
# By default, this diagnostic is only reported for private members.

CONTRIBUTING.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,9 @@ The repository makes use of continuous integration (CI) with GitHub Actions to v
3434

3535
## Good to know
3636

37-
### TraceSource logging
37+
### Logging
3838

39-
The Debug build of SSH.NET contains rudimentary logging functionality via `System.Diagnostics.TraceSource`. See `Renci.SshNet.Abstractions.DiagnosticAbstraction` for usage examples.
39+
The tests always log to the console. See the [Logging documentation](https://sshnet.github.io/SSH.NET/logging.html) on how to set a custom `ILoggerFactory`.
4040

4141
### Wireshark
4242

Directory.Packages.props

+3
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@
1515
<PackageVersion Include="Meziantou.Analyzer" Version="2.0.163" />
1616
<!-- Must be kept at version 1.0.0 or higher, see https://github.com/sshnet/SSH.NET/pull/1288 for details. -->
1717
<PackageVersion Include="Microsoft.Bcl.AsyncInterfaces" Version="1.0.0" />
18+
<PackageVersion Include="Microsoft.Extensions.Logging" Version="8.0.0" />
19+
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="6.0.0" />
20+
<PackageVersion Include="Microsoft.Extensions.Logging.Console" Version="8.0.0" />
1821
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
1922
<PackageVersion Include="MSTest.TestAdapter" Version="3.6.2" />
2023
<PackageVersion Include="MSTest.TestFramework" Version="3.6.2" />

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ The main types provided by this library are:
6262
## Additional Documentation
6363

6464
* [Further examples](https://sshnet.github.io/SSH.NET/examples.html)
65+
* [Logging](https://sshnet.github.io/SSH.NET/logging.html)
6566
* [API browser](https://sshnet.github.io/SSH.NET/api/Renci.SshNet.html)
6667

6768
## Encryption Methods

docfx/logging.md

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
Logging
2+
=================
3+
4+
SSH.NET uses the [Microsoft.Extensions.Logging](https://learn.microsoft.com/dotnet/core/extensions/logging) API to log diagnostic messages. In order to access the log messages of SSH.NET in your own application for diagnosis, register your own `ILoggerFactory` before using the SSH.NET APIs, for example:
5+
6+
```cs
7+
ILoggerFactory loggerFactory = LoggerFactory.Create(builder =>
8+
{
9+
builder.SetMinimumLevel(LogLevel.Debug);
10+
builder.AddConsole();
11+
});
12+
13+
Renci.SshNet.SshNetLoggingConfiguration.InitializeLogging(loggerFactory);
14+
15+
All messages by SSH.NET are logged under the `Renci.SshNet` category.

src/Renci.SshNet/Abstractions/DiagnosticAbstraction.cs

-69
This file was deleted.

src/Renci.SshNet/BaseClient.cs

+6-3
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44
using System.Threading;
55
using System.Threading.Tasks;
66

7-
using Renci.SshNet.Abstractions;
7+
using Microsoft.Extensions.Logging;
8+
89
using Renci.SshNet.Common;
910
using Renci.SshNet.Messages.Transport;
1011

@@ -20,6 +21,7 @@ public abstract class BaseClient : IBaseClient
2021
/// </summary>
2122
private readonly bool _ownsConnectionInfo;
2223

24+
private readonly ILogger _logger;
2325
private readonly IServiceFactory _serviceFactory;
2426
private readonly object _keepAliveLock = new object();
2527
private TimeSpan _keepAliveInterval;
@@ -190,6 +192,7 @@ private protected BaseClient(ConnectionInfo connectionInfo, bool ownsConnectionI
190192
_connectionInfo = connectionInfo;
191193
_ownsConnectionInfo = ownsConnectionInfo;
192194
_serviceFactory = serviceFactory;
195+
_logger = SshNetLoggingConfiguration.LoggerFactory.CreateLogger(GetType());
193196
_keepAliveInterval = Timeout.InfiniteTimeSpan;
194197
}
195198

@@ -343,7 +346,7 @@ public async Task ConnectAsync(CancellationToken cancellationToken)
343346
/// <exception cref="ObjectDisposedException">The method was called after the client was disposed.</exception>
344347
public void Disconnect()
345348
{
346-
DiagnosticAbstraction.Log("Disconnecting client.");
349+
_logger.LogInformation("Disconnecting client.");
347350

348351
CheckDisposed();
349352

@@ -442,7 +445,7 @@ protected virtual void Dispose(bool disposing)
442445

443446
if (disposing)
444447
{
445-
DiagnosticAbstraction.Log("Disposing client.");
448+
_logger.LogDebug("Disposing client.");
446449

447450
Disconnect();
448451

src/Renci.SshNet/Channels/Channel.cs

+5-2
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22
using System.Net.Sockets;
33
using System.Threading;
44

5-
using Renci.SshNet.Abstractions;
5+
using Microsoft.Extensions.Logging;
6+
67
using Renci.SshNet.Common;
78
using Renci.SshNet.Messages;
89
using Renci.SshNet.Messages.Connection;
@@ -18,6 +19,7 @@ internal abstract class Channel : IChannel
1819
private readonly Lock _messagingLock = new Lock();
1920
private readonly uint _initialWindowSize;
2021
private readonly ISession _session;
22+
private readonly ILogger _logger;
2123
private EventWaitHandle _channelClosedWaitHandle = new ManualResetEvent(initialState: false);
2224
private EventWaitHandle _channelServerWindowAdjustWaitHandle = new ManualResetEvent(initialState: false);
2325
private uint? _remoteWindowSize;
@@ -81,6 +83,7 @@ protected Channel(ISession session, uint localChannelNumber, uint localWindowSiz
8183
LocalChannelNumber = localChannelNumber;
8284
LocalPacketSize = localPacketSize;
8385
LocalWindowSize = localWindowSize;
86+
_logger = SshNetLoggingConfiguration.LoggerFactory.CreateLogger(GetType());
8487

8588
session.ChannelWindowAdjustReceived += OnChannelWindowAdjust;
8689
session.ChannelDataReceived += OnChannelData;
@@ -555,7 +558,7 @@ protected virtual void Close()
555558
var closeWaitResult = _session.TryWait(_channelClosedWaitHandle, ConnectionInfo.ChannelCloseTimeout);
556559
if (closeWaitResult != WaitResult.Success)
557560
{
558-
DiagnosticAbstraction.Log(string.Format("Wait for channel close not successful: {0:G}.", closeWaitResult));
561+
_logger.LogInformation("Wait for channel close not successful: {CloseWaitResult}", closeWaitResult);
559562
}
560563
}
561564
}

src/Renci.SshNet/Channels/ChannelDirectTcpip.cs

+5-3
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
using System.Net.Sockets;
44
using System.Threading;
55

6+
using Microsoft.Extensions.Logging;
7+
68
using Renci.SshNet.Abstractions;
79
using Renci.SshNet.Common;
810
using Renci.SshNet.Messages.Connection;
@@ -15,7 +17,7 @@ namespace Renci.SshNet.Channels
1517
internal sealed class ChannelDirectTcpip : ClientChannel, IChannelDirectTcpip
1618
{
1719
private readonly Lock _socketLock = new Lock();
18-
20+
private readonly ILogger _logger;
1921
private EventWaitHandle _channelOpen = new AutoResetEvent(initialState: false);
2022
private EventWaitHandle _channelData = new AutoResetEvent(initialState: false);
2123
private IForwardedPort _forwardedPort;
@@ -31,6 +33,7 @@ internal sealed class ChannelDirectTcpip : ClientChannel, IChannelDirectTcpip
3133
public ChannelDirectTcpip(ISession session, uint localChannelNumber, uint localWindowSize, uint localPacketSize)
3234
: base(session, localChannelNumber, localWindowSize, localPacketSize)
3335
{
36+
_logger = SshNetLoggingConfiguration.LoggerFactory.CreateLogger<ChannelDirectTcpip>();
3437
}
3538

3639
/// <summary>
@@ -157,8 +160,7 @@ private void ShutdownSocket(SocketShutdown how)
157160
}
158161
catch (SocketException ex)
159162
{
160-
// TODO: log as warning
161-
DiagnosticAbstraction.Log("Failure shutting down socket: " + ex);
163+
_logger.LogInformation(ex, "Failure shutting down socket");
162164
}
163165
}
164166
}

src/Renci.SshNet/Channels/ChannelForwardedTcpip.cs

+5-2
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
using System.Threading;
66
#endif
77

8+
using Microsoft.Extensions.Logging;
9+
810
using Renci.SshNet.Abstractions;
911
using Renci.SshNet.Common;
1012
using Renci.SshNet.Messages.Connection;
@@ -17,6 +19,7 @@ namespace Renci.SshNet.Channels
1719
internal sealed class ChannelForwardedTcpip : ServerChannel, IChannelForwardedTcpip
1820
{
1921
private readonly Lock _socketShutdownAndCloseLock = new Lock();
22+
private readonly ILogger _logger;
2023
private Socket _socket;
2124
private IForwardedPort _forwardedPort;
2225

@@ -45,6 +48,7 @@ internal ChannelForwardedTcpip(ISession session,
4548
remoteWindowSize,
4649
remotePacketSize)
4750
{
51+
_logger = SshNetLoggingConfiguration.LoggerFactory.CreateLogger<ChannelForwardedTcpip>();
4852
}
4953

5054
/// <summary>
@@ -142,8 +146,7 @@ private void ShutdownSocket(SocketShutdown how)
142146
}
143147
catch (SocketException ex)
144148
{
145-
// TODO: log as warning
146-
DiagnosticAbstraction.Log("Failure shutting down socket: " + ex);
149+
_logger.LogInformation(ex, "Failure shutting down socket");
147150
}
148151
}
149152
}

src/Renci.SshNet/Common/Extensions.cs

+7
Original file line numberDiff line numberDiff line change
@@ -351,5 +351,12 @@ internal static bool IsConnected(this Socket socket)
351351

352352
return socket.Connected;
353353
}
354+
355+
internal static string Join(this IEnumerable<string> values, string separator)
356+
{
357+
// Used to avoid analyzers asking to "use an overload with a char parameter"
358+
// which is not available on all targets.
359+
return string.Join(separator, values);
360+
}
354361
}
355362
}

src/Renci.SshNet/Connection/ConnectorBase.cs

+7-2
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
using System.Threading;
55
using System.Threading.Tasks;
66

7+
using Microsoft.Extensions.Logging;
8+
79
using Renci.SshNet.Abstractions;
810
using Renci.SshNet.Common;
911
using Renci.SshNet.Messages.Transport;
@@ -12,11 +14,14 @@ namespace Renci.SshNet.Connection
1214
{
1315
internal abstract class ConnectorBase : IConnector
1416
{
17+
private readonly ILogger _logger;
18+
1519
protected ConnectorBase(ISocketFactory socketFactory)
1620
{
1721
ThrowHelper.ThrowIfNull(socketFactory);
1822

1923
SocketFactory = socketFactory;
24+
_logger = SshNetLoggingConfiguration.LoggerFactory.CreateLogger(GetType());
2025
}
2126

2227
internal ISocketFactory SocketFactory { get; private set; }
@@ -34,7 +39,7 @@ protected ConnectorBase(ISocketFactory socketFactory)
3439
/// <exception cref="SocketException">An error occurred trying to establish the connection.</exception>
3540
protected Socket SocketConnect(EndPoint endPoint, TimeSpan timeout)
3641
{
37-
DiagnosticAbstraction.Log(string.Format("Initiating connection to '{0}'.", endPoint));
42+
_logger.LogInformation("Initiating connection to '{EndPoint}'.", endPoint);
3843

3944
var socket = SocketFactory.Create(SocketType.Stream, ProtocolType.Tcp);
4045

@@ -65,7 +70,7 @@ protected async Task<Socket> SocketConnectAsync(EndPoint endPoint, CancellationT
6570
{
6671
cancellationToken.ThrowIfCancellationRequested();
6772

68-
DiagnosticAbstraction.Log(string.Format("Initiating connection to '{0}'.", endPoint));
73+
_logger.LogInformation("Initiating connection to '{EndPoint}'.", endPoint);
6974

7075
var socket = SocketFactory.Create(SocketType.Stream, ProtocolType.Tcp);
7176
try

src/Renci.SshNet/ForwardedPortDynamic.cs

+5-2
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
using System.Text;
88
using System.Threading;
99

10+
using Microsoft.Extensions.Logging;
11+
1012
using Renci.SshNet.Abstractions;
1113
using Renci.SshNet.Channels;
1214
using Renci.SshNet.Common;
@@ -19,6 +21,7 @@ namespace Renci.SshNet
1921
/// </summary>
2022
public class ForwardedPortDynamic : ForwardedPort
2123
{
24+
private readonly ILogger _logger;
2225
private ForwardedPortStatus _status;
2326

2427
/// <summary>
@@ -72,6 +75,7 @@ public ForwardedPortDynamic(string host, uint port)
7275
BoundHost = host;
7376
BoundPort = port;
7477
_status = ForwardedPortStatus.Stopped;
78+
_logger = SshNetLoggingConfiguration.LoggerFactory.CreateLogger<ForwardedPortDynamic>();
7579
}
7680

7781
/// <summary>
@@ -409,8 +413,7 @@ private void InternalStop(TimeSpan timeout)
409413

410414
if (!_pendingChannelCountdown.Wait(timeout))
411415
{
412-
// TODO: log as warning
413-
DiagnosticAbstraction.Log("Timeout waiting for pending channels in dynamic forwarded port to close.");
416+
_logger.LogInformation("Timeout waiting for pending channels in dynamic forwarded port to close.");
414417
}
415418
}
416419

0 commit comments

Comments
 (0)