Skip to content

Add built-in tracing and metrics (ActivitySource/Meter) - #1857

Open
YoussefElshemi wants to merge 4 commits into
sshnet:developfrom
YoussefElshemi:native-telemetry
Open

YoussefElshemi wants to merge 4 commits into
sshnet:developfrom
YoussefElshemi:native-telemetry

Conversation

@YoussefElshemi

Copy link
Copy Markdown

Closes #1854

Adds built-in tracing and metrics using System.Diagnostics ActivitySource and Meter, both named Renci.SshNet. OpenTelemetry (or any other listener) picks them up with:

builder.Services.AddOpenTelemetry()
    .WithTracing(t => t.AddSource("Renci.SshNet"))
    .WithMetrics(m => m.AddMeter("Renci.SshNet"));

When nothing is listening, SshNetTelemetry.IsEnabled is checked first, so no spans, tags or timestamps are created.

What is instrumented

Each operation produces a Client span named {protocol} {operation} (e.g. sftp UploadFile) and a ssh.client.operation.duration histogram measurement (seconds).

  • Connect for SshClient, SftpClient and NetConfClient (ssh / sftp / netconf), sync and async
  • SFTP: UploadFile, DownloadFile, ListDirectory, ChangeDirectory, ChangePermissions, CreateDirectory, DeleteDirectory, DeleteFile, Delete, RenameFile, SymbolicLink, Get, Exists, GetStatus, GetAttributes, SetAttributes — sync, async and Begin/End where they exist
  • SshCommand execution (ssh Execute)

Not instrumented:

  • SCP (ScpClient is unchanged, as discussed in the issue; its Connect still goes through BaseClient and shows up as ssh Connect)
  • Stream-based APIs (Open*, ReadAll*, WriteAll*, AppendAll*), since a span would only cover opening the handle, not the I/O
  • Command text, as it may contain secrets

Attributes

Attribute Source Span Metric
network.protocol.name semconv ✓ ✓
server.address, server.port semconv ✓ ✓
error.type semconv ✓ ✓
file.path semconv ✓ (high cardinality)
ssh.operation.name custom ✓ ✓
sftp.transfer.bytes custom ✓
ssh.command.exit_code custom ✓

There is no SSH/SFTP semantic convention yet, so the custom names should be treated as experimental. Failed operations set span status to Error and record error.type (exception type name). Exists returning false is not an error.

Notes for review

  • SshCommand.ExecuteAsync isn't an async method, so it restores Activity.Current after starting the span; otherwise the caller would continue inside the command's span.
  • The command span is ended before the TaskCompletionSource is completed, so it has ended by the time awaiters resume. It is also ended if the command is disposed mid-run.
  • Delete and ChangePermissions call Get internally, so their spans have a child Get span.
  • New dependency: System.Diagnostics.DiagnosticSource 8.0.1 on net462/netstandard2.0 only (newer TFMs have it in-box).
  • Public Connect/ConnectAsync in BaseClient now wrap private ConnectCore/ConnectCoreAsync; no public API changes.

Testing

  • Release build clean on all targets
  • Unit tests pass (net10.0)
  • New TelemetryTests integration tests (spans, parenting, attributes, durations, errors, command timeout/exit code) pass on net48, net8.0, net9.0 and net10.0
  • Existing SFTP / SshClient / connectivity integration tests pass (net10.0)
  • Also checked the output manually against the .NET Aspire dashboard

This change was developed with AI assistance. I have reviewed and tested all of it.

Emits spans and an ssh.client.operation.duration histogram from an
ActivitySource/Meter named "Renci.SshNet" for Connect, SFTP upload,
download and list, and SshCommand execution. Nothing is allocated or
read when there are no listeners.
The command span is now ended before the task completes, so it has
ended by the time awaiters resume, and a command disposed mid-run
also ends its span. Adds integration tests for SFTP, connect and
command telemetry.
Adds spans and durations for ChangeDirectory, ChangePermissions,
CreateDirectory, DeleteDirectory, DeleteFile, Delete, RenameFile,
SymbolicLink, Get, Exists, GetStatus, GetAttributes and SetAttributes,
sync and async. Exists reporting a missing path is not an error.

Reverts the SCP protocol name, as SCP is a legacy protocol.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Proposal: built-in tracing and metrics via ActivitySource/Meter

1 participant