Packages
new_relic_agent
1.36.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/telemetry/finch.ex
defmodule NewRelic.Telemetry.Finch do
use GenServer
require Logger
@moduledoc """
Provides `Finch` instrumentation via `telemetry`.
Finch requests are auto-discovered and instrumented as "external" calls.
We automatically gather:
* Transaction segments
* External metrics
* External spans
You can opt-out of this instrumentation with `:finch_instrumentation_enabled` via configuration.
See `NewRelic.Config` for details.
"""
def start_link(_) do
config = %{
enabled?: NewRelic.Config.feature?(:finch_instrumentation),
handler_id: {:new_relic, :finch}
}
GenServer.start_link(__MODULE__, config, name: __MODULE__)
end
@finch_request_start [:finch, :request, :start]
@finch_request_stop [:finch, :request, :stop]
@finch_request_exception [:finch, :request, :exception]
@finch_events [
@finch_request_start,
@finch_request_stop,
@finch_request_exception
]
@doc false
def init(%{enabled?: false}), do: :ignore
def init(%{enabled?: true} = config) do
:telemetry.attach_many(
config.handler_id,
@finch_events,
&__MODULE__.handle_event/4,
config
)
Process.flag(:trap_exit, true)
{:ok, config}
end
@doc false
def terminate(_reason, %{handler_id: handler_id}) do
:telemetry.detach(handler_id)
end
def handle_event(
@finch_request_start,
%{system_time: start_time},
%{request: request},
config
) do
if instrument?() do
{span, previous_span, previous_span_attrs} =
NewRelic.DistributedTrace.set_current_span(
label: {request.method, request.scheme, request.host, request.path},
ref: make_ref()
)
Process.put(
config.handler_id,
%{
start_time: start_time,
span: span,
previous_span: previous_span,
previous_span_attrs: previous_span_attrs
}
)
end
end
def handle_event(
@finch_request_stop,
%{duration: duration},
%{name: finch_pool, request: request, result: result},
config
) do
if instrument?() do
with %{
start_time: start_time,
span: span,
previous_span: previous_span,
previous_span_attrs: previous_span_attrs
} <- Process.delete(config.handler_id) do
metric_name = "External/#{request.host}/Finch/#{request.method}"
secondary_name = "#{request.host} - Finch/#{request.method}"
duration_ms = System.convert_time_unit(duration, :native, :microsecond) / 1000
duration_s = duration_ms / 1000
id = span
parent_id = previous_span || :root
url =
URI.to_string(%URI{scheme: "#{request.scheme}", host: request.host, path: request.path})
result_attrs =
case result do
{:ok, %{__struct__: Finch.Response} = response} -> %{"response.status": response.status}
{:ok, _acc} -> %{}
{:error, exception} -> %{error: true, "error.message": Exception.message(exception)}
end
NewRelic.Transaction.Reporter.add_trace_segment(%{
primary_name: metric_name,
secondary_name: secondary_name,
attributes: %{},
pid: self(),
id: span,
parent_id: parent_id,
start_time: start_time,
duration: duration
})
NewRelic.report_span(
timestamp_ms: System.convert_time_unit(start_time, :native, :millisecond),
duration_s: duration_s,
name: metric_name,
edge: [span: id, parent: parent_id],
category: "http",
attributes:
%{
url: url,
method: request.method,
component: "Finch",
"finch.pool": finch_pool
}
|> Map.merge(result_attrs)
)
NewRelic.incr_attributes(
"external.#{request.host}.call_count": 1,
"external.#{request.host}.duration_ms": duration_ms
)
NewRelic.report_metric(
{:external, url, "Finch", request.method},
duration_s: duration_s
)
NewRelic.Transaction.Reporter.track_metric({
{:external, url, "Finch", request.method},
duration_s: duration_s
})
NewRelic.DistributedTrace.reset_span(
previous_span: previous_span,
previous_span_attrs: previous_span_attrs
)
end
end
end
def handle_event(
@finch_request_exception,
%{duration: duration},
%{kind: kind, reason: reason, name: finch_pool, request: request},
config
) do
if instrument?() do
with %{
start_time: start_time,
span: span,
previous_span: previous_span,
previous_span_attrs: previous_span_attrs
} <- Process.delete(config.handler_id) do
metric_name = "External/#{request.host}/Finch/#{request.method}"
duration_ms = System.convert_time_unit(duration, :native, :microsecond) / 1000
duration_s = duration_ms / 1000
id = span
parent_id = previous_span || :root
url = URI.to_string(%URI{scheme: "#{request.scheme}", host: request.host, path: request.path})
error_message = NewRelic.Util.Error.format_reason(kind, reason)
NewRelic.report_span(
timestamp_ms: System.convert_time_unit(start_time, :native, :millisecond),
duration_s: duration_s,
name: metric_name,
edge: [span: id, parent: parent_id],
category: "http",
attributes: %{
url: url,
method: request.method,
component: "Finch",
"finch.pool": finch_pool,
error: true,
"error.message": error_message
}
)
NewRelic.DistributedTrace.reset_span(
previous_span: previous_span,
previous_span_attrs: previous_span_attrs
)
end
end
end
def handle_event(_event, _measurements, _meta, config) do
with %{
previous_span: previous_span,
previous_span_attrs: previous_span_attrs
} <- Process.delete(config.handler_id) do
NewRelic.DistributedTrace.reset_span(
previous_span: previous_span,
previous_span_attrs: previous_span_attrs
)
end
end
# Avoid double instrumenting in situation where
# application has manually instrumented externals already
defp instrument?() do
case Process.get(:nr_already_tracing_external) do
true ->
warn_once(
"[New Relic] Trace `:external` deprecated in favor of automatic Finch instrumentation. " <>
"Please remove @trace `category: :external` annotations from Finch requests, or disable " <>
"automatic Finch instrumentation."
)
false
_ ->
true
end
end
defp warn_once(message) do
case :persistent_term.get(__MODULE__, :not_logged) do
:logged ->
:skip
:not_logged ->
:persistent_term.put(__MODULE__, :logged)
Logger.warning(message)
end
end
end