Packages
new_relic_agent
1.35.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/tracer/macro.ex
defmodule NewRelic.Tracer.Macro do
require Logger
# Function Tracer Macros
# 1) __on_definition__: When a function is defined & it's been marked to @trace, we
# store information about the function in module attributes
# 2) __before_compile__: We take the list of functions that are marked to @trace and:
# a) Make them overridable
# b) Re-define them with our tracing code wrapped around the original logic
@moduledoc false
alias NewRelic.Tracer
defguardp is_variable(name, context)
when is_atom(name) and (is_atom(context) or is_nil(context))
# Take no action on the definition of a function head
def __on_definition__(_env, _access, _name, _args, _guards, nil), do: nil
def __on_definition__(_env, _access, _name, _args, _guards, []), do: nil
def __on_definition__(%{module: module}, access, name, args, guards, do: body) do
if trace_info =
trace_function?(module, name, length(args))
|> trace_deprecated?(module, name) do
Module.put_attribute(module, :nr_tracers, %{
module: module,
access: access,
function: name,
args: args,
guards: guards,
body: body,
trace_info: trace_info
})
Module.put_attribute(module, :nr_last_tracer, {name, length(args), trace_info})
Module.delete_attribute(module, :trace)
end
end
# Take no action if there are other function-level clauses
def __on_definition__(%{module: module}, _access, name, args, _guards, clauses) do
if trace_function?(module, name, length(args)) do
found =
clauses
|> Keyword.drop([:do])
|> Keyword.keys()
|> Enum.map(&"`#{&1}`")
|> Enum.join(", ")
Logger.warning(
"[New Relic] Unable to trace `#{inspect(module)}.#{name}/#{length(args)}` " <>
"due to additional function-level clauses: #{found} -- please remove @trace"
)
Module.delete_attribute(module, :trace)
end
end
defmacro __before_compile__(%{module: module}) do
Module.delete_attribute(module, :nr_last_tracer)
Module.make_overridable(module, function_specs(module))
quote do
(unquote_splicing(function_definitions(module)))
end
end
defp trace_function?(module, name, arity),
do:
trace_function?(:via_annotation, module) ||
trace_function?(:via_multiple_heads, module, name, arity)
defp trace_function?(:via_annotation, module), do: Module.get_attribute(module, :trace)
defp trace_function?(:via_multiple_heads, module, name, arity) do
case Module.get_attribute(module, :nr_last_tracer) do
{^name, ^arity, trace_info} -> trace_info
_ -> false
end
end
defp trace_deprecated?({_, category: :datastore}, module, name) do
Logger.warning(
"[New Relic] Trace `:datastore` deprecated in favor of automatic Ecto instrumentation. " <>
"Please remove @trace from #{inspect(module)}.#{name}"
)
false
end
defp trace_deprecated?(trace_info, _, _) do
trace_info
end
defp function_specs(module),
do:
module
|> Module.get_attribute(:nr_tracers)
|> Enum.map(&traced_function_spec/1)
|> Enum.uniq()
defp function_definitions(module),
do:
module
|> Module.get_attribute(:nr_tracers)
|> Enum.map(&traced_function_definition/1)
|> Enum.reverse()
defp traced_function_spec(%{function: function, args: args}), do: {function, length(args)}
defp traced_function_definition(%{
module: module,
access: :def,
function: function,
args: args,
body: body,
guards: [],
trace_info: trace_info
}) do
quote do
def unquote(function)(unquote_splicing(build_function_args(args))) do
unquote(traced_function_body(body, module, function, args, trace_info))
end
end
end
defp traced_function_definition(%{
module: module,
access: :def,
function: function,
args: args,
body: body,
guards: guards,
trace_info: trace_info
}) do
quote do
def unquote(function)(unquote_splicing(build_function_args(args)))
when unquote_splicing(guards) do
unquote(traced_function_body(body, module, function, args, trace_info))
end
end
end
defp traced_function_definition(%{
module: module,
access: :defp,
function: function,
args: args,
body: body,
guards: [],
trace_info: trace_info
}) do
quote do
defp unquote(function)(unquote_splicing(build_function_args(args))) do
unquote(traced_function_body(body, module, function, args, trace_info))
end
end
end
defp traced_function_definition(%{
module: module,
access: :defp,
function: function,
args: args,
body: body,
guards: guards,
trace_info: trace_info
}) do
quote do
defp unquote(function)(unquote_splicing(build_function_args(args)))
when unquote_splicing(guards) do
unquote(traced_function_body(body, module, function, args, trace_info))
end
end
end
defp traced_function_body(body, module, function, args, trace_info) do
trace_annotation =
case trace_info do
{name, options} -> {name, options}
name -> {name, []}
end
quote do
with {_, opts} <- unquote(trace_annotation),
:external <- Keyword.get(opts, :category) do
Process.put(:nr_already_tracing_external, true)
end
current_ref = make_ref()
{span, previous_span, previous_span_attrs} =
NewRelic.DistributedTrace.set_current_span(
label: {unquote(module), unquote(function), unquote(length(args))},
ref: current_ref
)
start_time = System.system_time()
start_time_mono = System.monotonic_time()
[reductions: start_reductions] = Process.info(self(), [:reductions])
try do
unquote(body)
rescue
exception ->
message = NewRelic.Util.Error.format_reason(:error, exception)
NewRelic.DistributedTrace.set_span(:error, message: message)
reraise exception, __STACKTRACE__
catch
:exit, value ->
message = NewRelic.Util.Error.format_reason(:exit, value)
NewRelic.DistributedTrace.set_span(:error, message: "(EXIT) #{message}")
exit(value)
after
end_time_mono = System.monotonic_time()
[reductions: end_reductions] = Process.info(self(), [:reductions])
parent_ref =
case previous_span do
{_, ref} -> ref
nil -> :root
end
duration_ms = System.convert_time_unit(end_time_mono - start_time_mono, :native, :microsecond) / 1_000
duration_acc = Process.get({:nr_duration_acc, parent_ref}, 0)
Process.put({:nr_duration_acc, parent_ref}, duration_acc + duration_ms)
child_duration_ms = Process.delete({:nr_duration_acc, current_ref}) || 0
if parent_ref == :root, do: Process.delete({:nr_duration_acc, parent_ref})
reductions = end_reductions - start_reductions
Tracer.Report.call(
{unquote(module), unquote(function), unquote(build_call_args(args))},
unquote(trace_annotation),
inspect(self()),
{span, previous_span || :root},
{start_time, start_time_mono, end_time_mono, child_duration_ms, reductions}
)
NewRelic.DistributedTrace.reset_span(
previous_span: previous_span,
previous_span_attrs: previous_span_attrs
)
Process.delete(:nr_already_tracing_external)
end
end
end
defp build_function_args(args) when is_list(args), do: Enum.map(args, &build_function_args/1)
# Don't try to re-declare the default argument
defp build_function_args({:\\, _, [arg, _default]}),
do: arg
defp build_function_args(arg), do: arg
def build_call_args(args) do
Macro.postwalk(args, &rewrite_call_term/1)
end
# Unwrap Struct literals into a Map, they can't be re-referenced directly due to enforced_keys
@struct_keys [:__aliases__, :__MODULE__]
defp rewrite_call_term({:%, line, [{key, _, _} = struct, {:%{}, _, members}]})
when key in @struct_keys do
{:%{}, line, [{:__struct__, struct}] ++ members}
end
# Strip default arguments
defp rewrite_call_term({:\\, _, [arg, _default]}), do: arg
# Drop the de-structuring side of a pattern match
defp rewrite_call_term({:=, _, [left, right]}) do
cond do
:__ignored__ == left -> right
:__ignored__ == right -> left
is_variable?(right) -> right
is_variable?(left) -> left
end
end
# Replace ignored variables with an atom
defp rewrite_call_term({name, _, context} = term) when is_variable(name, context) do
case Atom.to_string(name) do
"__" <> _special_form -> term
"_" <> _ignored_var -> :__ignored__
_ -> term
end
end
# Replace :__ignored__ with [] when it's the tail of a list so we don't create an improper list
defp rewrite_call_term({:|, line, [left, :__ignored__]}) do
{:|, line, [left, []]}
end
defp rewrite_call_term(term), do: term
defp is_variable?({name, _, context}) when is_variable(name, context), do: true
defp is_variable?(_term), do: false
end