Coelho.Telemetry (coelho v0.14.0)

Copy Markdown View Source

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:

EventWhenMetadata
[: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

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.

Summary

Functions

Whether events are being emitted at all.

Runs fun, emitting a span around it.

Functions

enabled?()

@spec enabled?() :: boolean()

Whether events are being emitted at all.

span(event, metadata, fun, measure)

@spec span([atom()], (-> map()), (-> result), (result -> 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.