Packages
new_relic_agent
1.18.0-rc.4
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
# Take no action if there is a top-level rescue clause
def __on_definition__(env, _access, name, _args, _guards, do: _, rescue: _) do
Logger.warn(
"[New Relic] Unable to trace `#{inspect(env.module)}.#{name}` due to top-level rescue clause -- please remove @trace"
)
end
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
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
def trace_function?(module, name, arity),
do:
trace_function?(:via_annotation, module) ||
trace_function?(:via_multiple_heads, module, name, arity)
def trace_function?(:via_annotation, module), do: Module.get_attribute(module, :trace)
def 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
def trace_deprecated?({_, category: :datastore}, module, name) do
Logger.warn(
"[New Relic] Trace `:datastore` deprecated in favor of automatic ecto instrumentation. " <>
"Please remove @trace from #{inspect(module)}.#{name}"
)
false
end
def trace_deprecated?(trace_info, _, _) do
trace_info
end
def function_specs(module),
do:
module
|> Module.get_attribute(:nr_tracers)
|> Enum.map(&traced_function_spec/1)
|> Enum.uniq()
def function_definitions(module),
do:
module
|> Module.get_attribute(:nr_tracers)
|> Enum.map(&traced_function_definition/1)
|> Enum.reverse()
def traced_function_spec(%{function: function, args: args}), do: {function, length(args)}
def 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
def 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
def 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
def 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
def traced_function_body(body, module, function, args, trace_info) do
quote do
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()
try do
unquote(body)
after
end_time_mono = System.monotonic_time()
parent_ref =
case previous_span do
{_, ref} -> ref
nil -> :root
end
duration_ms =
System.convert_time_unit(end_time_mono - start_time_mono, :native, :millisecond)
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.get({:nr_duration_acc, current_ref}, 0)
Tracer.Report.call(
{unquote(module), unquote(function), unquote(build_call_args(args))},
unquote(trace_info),
inspect(self()),
{span, previous_span || :root},
{start_time, start_time_mono, end_time_mono, child_duration_ms}
)
NewRelic.DistributedTrace.reset_span(
previous_span: previous_span,
previous_span_attrs: previous_span_attrs
)
end
end
end
def build_function_args(args) when is_list(args), do: Enum.map(args, &build_function_args/1)
def build_function_args({:\\, _, [arg, _default]}),
# Don't try to re-declare the default argument
do: arg
def build_function_args(arg), do: arg
def build_call_args(args) do
Macro.postwalk(args, &rewrite_call_term/1)
end
# Strip default arguments
def rewrite_call_term({:\\, _, [arg, _default]}), do: arg
# Drop the de-structuring side of a pattern match
def rewrite_call_term({:=, _, [left, right]}) do
cond do
is_variable?(right) -> right
is_variable?(left) -> left
end
end
# Replace ignored variables with an atom
def rewrite_call_term({name, _, context} = term) when is_variable(name, context) do
case Atom.to_string(name) do
"_" <> _rest -> :__ignored__
_ -> term
end
end
def rewrite_call_term(term), do: term
def is_variable?({name, _, context}) when is_variable(name, context), do: true
def is_variable?(_term), do: false
end