Diagnostics

Logging

Attach an SSHLogHandler to receive structured events as they happen. The handler is invoked off the worker queue.

let recorder = SSHLogRecorder(capacity: 200)
configuration.logHandler = { event in recorder.record(event) }

SSHLogRecorder stores events as a fixed-capacity ring of redacted entries: metadata keys containing password, passphrase, secret, token, or privatekey are scrubbed before storage.

Diagnostic report

let report = connection.diagnosticReport(
    phase: "post-connect",
    metadata: ["build": "1.2.3"],
    recentEvents: recorder.events
)
print(report.host, report.port, report.authentication, report.hostKeyPolicy)

Reports redact both the supplied metadata and the embedded recent events using the same key list.

Algorithm profile

// Built-ins:
configuration.algorithmProfile = .modern        // libssh defaults + RSA >= 3072
configuration.algorithmProfile = .legacyRSA     // opts back into ssh-rsa, RSA >= 1024

// Custom (lists are passed straight to libssh; OpenSSH-compatible +/-/^ modifiers work):
configuration.algorithmProfile = SSHAlgorithmProfile(
    keyExchangeAlgorithms: "curve25519-sha256",
    macsClientToServer: "+hmac-sha2-512-etm@openssh.com",
    minimumRSAKeySize: 2048
)

// Inspect what libssh actually negotiates given the profile:
let snapshot = try configuration.algorithmProfile.inspectEffectiveAlgorithms()
print(snapshot.keyExchangeAlgorithms)

Unsupported names raise SSHKitError at inspection or connection setup — SSHKit does not silently strip them.

Latency probe

let report = try await SSHPortLatencyProbe.measure(
    configuration: configuration,
    serviceCommand: "true"
)
print("connect=\(report.connectDuration)s service=\(report.sshServiceDuration)s total=\(report.totalDuration)s")
print("route:", report.route)

The probe connects, runs serviceCommand, and disconnects. The report’s route reflects which transport actually ran — .direct, .socks5, .httpConnect, or .proxyJump.