Packages
new_relic_agent
1.23.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/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(
: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 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