# `Latu.Telemetry`
[🔗](https://github.com/zero-one-group/latu/blob/v0.4.0/lib/latu/telemetry.ex#L1)

The `:telemetry` events Latu emits, and the only place their names are written down.

`:telemetry.execute/3` is a plain function call, so this fits an architecture that holds no
processes: a handler runs in whichever process is talking to Spark, which is the caller's.
A slow handler slows the query, exactly as a `:progress` function does.

| Event | Measurements | When |
|---|---|---|
| `[:latu, :rpc, :start]` | `system_time` | every unary RPC, before the call |
| `[:latu, :rpc, :stop]` | `duration` | the same call, after it |
| `[:latu, :rpc, :exception]` | `duration` | the call raised rather than returning |
| `[:latu, :execute, :start]` | `system_time` | a result stream is first enumerated |
| `[:latu, :execute, :stop]` | `duration` | that stream ended, however it ended |
| `[:latu, :retry, :attempt]` | `backoff`, `attempt` | a transient error the transport retries |
| `[:latu, :reattach, :attempt]` | `backoff`, `attempt` | an ordinary mid-stream reattach |
| `[:latu, :result, :batch]` | `rows`, `bytes` | each Arrow batch |
| `[:latu, :result, :progress]` | `percent` | each progress message from the server |

Metadata is `rpc`, `outcome` and `error_class` on the RPC events, and `session_id` plus
`operation_id` on everything that belongs to an execution. The RPC events carry no
`operation_id`; a `[:latu, :retry, :attempt]` for a unary call carries `rpc` in its place;
`[:latu, :execute, :stop]` carries an `outcome` of `:ok`, `:error` or `:abandoned`.
Durations are in native time units, as `:telemetry.span/3`'s are.

**`[:latu, :rpc, :*]` covers every gRPC call, including `ExecutePlan` — but for that one and
`ReattachExecute` it measures *opening* the stream, not draining it**, because a Latu result
is a lazy stream and there is no function whose return is the end of the query. So a dashboard
built on the rpc duration would show a 30-second query as two milliseconds. That is what
`[:latu, :execute, :*]` is for: it opens when the stream is first enumerated and closes when
it ends, whether that is a finish, a failure, or a consumer that stopped reading — the
`:abandoned` outcome, worth alerting on, since an abandoned reattachable execution goes on
running on the server (see `Latu.interrupt/2`).

Names follow [SparkEx](https://github.com/lukaszsamson/spark_ex)'s, with `:latu` for its
`:spark_ex`, so a metrics reporter written for one needs nothing but a prefix for the other.

## What is never in metadata

The session's `:token` and its `:headers`. Every event is built from named fields —
`session_id`, `operation_id` — and never from the `%Latu.Session{}` itself, so a handler that
logs its metadata cannot leak a credential. `test/integration/telemetry_test.exs` asserts it
against a session that has a token.

Nor is the plan, the schema or any row. A measurement is a number and metadata is an id.

## Attaching

    :telemetry.attach_many(
      "latu-log",
      [[:latu, :rpc, :stop], [:latu, :retry, :attempt]],
      fn event, measurements, metadata, _config ->
        Logger.info("#{inspect(event)} #{inspect(measurements)} #{inspect(metadata)}")
      end,
      nil
    )

---

*Consult [api-reference.md](api-reference.md) for complete listing*
