Skip to content

Refactor logging to allow a loggerfactory per session #1673

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jul 26, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 25 additions & 1 deletion docfx/logging.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,31 @@
Logging
=================

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:
SSH.NET uses the [Microsoft.Extensions.Logging](https://learn.microsoft.com/dotnet/core/extensions/logging) API to log diagnostic messages.

It is possible to specify a logger in the `ConnectionInfo`, for example:

```cs
using Microsoft.Extensions.Logging;

ILoggerFactory loggerFactory = LoggerFactory.Create(builder =>
{
builder.SetMinimumLevel(LogLevel.Debug);
builder.AddConsole();
});

var connectionInfo = new ConnectionInfo("sftp.foo.com",
"guest",
new PasswordAuthenticationMethod("guest", "pwd"));

connectionInfo.LoggerFactory = loggerFactory;
using (var client = new SftpClient(connectionInfo))
{
client.Connect();
}
```

You can also register an application-wide `ILoggerFactory` before using the SSH.NET APIs, this will be used as a fallback if the `ConnectionInfo` is not set, for example:

```cs
using Microsoft.Extensions.Logging;
Expand Down
2 changes: 1 addition & 1 deletion src/Renci.SshNet/BaseClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ private protected BaseClient(ConnectionInfo connectionInfo, bool ownsConnectionI
_connectionInfo = connectionInfo;
_ownsConnectionInfo = ownsConnectionInfo;
_serviceFactory = serviceFactory;
_logger = SshNetLoggingConfiguration.LoggerFactory.CreateLogger(GetType());
_logger = (connectionInfo.LoggerFactory ?? SshNetLoggingConfiguration.LoggerFactory).CreateLogger(GetType());
_keepAliveInterval = Timeout.InfiniteTimeSpan;
}

Expand Down
2 changes: 1 addition & 1 deletion src/Renci.SshNet/Channels/Channel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ protected Channel(ISession session, uint localChannelNumber, uint localWindowSiz
LocalChannelNumber = localChannelNumber;
LocalPacketSize = localPacketSize;
LocalWindowSize = localWindowSize;
_logger = SshNetLoggingConfiguration.LoggerFactory.CreateLogger(GetType());
_logger = session.SessionLoggerFactory.CreateLogger(GetType());

session.ChannelWindowAdjustReceived += OnChannelWindowAdjust;
session.ChannelDataReceived += OnChannelData;
Expand Down
2 changes: 1 addition & 1 deletion src/Renci.SshNet/Channels/ChannelDirectTcpip.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ internal sealed class ChannelDirectTcpip : ClientChannel, IChannelDirectTcpip
public ChannelDirectTcpip(ISession session, uint localChannelNumber, uint localWindowSize, uint localPacketSize)
: base(session, localChannelNumber, localWindowSize, localPacketSize)
{
_logger = SshNetLoggingConfiguration.LoggerFactory.CreateLogger<ChannelDirectTcpip>();
_logger = session.SessionLoggerFactory.CreateLogger<ChannelDirectTcpip>();
}

/// <summary>
Expand Down
2 changes: 1 addition & 1 deletion src/Renci.SshNet/Channels/ChannelForwardedTcpip.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ internal ChannelForwardedTcpip(ISession session,
remoteWindowSize,
remotePacketSize)
{
_logger = SshNetLoggingConfiguration.LoggerFactory.CreateLogger<ChannelForwardedTcpip>();
_logger = session.SessionLoggerFactory.CreateLogger<ChannelForwardedTcpip>();
}

/// <summary>
Expand Down
4 changes: 2 additions & 2 deletions src/Renci.SshNet/Connection/ConnectorBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,12 @@ internal abstract class ConnectorBase : IConnector
{
private readonly ILogger _logger;

protected ConnectorBase(ISocketFactory socketFactory)
protected ConnectorBase(ISocketFactory socketFactory, ILoggerFactory loggerFactory)
{
ThrowHelper.ThrowIfNull(socketFactory);

SocketFactory = socketFactory;
_logger = SshNetLoggingConfiguration.LoggerFactory.CreateLogger(GetType());
_logger = loggerFactory.CreateLogger(GetType());
}

internal ISocketFactory SocketFactory { get; private set; }
Expand Down
6 changes: 4 additions & 2 deletions src/Renci.SshNet/Connection/DirectConnector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@
using System.Net.Sockets;
using System.Threading;

using Microsoft.Extensions.Logging;

namespace Renci.SshNet.Connection
{
internal sealed class DirectConnector : ConnectorBase
{
public DirectConnector(ISocketFactory socketFactory)
: base(socketFactory)
public DirectConnector(ISocketFactory socketFactory, ILoggerFactory loggerFactory)
: base(socketFactory, loggerFactory)
{
}

Expand Down
6 changes: 4 additions & 2 deletions src/Renci.SshNet/Connection/HttpConnector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
using System.Net.Sockets;
using System.Text.RegularExpressions;

using Microsoft.Extensions.Logging;

using Renci.SshNet.Abstractions;
using Renci.SshNet.Common;

Expand Down Expand Up @@ -48,8 +50,8 @@ internal sealed partial class HttpConnector : ProxyConnector
private static readonly Regex HttpHeaderRegex = new Regex(HttpHeaderPattern, RegexOptions.Compiled);
#endif

public HttpConnector(ISocketFactory socketFactory)
: base(socketFactory)
public HttpConnector(ISocketFactory socketFactory, ILoggerFactory loggerFactory)
: base(socketFactory, loggerFactory)
{
}

Expand Down
6 changes: 4 additions & 2 deletions src/Renci.SshNet/Connection/ProxyConnector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
using System.Threading;
using System.Threading.Tasks;

using Microsoft.Extensions.Logging;

namespace Renci.SshNet.Connection
{
/// <summary>
Expand All @@ -12,8 +14,8 @@ namespace Renci.SshNet.Connection
/// </summary>
internal abstract class ProxyConnector : ConnectorBase
{
protected ProxyConnector(ISocketFactory socketFactory)
: base(socketFactory)
protected ProxyConnector(ISocketFactory socketFactory, ILoggerFactory loggerFactory)
: base(socketFactory, loggerFactory)
{
}

Expand Down
6 changes: 4 additions & 2 deletions src/Renci.SshNet/Connection/Socks4Connector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
using System.Net.Sockets;
using System.Text;

using Microsoft.Extensions.Logging;

using Renci.SshNet.Abstractions;
using Renci.SshNet.Common;

Expand All @@ -17,8 +19,8 @@ namespace Renci.SshNet.Connection
/// </remarks>
internal sealed class Socks4Connector : ProxyConnector
{
public Socks4Connector(ISocketFactory socketFactory)
: base(socketFactory)
public Socks4Connector(ISocketFactory socketFactory, ILoggerFactory loggerFactory)
: base(socketFactory, loggerFactory)
{
}

Expand Down
6 changes: 4 additions & 2 deletions src/Renci.SshNet/Connection/Socks5Connector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
using System.Net.Sockets;
using System.Text;

using Microsoft.Extensions.Logging;

using Renci.SshNet.Abstractions;
using Renci.SshNet.Common;

Expand All @@ -18,8 +20,8 @@ namespace Renci.SshNet.Connection
/// </remarks>
internal sealed class Socks5Connector : ProxyConnector
{
public Socks5Connector(ISocketFactory socketFactory)
: base(socketFactory)
public Socks5Connector(ISocketFactory socketFactory, ILoggerFactory loggerFactory)
: base(socketFactory, loggerFactory)
{
}

Expand Down
10 changes: 10 additions & 0 deletions src/Renci.SshNet/ConnectionInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
using System.Security.Cryptography;
using System.Text;

using Microsoft.Extensions.Logging;

using Renci.SshNet.Common;
using Renci.SshNet.Compression;
using Renci.SshNet.Messages.Authentication;
Expand Down Expand Up @@ -495,5 +497,13 @@ IList<IAuthenticationMethod> IConnectionInfoInternal.AuthenticationMethods
get { return AuthenticationMethods.Cast<IAuthenticationMethod>().ToList(); }
#pragma warning restore S2365 // Properties should not make collection or array copies
}

/// <summary>
/// Gets or sets logger factory for this connection.
/// </summary>
/// <value>
/// The logger factory for this connection. If <see langword="null"/> then <see cref="SshNetLoggingConfiguration.LoggerFactory"/> is used.
/// </value>
public ILoggerFactory LoggerFactory { get; set; }
}
}
9 changes: 6 additions & 3 deletions src/Renci.SshNet/ForwardedPortDynamic.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ namespace Renci.SshNet
/// </summary>
public class ForwardedPortDynamic : ForwardedPort
{
private readonly ILogger _logger;
private ForwardedPortStatus _status;

/// <summary>
Expand Down Expand Up @@ -75,7 +74,6 @@ public ForwardedPortDynamic(string host, uint port)
BoundHost = host;
BoundPort = port;
_status = ForwardedPortStatus.Stopped;
_logger = SshNetLoggingConfiguration.LoggerFactory.CreateLogger<ForwardedPortDynamic>();
}

/// <summary>
Expand Down Expand Up @@ -416,7 +414,12 @@ private void InternalStop(TimeSpan timeout)

if (!_pendingChannelCountdown.Wait(timeout))
{
_logger.LogInformation("Timeout waiting for pending channels in dynamic forwarded port to close.");
var session = Session;
if (session != null)
{
var logger = session.SessionLoggerFactory.CreateLogger<ForwardedPortDynamic>();
logger.LogInformation("Timeout waiting for pending channels in dynamic forwarded port to close.");
}
}
}

Expand Down
9 changes: 6 additions & 3 deletions src/Renci.SshNet/ForwardedPortLocal.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ namespace Renci.SshNet
/// </summary>
public partial class ForwardedPortLocal : ForwardedPort
{
private readonly ILogger _logger;
private ForwardedPortStatus _status;
private bool _isDisposed;
private Socket _listener;
Expand Down Expand Up @@ -103,7 +102,6 @@ public ForwardedPortLocal(string boundHost, uint boundPort, string host, uint po
Host = host;
Port = port;
_status = ForwardedPortStatus.Stopped;
_logger = SshNetLoggingConfiguration.LoggerFactory.CreateLogger<ForwardedPortLocal>();
}

/// <summary>
Expand Down Expand Up @@ -390,7 +388,12 @@ private void InternalStop(TimeSpan timeout)

if (!_pendingChannelCountdown.Wait(timeout))
{
_logger.LogInformation("Timeout waiting for pending channels in local forwarded port to close.");
var session = Session;
if (session != null)
{
var logger = session.SessionLoggerFactory.CreateLogger<ForwardedPortLocal>();
logger.LogInformation("Timeout waiting for pending channels in local forwarded port to close.");
}
}
}

Expand Down
9 changes: 6 additions & 3 deletions src/Renci.SshNet/ForwardedPortRemote.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ namespace Renci.SshNet
/// </summary>
public class ForwardedPortRemote : ForwardedPort
{
private readonly ILogger _logger;
private ForwardedPortStatus _status;
private bool _requestStatus;
private EventWaitHandle _globalRequestResponse = new AutoResetEvent(initialState: false);
Expand Down Expand Up @@ -100,7 +99,6 @@ public ForwardedPortRemote(IPAddress boundHostAddress, uint boundPort, IPAddress
HostAddress = hostAddress;
Port = port;
_status = ForwardedPortStatus.Stopped;
_logger = SshNetLoggingConfiguration.LoggerFactory.CreateLogger<ForwardedPortRemote>();
}

/// <summary>
Expand Down Expand Up @@ -212,7 +210,12 @@ protected override void StopPort(TimeSpan timeout)

if (!_pendingChannelCountdown.Wait(timeout))
{
_logger.LogInformation("Timeout waiting for pending channels in remote forwarded port to close.");
var session = Session;
if (session != null)
{
var logger = session.SessionLoggerFactory.CreateLogger<ForwardedPortRemote>();
logger.LogInformation("Timeout waiting for pending channels in remote forwarded port to close.");
}
}

_status = ForwardedPortStatus.Stopped;
Expand Down
10 changes: 10 additions & 0 deletions src/Renci.SshNet/IConnectionInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
using System.Collections.Generic;
using System.Text;

using Microsoft.Extensions.Logging;

using Renci.SshNet.Common;
using Renci.SshNet.Messages.Connection;

Expand All @@ -12,6 +14,14 @@ namespace Renci.SshNet
/// </summary>
internal interface IConnectionInfo
{
/// <summary>
/// Gets the logger factory for this connection.
/// </summary>
/// <value>
/// The logger factory for this connection. If <see langword="null"/> then <see cref="SshNetLoggingConfiguration.LoggerFactory"/> is used.
/// </value>
public ILoggerFactory LoggerFactory { get; }

/// <summary>
/// Gets the timeout to used when waiting for a server to acknowledge closing a channel.
/// </summary>
Expand Down
10 changes: 10 additions & 0 deletions src/Renci.SshNet/ISession.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
using System.Threading;
using System.Threading.Tasks;

using Microsoft.Extensions.Logging;

using Renci.SshNet.Channels;
using Renci.SshNet.Common;
using Renci.SshNet.Messages;
Expand All @@ -22,6 +24,14 @@ internal interface ISession : IDisposable
/// <value>The connection info.</value>
IConnectionInfo ConnectionInfo { get; }

/// <summary>
/// Gets the logger factory for this session.
/// </summary>
/// <value>
/// The logger factory for this session. Will never return <see langword="null"/>.
/// </value>
public ILoggerFactory SessionLoggerFactory { get; }

/// <summary>
/// Gets a value indicating whether the session is connected.
/// </summary>
Expand Down
10 changes: 10 additions & 0 deletions src/Renci.SshNet/ISubsystemSession.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
using System;
using System.Threading;

using Microsoft.Extensions.Logging;

using Renci.SshNet.Common;

namespace Renci.SshNet
Expand All @@ -10,6 +12,14 @@ namespace Renci.SshNet
/// </summary>
internal interface ISubsystemSession : IDisposable
{
/// <summary>
/// Gets the logger factory for this subsystem session.
/// </summary>
/// <value>
/// The logger factory for this connection. Will never return <see langword="null"/>.
/// </value>
public ILoggerFactory SessionLoggerFactory { get; }

/// <summary>
/// Gets or sets the number of milliseconds to wait for an operation to complete.
/// </summary>
Expand Down
4 changes: 2 additions & 2 deletions src/Renci.SshNet/Security/KeyExchange.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ namespace Renci.SshNet.Security
/// </summary>
public abstract class KeyExchange : Algorithm, IKeyExchange
{
private readonly ILogger _logger;
private ILogger _logger;
private Func<byte[], KeyHostAlgorithm> _hostKeyAlgorithmFactory;
private CipherInfo _clientCipherInfo;
private CipherInfo _serverCipherInfo;
Expand Down Expand Up @@ -69,13 +69,13 @@ public byte[] ExchangeHash
/// </summary>
protected KeyExchange()
{
_logger = SshNetLoggingConfiguration.LoggerFactory.CreateLogger(GetType());
}

/// <inheritdoc/>
public virtual void Start(Session session, KeyExchangeInitMessage message, bool sendClientInitMessage)
{
Session = session;
_logger = Session.SessionLoggerFactory.CreateLogger(GetType());

if (sendClientInitMessage)
{
Expand Down
Loading