Mob.Event.Trace (mob v0.9.7)

Copy Markdown View Source

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 tracer

Tracers 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

broadcast(addr, event, payload)

@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.

broadcast_input(message, screen)

@spec broadcast_input(tuple(), module()) :: :ok

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.

stop()

@spec stop() :: :ok

Stop tracing: unsubscribe every tracer.

subscribe(filter \\ nil)

@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}.

subscribe(pid, filter)

@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.

unsubscribe(pid \\ self())

@spec unsubscribe(pid()) :: :ok

Unsubscribe pid (defaults to the current process).