# Telemetry

`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`

```elixir
def deps do
  [
    {:sftpd, "~> 0.1.1"}
  ]
end
```

## Event 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:

- `:read` adds `:bytes`
- `:write` adds `: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`
- `:reason` when 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:

- `:read` may emit `:eof`
- `:is_dir` emits `:directory` or `:not_directory`
- exceptions inside `Sftpd.Telemetry.span/4` emit `result: :exception` plus
  `:kind` and `: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_ref` on success

`[:sftpd, :server, :stop]`

- `:server_ref`
- `:result`

## Examples

Attach a single handler:

```elixir
: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:

```elixir
: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:

```elixir
: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_sftpd` implementation 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 `Sftpd` and `Sftpd.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.
