Packages
new_relic_agent
1.0.3
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
# Take no action if there is a top-level rescue clause
def __on_definition__(env, _access, name, _args, _guards, do: _, rescue: _) do
Logger.warn(
"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)) 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 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
start_time = System.system_time()
start_time_mono = System.monotonic_time()
NewRelic.DistributedTrace.set_current_span(
mfa: {unquote(module), unquote(function), unquote(length(args))}
)
try do
unquote(body)
after
end_time_mono = System.monotonic_time()
NewRelic.DistributedTrace.clear_current_span()
Tracer.Report.call(
{unquote(module), unquote(function), unquote(build_call_args(args))},
unquote(trace_info),
inspect(self()),
{start_time, start_time_mono, end_time_mono}
)
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
# Search for variables on the left side of a pattern match
# and prefix them with an underscore so they don't end up as
# unused variables
def rewrite_call_term({:=, metadata, [pattern, {name, _, context} = value]})
when is_variable(name, context) do
ignored_pattern = Macro.postwalk(pattern, &rewrite_match_pattern/1)
{:=, metadata, [ignored_pattern, value]}
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 rewrite_match_pattern({name, metadata, context} = term)
when is_variable(name, context) do
case Atom.to_string(name) do
"_" <> _rest -> term
name_str -> {:"_#{name_str}", metadata, context}
end
end
def rewrite_match_pattern(term), do: term
end