Proposed new API for collecting toolbar data
juanperi opened this issue · comments
The idea is to back the collection of all the data in independent data structures.
The following is an example of usage in a plug, that keeps track of the original conn, the final conn (useless, i know :) ) and the time that the request takes
defmodule ExDebugToolbar.Plug.Request do
import Plug.Conn
alias ExDebugToolbar.Collector.{Timed, Static}
alias ExDebugToolbar.Toolbar
def init(default), do: default
def call(%Plug.Conn{} = conn, _default) do
# first do something that puts the request id in the current process (and generates it if not there)
# Static "collector". This one expects to be called only once per name, and stores the "data" in the registry
Toolbar.attach(%Static{name: :original_conn, data: conn})
# Timed "collector". The first time it is called with a given name, it saves the current time. The second time
# it is called, it stores the finish time, and calculates the diff. That is done in the finish/1 callback
Toolbar.attach(%Timed{name: :request_time})
Plug.Conn.register_before_send(conn, &finish/1)
end
defp finish(conn) do
Toolbar.attach(%Static{name: :final_conn, data: conn})
Toolbar.attach(%Timed{name: :request_time})
conn
end
end
Definition of collector. The protocol and the behavior allow us to have completely independen handling from one collector to another
defprotocol ExDebugToolbar.Collector do
@doc "global name of the collector. There is only one of this per request_id"
def name(data)
@doc "returns a module that implements the ExDebugToolbar.Updater behavior"
def updater(data)
end
defmodule ExDebugToolbar.Updater do
@callback update(previous_state, data) :: new_state
end
this is how the entrypoint looks like
defmodule ExDebugToolbar.Toolbar do
def attach(collector), do: Process.get(:request_id) |> attach(collector)
def attach(request_id, collector) do
current_request = case lookup(request_id) do
nil -> create(request_id)
request -> request
end
updater = ExDebugToolbar.Collector.updater(collector)
name = ExDebugToolbar.Collector.name(collector)
current_data = Map.fetch(current_request, name, nil)
next_data = updater.update(current_data, collector)
Registry.update(request_id, Map.put(current_request, name, next_data))
end
end
and 3 examples of implementation of low level collectors
defmodule ExDebugToolbar.Collector.Timed do
defstruct [:name, :started_at, :ended_at, :duration]
@behaviour ExDebugToolbar.Updater
def update(nil, %Timed{name: name} = data) when not is_nil(name) do
%{timed | started_at: DateTime.utc_now()}
end
def update(%Timed{} = previous_state, _data) do
ended_at = DateTime.utc_now()
duration = DateTime.to_unix(ended_at, :microsecond) - DateTime.to_unix(previous_state.started_at, :microsecond)
%{previous_state | ended_at: ended_at, duration: duration}
end
end
defimpl ExDebugToolbar.Collector, for: ExDebugToolbar.Collector.Timed do
def name(collector) do
collector.name
end
def updater(_) do
ExDebugToolbar.Collector.Timed
end
end
defmodule ExDebugToolbar.Collector.Log do
defstruct [:name, :entries, :entry]
@behaviour ExDebugToolbar.Updater
def update(nil, %Log{name: name} = data) when not is_nil(name) do
data
end
def update(%Log{entries: entries} = previous_state, %Log{entry: entry} = data) do
previous_state | entries: (entries ++ [entry])
end
end
defimpl ExDebugToolbar.Collector, for: ExDebugToolbar.Collector.Log do
def name(log) do
log.name
end
def updater(_) do
ExDebugToolbar.Collector.Log
end
end
defmodule ExDebugToolbar.Collector.Static do
defstruct [:name, :data]
@behaviour ExDebugToolbar.Updater
def update(nil, %Static{name: name} = data) when not is_nil(name) do
data
end
end
defimpl ExDebugToolbar.Collector, for: ExDebugToolbar.Collector.Static do
def name(data) do
data.name
end
def updater(_) do
ExDebugToolbar.Collector.Static
end
end
I'm not sure if this compiles, but i think it conveys the idea I had about how to get the data into the registry.
Naming can be improved of course
now that I think of it, name should probably be moved to the outside of the collector. eg:
def attach(request_id, name, collector) do