Sftpd emits :telemetry events for server lifecycle and SFTP operations.
The package depends on :telemetry directly, so applications can attach
handlers without adding another dependency.
Installing Sftpd
def deps do
[
{:sftpd, "~> 0.1.1"}
]
endEvent Families
Sftpd emits three event families:
[:sftpd, :server, :start][:sftpd, :server, :stop][:sftpd, :sftp, operation]
operation is one of:
:open:close:read:write:list_dir:read_file_info:read_link_info:read_link:rename:delete:make_dir:del_dir:position:is_dir:get_cwd:make_symlink:write_file_info
Measurements
Every event includes:
%{duration: native_time}
Additional measurements:
:readadds:bytes:writeadds:bytes
duration is measured with System.monotonic_time/0 native units. Convert it
with System.convert_time_unit/3 before exporting or logging human-readable
durations.
Metadata
Common SFTP Metadata
All [:sftpd, :sftp, operation] events include:
:backend:backend_kind:result:reasonwhen an error reason is available
backend_kind is one of:
:module:genserver
For {:genserver, server} backends, :backend is inspect(server) rather
than a module name.
Result Values
Most operations use:
:ok:error
Special cases:
:readmay emit:eof:is_diremits:directoryor:not_directory- exceptions inside
Sftpd.Telemetry.span/4emitresult: :exceptionplus:kindand:reason, then are reraised
Operation-Specific Metadata
[:sftpd, :sftp, :open]
:path:requested_modes:mode:open_timeout
requested_modes contains the raw mode list passed into the SFTP file handler.
mode is the resolved value :read or :write after Sftpd normalizes it.
[:sftpd, :sftp, :close]
:io_device:close_timeout:close_shutdown_grace
[:sftpd, :sftp, :read]
:io_device:bytes_requested
The :read event does not include :path. If you need path-level context for
reads, correlate the :io_device back to the earlier [:sftpd, :sftp, :open]
event for that handle.
[:sftpd, :sftp, :write]
:io_device
[:sftpd, :sftp, :position]
:io_device:offset
[:sftpd, :sftp, :rename]
:src_path:dst_path
Path-based operations such as :list_dir, :read_file_info, :read_link_info,
:delete, :make_dir, and :del_dir add:
:path
Server Metadata
[:sftpd, :server, :start]
:port:max_sessions:backend:backend_kind:result:server_refon success
[:sftpd, :server, :stop]
:server_ref:result
Examples
Attach a single handler:
:telemetry.attach(
"sftpd-read-logger",
[:sftpd, :sftp, :read],
fn _event, measurements, metadata, _config ->
Logger.info(
"sftp read io_device=#{inspect(metadata.io_device)} bytes=#{measurements.bytes} result=#{metadata.result}"
)
end,
nil
)Attach one handler to multiple events:
:telemetry.attach_many(
"sftpd-audit",
[
[:sftpd, :server, :start],
[:sftpd, :server, :stop],
[:sftpd, :sftp, :write],
[:sftpd, :sftp, :delete]
],
fn event, measurements, metadata, _config ->
Logger.info("""
event=#{inspect(event)}
duration_native=#{measurements.duration}
result=#{metadata.result}
backend=#{inspect(metadata.backend)}
""")
end,
nil
)Convert durations before exporting metrics:
:telemetry.attach(
"sftpd-read-metrics",
[:sftpd, :sftp, :read],
fn _event, measurements, metadata, _config ->
duration_us =
System.convert_time_unit(measurements.duration, :native, :microsecond)
Logger.info(
"read io_device=#{inspect(metadata.io_device)} bytes=#{measurements.bytes} duration_us=#{duration_us}"
)
end,
nil
)Caveats
- OTP's built-in
:ssh_sftpdimplementation always reports close success to the client, even if final close-time flushing fails. Telemetry still records those server-side close failures, but the client may not see them. - Telemetry is emitted from
SftpdandSftpd.FileHandler, so event timings reflect the library's wrapper and backend call boundaries rather than network round-trip timings observed by the SFTP client. - Handlers run in the emitting process, so avoid slow handler work.