Packages
new_relic_agent
1.32.0
1.40.3
1.40.2
1.40.1
1.40.0
1.39.0
1.38.0
1.37.0
1.36.0
1.35.0
1.34.0
1.33.0
1.32.0
1.31.1
1.31.0
1.30.0
1.29.0
1.28.0
1.27.8
1.27.7
1.27.6
1.27.5
1.27.4
1.27.3
1.27.2
1.27.1
1.27.0
1.26.0
1.25.3
1.25.2
1.25.1
1.25.0
1.24.5
1.24.4
1.24.3
1.24.2
1.24.1
1.24.0
1.24.0-rc.10
1.24.0-rc.9
1.24.0-rc.8
1.24.0-rc.7
1.23.6
1.23.5
1.23.4
1.23.3
1.23.2
1.23.1
1.23.0
1.23.0-rc.6
1.23.0-rc.5
1.23.0-rc.4
1.23.0-rc.3
1.23.0-rc.2
1.23.0-rc.1
1.22.6
1.22.5
1.22.4
1.22.3
1.22.2
1.22.1
1.22.0
1.21.2
1.21.1
1.21.0
1.21.0-rc.3
1.21.0-rc.1
1.20.0
1.20.0-rc.1
1.19.7
1.19.6
1.19.5
1.19.4
1.19.3
1.19.2
1.19.1
1.19.0
1.18.5
1.18.4
1.18.3
1.18.2
1.18.1
1.18.0
1.18.0-rc.5
1.18.0-rc.4
1.18.0-rc.2
1.18.0-rc.1
1.17.1
1.17.0
1.17.0-rc.4
1.17.0-rc.3
1.17.0-rc.2
1.17.0-rc.1
1.17.0-rc.0
1.16.7
1.16.6
1.16.5
1.16.4
1.16.3
1.16.2
1.16.1
1.16.0
1.16.0-rc.2
1.16.0-rc.1
1.16.0-rc.0
1.15.0
1.15.0-rc.3
1.15.0-rc.2
1.15.0-rc.1
1.14.0
1.13.1
1.13.0
1.12.0
1.11.0
1.10.2
1.10.1
1.10.0
1.9.12
1.9.11
1.9.10
1.9.9
1.9.8
1.9.7
1.9.6
1.9.5
1.9.4
1.9.3
1.9.2
1.9.1
1.9.0
1.8.0
1.7.0
1.6.2
1.6.1
1.6.0
1.5.0
1.4.0
1.3.2
1.3.1
1.3.0
1.2.1
1.2.0
1.1.0
1.0.5
1.0.4
1.0.3
1.0.2
1.0.1
1.0.0
New Relic's Open-Source Elixir Agent
Current section
Files
Jump to
Current section
Files
lib/new_relic/telemetry/absinthe.ex
defmodule NewRelic.Telemetry.Absinthe do
use GenServer
@moduledoc """
Provides `Absinthe` instrumentation via `telemetry`
We automatically gather:
* Transaction name
* Distributed Trace span events
You can opt-out of this instrumentation as a whole with `:absinthe_instrumentation_enabled`
and specifically of query collection with `:query_collection_enabled` via configuration.
See `NewRelic.Config` for details.
"""
@doc false
def start_link(_) do
enabled = NewRelic.Config.feature?(:absinthe_instrumentation)
GenServer.start_link(__MODULE__, [enabled: enabled], name: __MODULE__)
end
@operation_start [:absinthe, :execute, :operation, :start]
@operation_stop [:absinthe, :execute, :operation, :stop]
@resolve_field_start [:absinthe, :resolve, :field, :start]
@resolve_field_stop [:absinthe, :resolve, :field, :stop]
@events [
@operation_start,
@operation_stop,
@resolve_field_start,
@resolve_field_stop
]
@doc false
def init(enabled: false), do: :ignore
def init(enabled: true) do
config = %{
handler_id: {:new_relic, :absinthe},
collect_query?: NewRelic.Config.feature?(:query_collection),
collect_args?: NewRelic.Config.feature?(:function_argument_collection)
}
:telemetry.attach_many(
config.handler_id,
@events,
&__MODULE__.handle_event/4,
config
)
Process.flag(:trap_exit, true)
{:ok, config}
end
@doc false
def terminate(_reason, %{handler_id: handler_id}) do
:telemetry.detach(handler_id)
end
@doc false
def handle_event(@operation_start, meas, meta, config) do
query = read_query(meta.options[:document], collect: config.collect_query?)
NewRelic.Tracer.Direct.start_span(
meta.id,
"Absinthe/Operation",
system_time: meas.system_time,
attributes: [
"absinthe.schema": inspect(meta.options[:schema]),
"absinthe.query": query
]
)
NewRelic.add_attributes(
"absinthe.schema": inspect(meta.options[:schema]),
"absinthe.query": query
)
NewRelic.incr_attributes("absinthe.operation.count": 1)
end
def handle_event(@operation_stop, meas, meta, _config) do
operation = apply(Absinthe.Blueprint, :current_operation, [meta.blueprint])
span_name = operation_span_name(meta.blueprint.execution.result.emitter)
transaction_name = transaction_name(meta.blueprint.schema, operation)
NewRelic.Tracer.Direct.stop_span(
meta.id,
name: span_name,
duration: meas.duration,
attributes: [
"absinthe.operation.type": operation.type,
"absinthe.operation.name": operation.name
]
)
NewRelic.add_attributes(
framework_name: transaction_name,
"absinthe.operation.type": operation.type,
"absinthe.operation.name": operation.name
)
end
def handle_event(@resolve_field_start, meas, %{resolution: resolution} = meta, config) do
path = apply(Absinthe.Resolution, :path, [resolution]) |> Enum.join(".")
resolver_name = resolver_name(resolution.middleware)
args = read_args(resolution.arguments, collect: config.collect_args?)
type =
apply(Absinthe.Type, :name, [
resolution.definition.schema_node.type,
resolution.schema
])
NewRelic.Tracer.Direct.start_span(
meta.id,
{resolver_name, "#{inspect(resolution.schema)}.#{path}"},
system_time: meas.system_time,
attributes: [
"absinthe.field.path": path,
"absinthe.field.type": type,
"absinthe.field.name": resolution.definition.name,
"absinthe.field.parent_type": resolution.definition.parent_type.name,
"absinthe.field.arguments": args
]
)
end
def handle_event(@resolve_field_stop, meas, meta, _config) do
NewRelic.Tracer.Direct.stop_span(
meta.id,
duration: meas.duration
)
end
defp read_query(query, collect: true), do: query
defp read_query(_query, collect: false), do: "[NOT_COLLECTED]"
defp read_args(arguments, collect: true) when map_size(arguments) == 0, do: nil
defp read_args(arguments, collect: true), do: inspect(arguments, limit: 5, printable_limit: 10)
defp read_args(_args, collect: false), do: "[NOT_COLLECTED]"
defp resolver_name(middleware) do
Enum.find_value(middleware, fn
{{Absinthe.Resolution, :call}, resolver_fn} ->
info = Function.info(resolver_fn)
case Keyword.get(info, :type) do
:external -> inspect(resolver_fn)
:local -> "&#{inspect(info[:module])}.anonymous/#{info[:arity]}"
end
{{middleware, :call}, _options} ->
inspect({middleware, :call})
_ ->
nil
end)
end
defp operation_span_name(%{type: type, name: name}) when is_binary(name) do
"Absinthe/Operation/#{to_string(type)}:#{name}"
end
defp operation_span_name(%{type: type}) do
"Absinthe/Operation/#{to_string(type)}"
end
defp transaction_name(schema, operation) do
deepest_path = operation |> collect_deepest_path() |> Enum.join(".")
"Absinthe/#{inspect(schema)}/#{operation.type}/#{deepest_path}"
end
defp collect_deepest_path(%{type: :mutation, selections: [%{name: name} | _]}) do
[name]
end
defp collect_deepest_path(%{type: :subscription, selections: [%{name: name} | _]}) do
[name]
end
defp collect_deepest_path(%{type: :query, selections: [selection]}) do
collect_deepest_path(selection, [])
end
defp collect_deepest_path(%{type: :query}) do
[]
end
defp collect_deepest_path(%{selections: selections, name: name}, acc) do
selections
|> Enum.reject(fn
%{name: "__typename"} -> true
_ -> false
end)
|> case do
[selection] -> collect_deepest_path(selection, acc ++ [name])
_ -> acc ++ [name]
end
end
defp collect_deepest_path(_, acc) do
acc
end
end