Live tracing of Mob events for IEx debugging.
Subscribe a process to receive every event that reaches a handler: events
delivered through Mob.Event.dispatch/4, and native input — taps, changes,
gestures, scroll — which arrives at a screen as a legacy tuple and is traced
by Mob.Screen.Server when the screen receives it (see
Mob.Event.NativeInput). Tracing is opt-in and costs one :persistent_term
read per event when no tracers are registered.
Usage
# In IEx connected to the running app:
Mob.Event.Trace.subscribe()
# Now every event lands in your mailbox too, tagged
# {:mob_trace, addr, event, payload}. A native tap on `on_tap: {self(),
# :save}` arrives as {:mob_trace, %Address{widget: :button, id: :save},
# :tap, nil}. Pattern-match it, log it, whatever.
flush() # see what's in the mailbox
# Filter on the way out:
Mob.Event.Trace.subscribe(fn addr -> addr.widget == :list end)
# From a shell on another node, name the pid to deliver to — `:rpc`
# runs the call in a short-lived process that would receive nothing:
:rpc.call(node, Mob.Event.Trace, :subscribe, [self(), nil])
# Stop tracing:
Mob.Event.Trace.unsubscribe() # this process
Mob.Event.Trace.stop() # every tracerTracers are monitored by Mob.Diag.Subscribers, so one that exits stops being
traced to without an unsubscribe/1. One whose node disconnects is paused,
filter kept, until that node reconnects.
What is not traced
Native input the screen never receives: an event for a screen that has
died is dropped by Mob.Listener and recorded as an :undeliverable
receipt instead. Arbitrary handle_info/2 messages — timers, PubSub — are
not events and are not traced. A native tag that is not a valid address id
has no canonical address and is not traced either.
Performance
When no tracers are registered (the default), each event reads an empty list
from :persistent_term and returns; native input is not even given an
address. When tracers are registered, each one is sended a copy of the
envelope, payload included. Tracer filter functions run in the dispatching
process, so keep them cheap.
Summary
Functions
Called by Mob.Event.dispatch/4 to deliver to all tracers. Internal API.
Called by Mob.Screen.Server for a native input message it received.
Internal API.
Stop tracing: unsubscribe every tracer.
Subscribe the current process to receive trace messages.
Subscribe pid — which may be on another node — with an optional filter.
Subscribing a pid again replaces its filter.
Unsubscribe pid (defaults to the current process).
Functions
@spec broadcast(Mob.Event.Address.t(), atom(), term()) :: :ok
Called by Mob.Event.dispatch/4 to deliver to all tracers. Internal API.
Never raises: it runs inside the dispatching screen, and a tracer is a debugging aid that must not change what it observes.
Called by Mob.Screen.Server for a native input message it received.
Internal API.
The address is built only when someone is listening, so with no tracers
this is the same empty-list read as broadcast/3. Never raises.
@spec stop() :: :ok
Stop tracing: unsubscribe every tracer.
@spec subscribe((Mob.Event.Address.t() -> boolean()) | nil) :: :ok
Subscribe the current process to receive trace messages.
If filter is provided, only events for which filter.(addr) returns
truthy are delivered to this subscriber.
Messages arrive shaped {:mob_trace, addr, event, payload}.
@spec subscribe(pid(), (Mob.Event.Address.t() -> boolean()) | nil) :: :ok
Subscribe pid — which may be on another node — with an optional filter.
Subscribing a pid again replaces its filter.
@spec unsubscribe(pid()) :: :ok
Unsubscribe pid (defaults to the current process).