Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Scout Absinthe (GraphQL) Instrumentation
defmodule ScoutApm.Absinthe.Plug do
alias ScoutApm.Internal.Layer
require Logger
@error_prefix "GraphQL query document could not be parsed"
@endpoint_prefix "GraphQL"
@default_action_name "unknown"
def init(default), do: default
def call(conn, _default) do
ScoutApm.TrackedRequest.start_layer("Controller", endpoint_name(conn))
conn
|> Plug.Conn.register_before_send(&before_send/1)
end
defp before_send(conn) do
endpoint_name = endpoint_name(conn)
uri = "#{conn.request_path}/#{action_name(conn)}"
ScoutApm.TrackedRequest.stop_layer(fn layer ->
layer
|> Layer.update_name(endpoint_name)
|> Layer.update_uri(uri)
end)
conn
end
defp endpoint_name(conn), do: "#{@endpoint_prefix}##{action_name(conn)}"
defp action_name(%{params: %{"operationName" => operation_name}} = _conn) when is_binary(operation_name) do
operation_name
end
defp action_name(%{params: %{"query" => query_bin}} = _conn) when is_binary(query_bin) do
query_chars = :erlang.binary_to_list(query_bin)
with \
{:ok, tokens, _line_count} <- :absinthe_lexer.string(query_chars),
{:ok, query_doc} <- :absinthe_parser.parse(tokens)
do
get_first_field_from_query_doc(query_doc)
else
{:error, raw_error} ->
log_raw_parse_error(raw_error)
@default_action_name
end
end
defp action_name(_conn), do: @default_action_name
defp get_first_field_from_query_doc(query_doc) do
query_doc
|> Map.get(:definitions, [%{}])
|> List.first()
|> Map.get(:selection_set, %{})
|> Map.get(:selections, [%{}])
|> List.first()
|> Map.get(:name, @default_action_name)
end
defp log_raw_parse_error({_line, :absinthe_parser, msgs}) do
message = msgs |> Enum.map(&to_string/1) |> Enum.join("")
Logger.error("#{@error_prefix}: #{message}")
end
defp log_raw_parse_error({_line, :absinthe_lexer, {problem, field}}) do
message = "#{problem}: #{field}"
Logger.error("#{@error_prefix}: #{message}")
end
defp log_raw_parse_error(_unknown), do: Logger.error("#{@error_prefix}: unknown error")
end
@cpursley

This comment has been minimized.

Copy link

commented Dec 11, 2018

For even more detail, I customized the context by adding the GraphQL query that was run for the endpoint/action by adding ScoutApm.Context.add("GraphQL Query", String.replace(query_bin, ~r/ +|\n/, " ")) to line 38 in the action_name function.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.