Forked from percygrunwald/scout_apm_absinthe_plug.ex
Last active
December 18, 2021 14:52
-
-
Save ryancurtin/60c6e147a346f9155befa82ff1a1b199 to your computer and use it in GitHub Desktop.
Scout Absinthe (GraphQL) Instrumentation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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