Logging and diagnostics
SshSharp reports what it does through the standard .NET channels: ILogger for logs, an ActivitySource for traces and a Meter
for metrics, all named SshSharp. None of them ever contain passwords, private keys, command text, user names or host names.
When to use
- A connection fails and the exception message is not enough: log at
Debug. - You run SshSharp in a service: export traces and metrics with OpenTelemetry.
- You need to know what a server negotiated (for an audit, or to find hosts without post-quantum key exchange).
Log
using var loggerFactory = LoggerFactory.Create(builder => builder
.AddSimpleConsole(o => o.TimestampFormat = "HH:mm:ss ")
.SetMinimumLevel(LogLevel.Debug));
await using var client = await SshClient.ConnectAsync("example.com", 22, new SshClientOptions
{
UserName = "deploy",
Password = Environment.GetEnvironmentVariable("DEPLOY_PASSWORD"),
HostKeyVerifier = verifier,
Logger = loggerFactory.CreateLogger("SshSharp"), // never logs passwords, keys, command text or user names
}, cancellationToken);
At Information: connected (algorithms and host key fingerprint), authenticated (which method), identity files and agent keys that
were skipped and why, host keys recorded by UpdateHostKeys, and connections without post-quantum key exchange. At Debug: key
exchanges and rekeys, channels and sessions, each authentication attempt.
With dependency injection, clients log through the container's ILoggerFactory automatically, under the category SshSharp.
Inspect a connection
Console.WriteLine($"server: {client.ServerIdentification}"); // SSH-2.0-OpenSSH_9.9 ...
Console.WriteLine($"host key: {client.HostKey.KeyType} {client.HostKey.Fingerprint}");
Console.WriteLine($"kex: {client.Algorithms.KeyExchange} (post-quantum: {client.Algorithms.IsPostQuantum})");
Console.WriteLine($"cipher: {client.Algorithms.EncryptionClientToServer}");
Console.WriteLine($"strict kex: {client.Algorithms.StrictKeyExchange}");
Console.WriteLine($"round trip: {(await client.PingAsync(cancellationToken)).TotalMilliseconds:F1} ms");
Console.WriteLine($"client: {SshSharpInfo.SoftwareVersion}"); // SshSharp_1.0.0
Trace and measure with OpenTelemetry
builder.Services.AddOpenTelemetry()
.WithTracing(tracing => tracing.AddSource(SshTelemetry.SourceName))
.WithMetrics(metrics => metrics.AddMeter(SshTelemetry.SourceName));
The SshSharp.Connect span covers connecting, key exchange and authentication (each ProxyJump hop is a span of its own inside
it), and SshSharp.Command covers each RunCommandAsync. The metrics count connections, channels, commands, bytes and key
exchanges, and time connecting. Errors and diagnostics lists every tag and instrument.
Listen without OpenTelemetry
The BCL's listeners see the same data, for a quick look in a console tool or a test:
// Without OpenTelemetry: the connect and command spans, straight from the ActivitySource.
var listener = new ActivityListener
{
ShouldListenTo = source => source.Name == SshTelemetry.SourceName,
Sample = (ref ActivityCreationOptions<ActivityContext> _) => ActivitySamplingResult.AllDataAndRecorded,
ActivityStopped = activity => Console.WriteLine(
$"{activity.OperationName} {activity.Duration.TotalMilliseconds:F0} ms {activity.Status} " +
string.Join(' ', activity.Tags.Select(t => $"{t.Key}={t.Value}"))),
};
ActivitySource.AddActivityListener(listener);
var listener = new MeterListener
{
InstrumentPublished = (instrument, l) =>
{
if (instrument.Meter.Name == SshTelemetry.SourceName)
{
l.EnableMeasurementEvents(instrument);
}
},
};
listener.SetMeasurementEventCallback<long>((instrument, value, tags, _) => Console.WriteLine($"{instrument.Name} {value:+#;-#;0}"));
listener.SetMeasurementEventCallback<double>((instrument, value, tags, _) => Console.WriteLine($"{instrument.Name} {value:F3}"));
listener.Start();
Notes and pitfalls
- Host names are not recorded, by design (see the threat model): add
server.addressto a parent span of your own when you want it. - Nothing is recorded unless something listens, so the instrumentation costs nothing when unused.
- Exception messages can contain what the server said (an SFTP error, a refused command). Treat them as server-controlled text before showing them to end users.
- Comparing with
ssh -vvv. TheDebuglog shows the same stages (key exchange, host key, authentication methods), so it is the place to look whensshconnects and SshSharp does not.
See also
- Errors and diagnostics (in-depth: every tag and instrument)
- Troubleshooting
- SshTelemetry in the API reference