Packages
new_relic_agent
1.25.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/distributed_trace.ex
defmodule NewRelic.DistributedTrace do
@nr_header "newrelic"
@w3c_traceparent "traceparent"
@w3c_tracestate "tracestate"
@moduledoc false
alias NewRelic.DistributedTrace.Context
alias NewRelic.Harvest.Collector.AgentRun
alias NewRelic.Transaction
def start(:http, headers) do
determine_context(:http, headers)
|> track_transaction(transport_type: "HTTP")
end
defp determine_context(:http, headers) do
case accept_distributed_trace_headers(:http, headers) do
%Context{} = context -> context
_ -> generate_new_context()
end
end
def accept_distributed_trace_headers(:http, headers) do
w3c_headers(headers) || newrelic_header(headers) || :no_payload
end
defp w3c_headers(headers) do
with {:ok, _traceparent} <- Map.fetch(headers, @w3c_traceparent),
%Context{} = context <- __MODULE__.W3CTraceContext.extract(headers) do
context
else
_ -> false
end
end
defp newrelic_header(headers) do
with {:ok, trace_payload} <- Map.fetch(headers, @nr_header),
%Context{} = context <- __MODULE__.NewRelicContext.extract(trace_payload) do
context
else
_ -> false
end
end
def distributed_trace_headers(:http) do
case get_tracing_context() do
nil ->
[]
context ->
context = %{
context
| span_guid: get_current_span_guid(),
timestamp: System.system_time(:millisecond)
}
nr_header = NewRelic.DistributedTrace.NewRelicContext.generate(context)
{traceparent, tracestate} = NewRelic.DistributedTrace.W3CTraceContext.generate(context)
[
{@nr_header, nr_header},
{@w3c_traceparent, traceparent},
{@w3c_tracestate, tracestate}
]
end
end
def generate_new_context() do
{priority, sampled} = generate_sampling()
%Context{
source: :new,
account_id: AgentRun.account_id(),
app_id: AgentRun.primary_application_id(),
trust_key: AgentRun.trusted_account_key(),
priority: priority,
sampled: sampled
}
end
def track_transaction(context, transport_type: type) do
context
|> assign_transaction_guid()
|> maybe_generate_sampling()
|> maybe_generate_trace_id()
|> report_attributes(transport_type: type)
|> report_attributes(:w3c)
|> convert_to_outbound()
|> set_tracing_context()
end
def maybe_generate_sampling(%Context{sampled: nil, priority: nil} = context) do
{priority, sampled} = generate_sampling()
%{context | sampled: sampled, priority: priority}
end
def maybe_generate_sampling(context), do: context
def maybe_generate_trace_id(%Context{trace_id: nil} = context) do
%{context | trace_id: generate_guid(16)}
end
def maybe_generate_trace_id(context) do
context
end
def report_attributes(
%{source: {:w3c, %{tracestate: :non_new_relic}}} = context,
transport_type: type
) do
[
"parent.transportType": type,
guid: context.trace_id,
traceId: context.trace_id,
priority: context.priority,
sampled: context.sampled,
parentId: context.parent_id,
parentSpanId: context.span_guid
]
|> NewRelic.add_attributes()
context
end
def report_attributes(
%Context{
parent_id: nil,
span_guid: nil
} = context,
transport_type: _type
) do
[
guid: context.guid,
traceId: context.trace_id,
priority: context.priority,
sampled: context.sampled
]
|> NewRelic.add_attributes()
context
end
def report_attributes(context, transport_type: type) do
[
"parent.type": context.type,
"parent.app": context.app_id,
"parent.account": context.account_id,
"parent.transportType": type,
"parent.transportDuration": transport_duration(context.timestamp),
parentId: context.parent_id,
parentSpanId: context.span_guid,
guid: context.guid,
traceId: context.trace_id,
priority: context.priority,
sampled: context.sampled
]
|> NewRelic.add_attributes()
context
end
def report_attributes(%{source: {:w3c, w3c}} = context, :w3c) do
NewRelic.add_attributes(tracingVendors: Enum.join(w3c.tracing_vendors, ","))
w3c.tracestate == :new_relic &&
NewRelic.add_attributes(trustedParentId: w3c.span_id)
context
end
def report_attributes(context, :w3c) do
context
end
def convert_to_outbound(%Context{} = context) do
%Context{
source: context.source,
account_id: AgentRun.account_id(),
app_id: AgentRun.primary_application_id(),
parent_id: context.guid,
trust_key: context.trust_key,
guid: context.guid,
trace_id: context.trace_id,
priority: context.priority,
sampled: context.sampled
}
end
def set_tracing_context(context) do
Transaction.Sidecar.trace_context(context)
end
def get_tracing_context() do
Transaction.Sidecar.trace_context()
end
def set_span(:generic, attrs) do
Process.put(:nr_current_span_attrs, Enum.into(attrs, %{}))
end
def set_span(:http, url: url, method: method, component: component) do
Process.put(:nr_current_span_attrs, %{url: url, method: method, component: component})
end
def set_span(:error, message: message) do
Process.put(
:nr_current_span_attrs,
Map.merge(get_span_attrs(), %{error: true, "error.message": message})
)
end
def set_span(
:datastore,
statement: statement,
instance: instance,
address: address,
hostname: hostname,
component: component
) do
Process.put(:nr_current_span_attrs, %{
statement: statement,
instance: instance,
address: address,
hostname: hostname,
component: component
})
end
def get_span_attrs() do
Process.get(:nr_current_span_attrs) || %{}
end
def set_current_span(label: label, ref: ref) do
current = {label, ref}
previous_span = Process.get(:nr_current_span)
previous_span_attrs = Process.get(:nr_current_span_attrs)
Process.put(:nr_current_span, current)
{current, previous_span, previous_span_attrs}
end
def get_current_span_guid() do
case Process.get(:nr_current_span) do
nil -> generate_guid(pid: self())
{label, ref} -> generate_guid(pid: self(), label: label, ref: ref)
end
end
def read_current_span() do
Process.get(:nr_current_span)
end
def reset_span(previous_span: previous_span, previous_span_attrs: previous_span_attrs) do
Process.put(:nr_current_span, previous_span)
Process.put(:nr_current_span_attrs, previous_span_attrs)
end
defp generate_sampling() do
case {generate_sample?(), generate_priority()} do
{true, priority} -> {priority + 1, true}
{false, priority} -> {priority, false}
end
end
defp generate_sample?() do
NewRelic.DistributedTrace.BackoffSampler.sample?()
end
defp generate_priority do
:rand.uniform() |> Float.round(6)
end
def assign_transaction_guid(context) do
Map.put(context, :guid, generate_guid())
end
def generate_guid(), do: :crypto.strong_rand_bytes(8) |> Base.encode16() |> String.downcase()
def generate_guid(16), do: :crypto.strong_rand_bytes(16) |> Base.encode16() |> String.downcase()
def generate_guid(pid: pid), do: encode_guid([pid, node()])
def generate_guid(pid: pid, label: label, ref: ref), do: encode_guid([label, ref, pid, node()])
def encode_guid(segments) when is_list(segments) do
segments
|> Enum.map(&encode_guid/1)
|> Enum.join("")
|> String.pad_trailing(16, "0")
end
def encode_guid(term) do
term
|> :erlang.phash2()
|> Integer.to_charlist(16)
|> to_string()
|> String.slice(0..3)
|> String.downcase()
end
defp transport_duration(context_start_time) do
(System.system_time(:millisecond) - context_start_time) / 1_000
end
end