Skip to content

Instantly share code, notes, and snippets.

@frekw
Last active August 28, 2017 14:46
Show Gist options
  • Save frekw/cb3ed4a33a63b34559ea347335afa74a to your computer and use it in GitHub Desktop.
Save frekw/cb3ed4a33a63b34559ea347335afa74a to your computer and use it in GitHub Desktop.
defmodule Instrumenter do
alias Absinthe.Resolution
@behaviour Absinthe.Middleware
@callback measurement(metric :: atom, result :: any, time :: non_neg_integer) :: none
@callback field(field :: string) :: none
defmacro __using__(opts) do
backend = Keyword.get(opts, :adapter, Instrumenter.Dummy)
quote do
def instrument([], _field, _obj), do: []
def instrument(mw, %{__reference__: %{module: Absinthe.Type.BuiltIns.Introspection}}, _obj), do: mw
def instrument(mw, %{identifier: id} = field, _obj) do
# TODO: Inject potential args.
[{{Instrumenter}, unquote(backend)} | mw]
end
def install(schema) do
instrumented? = fn %{middleware: mw} ->
mw
|> Enum.any?(fn
{{Instrumenter}, _} -> true
_ -> false
end)
end
for %{fields: fields} = object <- Absinthe.Schema.types(schema),
{k, %Absinthe.Type.Field{name: name, identifier: id} = field} <- fields,
instrumented?.(field) do
unquote(backend).field(name)
end
end
end
end
def call(%Resolution{state: :unresolved} = res, backend, _config) do
now = :os.timestamp()
%{res | middleware: res.middleware ++ [{{Instrumenter, :after_resolve}, start_at: now, backend: backend, field: res.definition.name, parent: res.parent_type.name}]}
end
def after_resolve(%Resolution{state: :resolved} = res, [start_at: start_at, backend: backend, field: field, parent: _]) do
diff = :timer.now_diff(:os.timestamp(), start_at)
result = case res.errors do
[] -> {:ok, res.value}
errors -> {:error, errors}
end
backend.measurement(field, result, diff)
res
end
end
defmodule Instrumenter.Dummy do
@behaviour Instrumenter
require Logger
def measurement(metric, {status, _result}, time) do
case status do
:error -> Logger.warn("#{metric} failed (took: #{inspect time})")
:ok -> Logger.info("#{metric} took: #{inspect time}")
end
end
def field(name) do
Logger.info("install field #{name}")
end
end
defmodule App.Instrumentation do
use Instrumenter
end
# during application boot
App.Instrumentation.install(App.Schema)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment