Packages
new_relic_agent
1.37.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/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(type, headers \\ %{})
def start(:http, headers) do
if NewRelic.Config.feature?(:distributed_tracing) do
determine_context(headers)
|> track_transaction(transport_type: "HTTP")
end
:ok
end
def start(:other, headers) do
if NewRelic.Config.feature?(:distributed_tracing) do
determine_context(headers)
|> track_transaction(transport_type: "Other")
end
:ok
end
defp determine_context(headers) do
case accept_distributed_trace_headers(headers) do
%Context{} = context -> context
_ -> generate_new_context()
end
end
defp accept_distributed_trace_headers(headers) do
w3c_headers(headers) || newrelic_header(headers) || :no_payload
end
defp w3c_headers(headers) do
with {:ok, traceparent} when is_binary(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} when is_binary(trace_payload) <- Map.fetch(headers, @nr_header),
%Context{} = context <- __MODULE__.NewRelicContext.extract(trace_payload) do
context
else
_ -> false
end
end
def distributed_trace_headers(:other) do
distributed_trace_headers(:http) |> Map.new()
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
defp 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
defp 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
defp maybe_generate_sampling(%Context{sampled: nil, priority: nil} = context) do
{priority, sampled} = generate_sampling()
%{context | sampled: sampled, priority: priority}
end
defp maybe_generate_sampling(context), do: context
defp maybe_generate_trace_id(%Context{trace_id: nil} = context) do
%{context | trace_id: generate_guid(16)}
end
defp maybe_generate_trace_id(context) do
context
end
defp 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
defp 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
defp 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
defp 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
defp report_attributes(context, :w3c) do
context
end
defp 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
defp 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
add_span_attributes(Map.new(attrs))
end
def set_span(:http, url: url, method: method, component: component) do
add_span_attributes(%{url: url, method: method, component: component})
end
def set_span(:error, message: message) do
add_span_attributes(%{error: true, "error.message": message})
end
def set_span(:datastore,
statement: statement,
instance: instance,
address: address,
hostname: hostname,
component: component
) do
add_span_attributes(%{
statement: statement,
instance: instance,
address: address,
hostname: hostname,
component: component
})
end
def add_span_attributes(attrs) do
Process.put(
:nr_current_span_attrs,
Map.merge(get_span_attrs(), Map.new(attrs))
)
:ok
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.delete(:nr_current_span)
previous_span_attrs = Process.delete(:nr_current_span_attrs)
Process.put(:nr_current_span, current)
{current, previous_span, previous_span_attrs}
end
defp 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.0, 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
defp 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