Every wait performed by WaitForIt is instrumented with :telemetry
events, so you can observe waiting in production: how long waits take, how many evaluations they
need, and how often they time out.
Events
WaitForIt emits a standard telemetry span under the [:wait_for_it, :wait] prefix.
[:wait_for_it, :wait, :start]
Emitted when a wait begins.
- Measurements:
%{system_time, monotonic_time} - Metadata:
%{wait_type, wait_context, timeout, interval, signal, env}
[:wait_for_it, :wait, :stop]
Emitted when a wait finishes — whether the waiting condition was met or the wait timed out.
- Measurements:
%{duration, evaluations}durationis in native time units (useSystem.convert_time_unit/3to convert).evaluationsis the number of times the waitable expression was evaluated.
- Metadata:
%{wait_type, wait_context, timeout, interval, signal, env, result, last_value}resultis:matched(the condition was met) or:timeout.
[:wait_for_it, :wait, :exception]
Emitted only if evaluating the waitable expression raises, throws, or exits unexpectedly. A
timeout is not an exception: it is reported as a :stop event with result: :timeout.
- Measurements:
%{duration} - Metadata:
%{wait_type, wait_context, timeout, interval, signal, env, kind, reason, stacktrace}
Wait context
wait_type names the form of waiting that ran, which is not always the form you wrote. A <~
clause in a with_wait desugars to a match_wait, so its events arrive as
wait_type: :match_wait — indistinguishable, on that key alone, from a match_wait you wrote
yourself.
The wait_context metadata tells the two apart:
nilfor a wait written directly (wait,match_wait,case_wait,cond_wait,until).%{construct: :with_wait, clause: index}for a<~clause of awith_wait/with_wait!, whereindexis the zero-based position of the clause withinon(...), counting every clause.
So for this pipeline:
with_wait on(
{:ok, user} <- fetch_user(id),
{:ok, order} <~ latest_order(user),
{:ok, ship} <~ shipment(order)
) do
{user, order, ship}
endthe two waits report %{construct: :with_wait, clause: 1} and %{construct: :with_wait, clause: 2}
respectively — the <- clause is not a wait and emits nothing, but it still occupies index 0, so
the index always points at the clause as written.
This makes it possible to chart a whole with_wait pipeline as a unit, or to find which clause
is the slow one:
def handle_event([:wait_for_it, :wait, :stop], meas, %{wait_context: %{clause: clause}} = meta, _) do
Logger.info("with_wait clause #{clause} #{meta.result} after #{meas.duration}")
end
def handle_event([:wait_for_it, :wait, :stop], _meas, _meta, _config), do: :okAttaching a handler
:telemetry.attach_many(
"wait-for-it-logger",
[
[:wait_for_it, :wait, :stop],
[:wait_for_it, :wait, :exception]
],
&MyApp.WaitForItHandler.handle_event/4,
nil
)defmodule MyApp.WaitForItHandler do
require Logger
def handle_event([:wait_for_it, :wait, :stop], measurements, metadata, _config) do
ms = System.convert_time_unit(measurements.duration, :native, :millisecond)
Logger.info(
"WaitForIt #{metadata.wait_type} #{metadata.result} after #{ms}ms " <>
"(#{measurements.evaluations} evaluations)"
)
end
def handle_event([:wait_for_it, :wait, :exception], _measurements, metadata, _config) do
Logger.error("WaitForIt #{metadata.wait_type} crashed: #{inspect(metadata.reason)}")
end
endUsing Telemetry.Metrics
The events compose with Telemetry.Metrics for
dashboards and reporters. For example:
import Telemetry.Metrics
[
# Distribution of wait durations, tagged by the form of waiting and the outcome.
distribution("wait_for_it.wait.stop.duration",
unit: {:native, :millisecond},
tags: [:wait_type, :result]
),
# How many evaluations waits needed — useful for tuning :interval.
summary("wait_for_it.wait.stop.evaluations", tags: [:wait_type]),
# Count of timeouts vs matches.
counter("wait_for_it.wait.stop.duration", tags: [:wait_type, :result])
]Because metadata includes result, you can alert on a rising rate of result: :timeout for a
given wait_type — an early signal that a dependency is getting slow.
Previous: Recipes
That's the end of the guides. See the WaitForIt module for the complete API reference.