Last active
January 5, 2021 00:12
-
-
Save percygrunwald/6dd1d9132855b0372785e75df4a26669 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 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 |
For anyone who is using Absinthe 1.5.x (https://github.com/absinthe-graphql/absinthe/blob/37d72813e1d89b05413e6f58f4563f89a84c94f8/CHANGELOG.md#v150-alpha), :absinthe_lexer
is no longer used. You'll have to make a slight modification to #action_name/2
. Check out my fork, which has been updated to work with Absinthe 1.5.x: https://gist.github.com/ryancurtin/60c6e147a346f9155befa82ff1a1b199
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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 theaction_name
function.