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 workSSHPortLatencyError.diagnosticReportfor SSH-port latency validation and sampling failures- recorder-generated
SSH client errorsupport text for public client errors such asconnectionScopeEnded, auth rejection, or password-change-required cases that are not connection/operation failure wrappers
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. Callback failures include the callback source, original error type, and any opt-in SSHCallbackFailureDiagnosticProviding code or safe summary supplied by the thrown error. SFTP status details include the raw code, typed SSHSFTPStatusCode, optional standard SSH_FX_* name, server message, and language tag.
Latency failure reports identify the latency error case and, when applicable, whether the failure happened during route setup or during the encrypted SSH service-request measurement.
For lifetime-boundary paths such as a background keepalive failure followed by connectionScopeEnded, keep the recorder wired into the connection. The support payload will include the SSH client error section plus recent connection-state log lines, including stateTrigger="background-failure" when Traversio observed one.
Support-export text is redacted by default before it reaches SSHClientLogEvent, formattedLine, OSLog, or the copy-ready failure reports. Traversio currently masks sensitive metadata keys and common inline password, passphrase, private key, secret, token, credential, and authorization fragments, including in server-provided messages and language-tag fields. Treat that as a safety boundary for Traversio diagnostics, not as permission to put application-owned secrets into custom log messages.
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
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().formattedTextIf you already captured a snapshot before presenting your support UI, export from that snapshot:
import Traversio
func diagnosticText(
from error: Error,
snapshot: SSHClientLogRecorderSnapshot
) -> String? {
snapshot.diagnosticReport(for: error)
}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