# `Coelho.Telemetry`
[🔗](https://github.com/nseaSeb/coelho/blob/main/lib/coelho/telemetry.ex#L1)

The events Coelho emits, and what they carry.

Three spans, each `:start` / `:stop` / `:exception` in the usual shape, so
`:telemetry.attach_many/4` and `Telemetry.Metrics` work on them without
anything special:

| Event | When | Metadata |
| --- | --- | --- |
| `[:coelho, :validate, _]` | `Coelho.Document.validate/2` | `:schema`, and on `:stop` `:result` (`:ok`/`:error`), `:errors`, `:nodes`, `:text_length` |
| `[:coelho, :render, _]` | `Coelho.Render.to_iodata/3` | `:schema`, and on `:stop` `:bytes` |
| `[:coelho, :storage, _]` | `Coelho.Storage.put/3` | `:storage`, `:key`, and on `:stop` `:result` |

`:schema` is `Coelho.Schema.fingerprint/1` rather than the struct — a
schema is a few kilobytes of specs and parse rules, and putting it in the
metadata of every keystroke's validation would hand every handler a copy.
It is settled when the schema is built, so reading it costs nothing.

> #### `:key` is not a metric tag {: .warning}
>
> A storage key is unbounded — one per attachment ever stored — so tagging
> a `Telemetry.Metrics` definition by it creates a metric series per
> attachment, and the metrics backend keeps every one of them for as long
> as it keeps anything. It is in the metadata for a log line or a trace,
> where a single event carries it and is then done with it. Tag by
> `:storage`, which is a module name, or by `:result`.

## What it costs

Nothing at all in a build without `:telemetry`: the metadata and the
measurements are functions, and neither is called. With `:telemetry`
present the metadata is built whether or not a handler is attached, so it
is kept to what is already known — `:nodes` and `:text_length` are counted
by the bounds check validation runs anyway, not by a walk of their own.

## Attaching

    :telemetry.attach_many(
      "coelho",
      [[:coelho, :validate, :stop], [:coelho, :render, :stop]],
      &MyApp.Telemetry.handle/4,
      nil
    )

Validation runs on every keystroke of every editor, so a handler on it is
on a hot path — count and summarise there, do not log.

## Without `:telemetry`

The dependency is optional and Coelho works without it: the spans compile
down to calling the function, and nothing is emitted. Nothing to configure
either way.

# `enabled?`

```elixir
@spec enabled?() :: boolean()
```

Whether events are being emitted at all.

# `span`

```elixir
@spec span([atom()], (-&gt; map()), (-&gt; result), (result -&gt; map())) :: result
when result: term()
```

Runs `fun`, emitting a span around it.

Both `metadata` and `measure` are *functions*, and neither is called in a
build without `:telemetry`. Passing the metadata as a value would build it
on every call whether or not anything was listening, which on a path that
runs per keystroke is the measurement costing more than the work: this is
the difference between an optional dependency and an optional cost.

`measure` is handed the result and answers what to merge into the `:stop`
event's metadata.

---

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