Skip to content

Instantly share code, notes, and snippets.

@ryancurtin
Forked from percygrunwald/scout_apm_absinthe_plug.ex
Last active December 18, 2021 14:52
Show Gist options
  • Save ryancurtin/60c6e147a346f9155befa82ff1a1b199 to your computer and use it in GitHub Desktop.
Save ryancurtin/60c6e147a346f9155befa82ff1a1b199 to your computer and use it in GitHub Desktop.
Scout Absinthe (GraphQL) Instrumentation
defmodule ReaperWeb.Plugs.ScoutApmAbsinthe do
@moduledoc """
This plug allows us to add context to our GraphQL requests in Scout.
We are intuiting the operation name from the query itself and attaching it
to a Scout layer
"""
alias ScoutApm.Internal.Layer
alias Reaper.MetaLogger, as: 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
with {:ok, tokens, _, _, _, _} <- Absinthe.Lexer.do_tokenize(query_bin),
{:ok, query_doc} <- :absinthe_parser.parse(tokens) do
get_first_field_from_query_doc(query_doc)
else
{:error, rest, {_line, _char}} ->
log_raw_parse_error(rest)
{: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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment