PhoenixDevToolbar.Plugin behaviour (PhoenixDevToolbar v0.0.2)

Copy Markdown View Source

Experimental

This API may change in any 0.0.x release. Changes are listed in the CHANGELOG.

The behaviour every toolbar panel implements, the built-ins included (ADR 05).

A plugin collects events from :telemetry inside the host's processes and, in the panel, folds them into summary badges (summary_init/1, summary_add/2, badges/1) and shows them in its own tab (panel/0, see PhoenixDevToolbar.Panel). Register plugins in config/dev.exs:

config :phoenix_dev_toolbar,
  plugins: [:defaults, {MyApp.Toolbar.Jobs, queues: [:default]}]

:defaults expands to the built-ins (Ecto, Request, LiveView, Process, System). Listing a built-in with options overrides it; {:exclude, Module} removes one. An invalid entry is logged and skipped: the host application always boots.

Example

defmodule MyApp.Toolbar.Jobs do
  use PhoenixDevToolbar.Plugin, id: :jobs, name: "Jobs"

  alias PhoenixDevToolbar.{Context, Event}

  @impl PhoenixDevToolbar.Plugin
  def init(opts), do: {:ok, %{queues: Keyword.get(opts, :queues, :all)}}

  @impl PhoenixDevToolbar.Plugin
  def telemetry_events(_config), do: [[:oban, :engine, :insert_job, :stop]]

  @impl PhoenixDevToolbar.Plugin
  def collect(_event, {measurements, metadata}, %Context{}) do
    data = %{worker: metadata.job.worker}
    {:record, Event.new(:jobs, :inserted, data, duration: measurements.duration)}
  end
end

Where the code runs

init/1 runs once, when the toolbar attaches its handlers. collect/3 runs inside the host process that emitted the event (a request, a LiveView, a Repo caller), and only for processes the toolbar tracks. So it must be cheap and pure:

  • never call another process synchronously, never do I/O
  • bound everything it keeps: inspect/2 limits, never whole structs
  • never create atoms from runtime data

A collect/3 that raises, throws or exits is disabled (quarantined) and the error is logged once; the other plugins keep working.

The behaviour is unstable before 1.0 (see the note at the top).

Summary

Types

A badge of the summary bar, shown even when the panel is collapsed. id becomes the DOM id pdt-badge-<id>, unique in the whole panel: start it with your plugin's id. title is the tooltip.

What init/1 returns: handed to every other callback.

Callbacks

The summary bar badges for the summary, in display order.

Turns one telemetry event into a recorded event, or skips it. Runs inside the emitting host process: cheap, pure, never raises. context.config is the plugin's config.

A unique id, stored in every event the plugin records (Event.plugin).

Validates the options given in the config and returns the plugin's config. Runs once, in the toolbar's own process. Defaults to {:ok, opts}.

The tab label in the panel.

The plugin's tab: a module that does use PhoenixDevToolbar.Panel. A plugin without it collects events and shows badges only.

Folds one of the plugin's events into its summary. Pure and cheap.

The initial state of the plugin's summary for a page, folded by summary_add/2 in the panel process, event by event: it survives the oldest events being trimmed from the buffer.

The :telemetry events to attach to.

Functions

Sets the behaviour and defines id/0, name/0 and a default init/1.

Types

badge()

@type badge() :: %{
  :id => String.t(),
  :text => String.t(),
  :level => :ok | :info | :warn | :error,
  optional(:title) => String.t()
}

A badge of the summary bar, shown even when the panel is collapsed. id becomes the DOM id pdt-badge-<id>, unique in the whole panel: start it with your plugin's id. title is the tooltip.

config()

@type config() :: term()

What init/1 returns: handed to every other callback.

Callbacks

badges(summary)

(optional)
@callback badges(summary :: term()) :: [badge()]

The summary bar badges for the summary, in display order.

collect(event_name, tuple, t)

@callback collect(
  :telemetry.event_name(),
  {measurements :: map(), metadata :: map()},
  PhoenixDevToolbar.Context.t()
) :: {:record, PhoenixDevToolbar.Event.t()} | :skip

Turns one telemetry event into a recorded event, or skips it. Runs inside the emitting host process: cheap, pure, never raises. context.config is the plugin's config.

id()

@callback id() :: atom()

A unique id, stored in every event the plugin records (Event.plugin).

init(opts)

(optional)
@callback init(opts :: keyword()) :: {:ok, config()} | {:error, String.t()}

Validates the options given in the config and returns the plugin's config. Runs once, in the toolbar's own process. Defaults to {:ok, opts}.

name()

@callback name() :: String.t()

The tab label in the panel.

panel()

(optional)
@callback panel() :: module()

The plugin's tab: a module that does use PhoenixDevToolbar.Panel. A plugin without it collects events and shows badges only.

summary_add(summary, t)

(optional)
@callback summary_add(summary :: term(), PhoenixDevToolbar.Event.t()) :: term()

Folds one of the plugin's events into its summary. Pure and cheap.

summary_init(config)

(optional)
@callback summary_init(config()) :: term()

The initial state of the plugin's summary for a page, folded by summary_add/2 in the panel process, event by event: it survives the oldest events being trimmed from the buffer.

telemetry_events(config)

@callback telemetry_events(config()) :: [:telemetry.event_name()]

The :telemetry events to attach to.

Functions

__using__(opts)

(macro)

Sets the behaviour and defines id/0, name/0 and a default init/1.

Options: :id (required, an atom) and :name (the tab label, defaults to the id).