Traversio

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 SSHClientLogEvent values collected during connection setup and later operations
  • a copyable SSHConnectionFailure.diagnosticReport or SSHOperationFailure.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:

  • SSHClientLogRecorder for a bounded in-memory recent-event buffer
  • SSHClientLogHandler.recorder(_:minimumLevel:) or SSHClientLogRecorder.logHandler(minimumLevel:) to wire that buffer into the public connection entry points
  • SSHClientLogHandler.sink(...) to receive structured events
  • SSHClientLogEvent.formattedLine for plain-text export without inventing a formatter first
  • SSHConnectionFailure.diagnosticReport for connection-setup failures
  • SSHOperationFailure.diagnosticReport for post-auth failures such as session, forwarding, or SFTP work
  • SSHPortLatencyError.diagnosticReport for SSH-port latency validation and sampling failures
  • recorder-generated SSH client error support text for public client errors such as connectionScopeEnded, 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-tcpip channel
  • 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:

  • events
  • formattedLines
  • formattedText
  • droppedEventCount
  • diagnosticReport(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().formattedText

If 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:

  1. keep the last 50-100 log events in memory
  2. when an operation fails, call diagnosticReport(for:) on the recorder or on its snapshot
  3. offer a single "Copy Diagnostic Report" action to the user

On this page