Diagnostics
Capture copyable SSH failure details and recent log events in an app-friendly way.
Diagnostics are part of normal application integration. The most useful inputs for support and debugging are:
- structured
SSHClientLogEventvalues collected during connection setup and later operations - a copyable
SSHConnectionFailure.diagnosticReportorSSHOperationFailure.diagnosticReport
Keep this pattern close to the code that opens a connection or starts an operation, then surface the exported text through your own UI or support flow.
Available Diagnostics
Traversio already exposes the stable pieces needed for a support export:
SSHClientLogRecorderfor a bounded in-memory recent-event bufferSSHClientLogHandler.recorder(_:minimumLevel:)orSSHClientLogRecorder.logHandler(minimumLevel:)to wire that buffer into the public connection entry pointsSSHClientLogHandler.sink(...)to receive structured eventsSSHClientLogEvent.formattedLinefor plain-text export without inventing a formatter firstSSHConnectionFailure.diagnosticReportfor connection-setup failuresSSHOperationFailure.diagnosticReportfor post-auth failures such as session, forwarding, or SFTP work
Those reports include the endpoint, stage or scope, negotiated algorithms when known, remote disconnect/debug details, channel IDs when relevant, and SFTP status details when the server returned them.
ProxyJump failures are easier to localize because the connection log stream records:
- when the overall ProxyJump setup starts
- when each hop starts
- when Traversio opens the nested
direct-tcpipchannel - when the final target connection begins
Built-In Recorder
Use SSHClientLogRecorder when the app needs a bounded recent-event buffer for support export:
import Traversio
let diagnostics = SSHClientLogRecorder(maximumEventCount: 80)
let logHandler = diagnostics.logHandler(minimumLevel: .info)The recorder keeps the newest events, drops older ones once the buffer is full, and exposes that truncation through SSHClientLogRecorderSnapshot.droppedEventCount.
If you prefer the factory on the handler type, this is equivalent:
let diagnostics = SSHClientLogRecorder(maximumEventCount: 80)
let logHandler = SSHClientLogHandler.recorder(
diagnostics,
minimumLevel: .info
)snapshot() gives you:
eventsformattedLinesformattedTextdroppedEventCountdiagnosticReport(for:)
Custom Pipelines
If you already have your own logging stack, keep using SSHClientLogHandler.sink(...) and forward the same event into both places:
import Traversio
let diagnostics = SSHClientLogRecorder(maximumEventCount: 80)
let logHandler = SSHClientLogHandler.sink(minimumLevel: .info) { event in
diagnostics.record(event)
myLogger.write(event.formattedLine)
}That keeps Traversio's bounded support-export buffer and your app's existing log pipeline in sync.
Exporting A Support Payload
This is the smallest useful flow for a copy button, a bug-report sheet, or a support attachment:
import Traversio
@available(macOS 26.0, iOS 26.0, *)
func runCheckedCommand(configuration: SSHClientConfiguration) async {
let diagnostics = SSHClientLogRecorder(maximumEventCount: 80)
let logHandler = diagnostics.logHandler(minimumLevel: .info)
do {
try await SSHClient.withConnection(
configuration: configuration,
logHandler: logHandler
) { connection in
_ = try await connection.execute("uptime")
}
} catch {
if let report = diagnostics.diagnosticReport(for: error) {
print(report)
}
}
}If you only want the log lines:
let logText = diagnostics.snapshot().formattedTextYou can also export only the wrapped failure when recent events are optional:
import Traversio
func diagnosticText(from error: Error) -> String? {
guard let clientError = error as? SSHClientError else {
return nil
}
switch clientError {
case let .connectionFailed(failure):
return failure.diagnosticReport
case let .operationFailed(failure):
return failure.diagnosticReport
default:
return nil
}
}Placement In Your App
The most practical place to wire this up is the boundary where your app creates SSHClientConfiguration and starts the connection or operation. That captures:
- setup failures such as TCP, banner, key exchange, host-trust, auth, timeout, or ProxyJump issues
- later failures such as channel startup, forwarding, or SFTP request errors
Surface the exported text through your own UI. A typical pattern is:
- keep the last 50-100 log events in memory
- when an operation fails, call
diagnosticReport(for:)on the recorder or on its snapshot - offer a single "Copy Diagnostic Report" action to the user