Packages
new_relic_agent
1.31.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/plug.ex
defmodule NewRelic.Telemetry.Plug do
use GenServer
@moduledoc """
Provides `Plug` instrumentation via `telemetry`.
Plug pipelines are auto-discovered and instrumented.
We automatically gather:
* Transaction metrics and events
* Transaction Traces
* Distributed Traces
You can opt-out of this instrumentation with `:plug_instrumentation_enabled` via configuration.
See `NewRelic.Config` for details.
"""
alias NewRelic.{Transaction, DistributedTrace, Util}
@doc false
def start_link(_) do
config = %{
enabled?: NewRelic.Config.feature?(:plug_instrumentation),
handler_id: {:new_relic, :plug}
}
GenServer.start_link(__MODULE__, config, name: __MODULE__)
end
@cowboy_start [:cowboy, :request, :start]
@cowboy_stop [:cowboy, :request, :stop]
@cowboy_exception [:cowboy, :request, :exception]
@bandit_start [:bandit, :request, :start]
@bandit_stop [:bandit, :request, :stop]
@bandit_exception [:bandit, :request, :exception]
@plug_router_start [:plug, :router_dispatch, :start]
@plug_events [
@cowboy_start,
@cowboy_stop,
@cowboy_exception,
@plug_router_start,
@bandit_start,
@bandit_stop,
@bandit_exception
]
@doc false
def init(%{enabled?: false}), do: :ignore
def init(%{enabled?: true} = config) do
:telemetry.attach_many(
config.handler_id,
@plug_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
@doc false
def handle_event(
[server, :request, :start],
measurements,
meta,
_config
) do
measurements = Map.put_new(measurements, :system_time, System.system_time())
with :collect <- Transaction.Reporter.start_transaction(:web, path(meta, server)) do
headers = get_headers(meta, server)
if NewRelic.Config.enabled?(),
do: DistributedTrace.start(:http, headers)
add_start_attrs(meta, measurements, headers, server)
maybe_report_queueing(headers)
end
end
def handle_event(
@plug_router_start,
_measurements,
%{conn: conn, route: route},
_config
) do
NewRelic.add_attributes(plug_name: plug_name(conn, route))
end
def handle_event(
[server, :request, :stop],
meas,
meta,
_config
) do
add_stop_attrs(meas, meta, server)
add_stop_error_attrs(meta)
Transaction.Reporter.stop_transaction(:web)
end
# Don't treat cowboy 404 as an exception
def handle_event(
[:cowboy, :request, :exception],
meas,
%{resp_status: "404" <> _} = meta,
_config
) do
add_stop_attrs(meas, meta, :cowboy)
Transaction.Reporter.stop_transaction(:web)
end
def handle_event(
[server, :request, :exception],
meas,
%{kind: kind} = meta,
_config
) do
add_stop_attrs(meas, meta, server)
{reason, stack} = NewRelic.Util.Telemetry.reason_and_stack(meta)
Transaction.Reporter.fail(%{kind: kind, reason: reason, stack: stack})
Transaction.Reporter.stop_transaction(:web)
end
def handle_event(_event, _measurements, _meta, _config) do
:ignore
end
defp add_start_attrs(meta, meas, headers, :cowboy) do
[
pid: inspect(self()),
system_time: meas[:system_time],
host: meta.req.host,
path: meta.req.path,
remote_ip: meta.req.peer |> elem(0) |> :inet_parse.ntoa() |> to_string(),
referer: headers["referer"],
user_agent: headers["user-agent"],
content_type: headers["content-type"],
request_method: meta.req.method
]
|> NewRelic.add_attributes()
end
defp add_start_attrs(meta, meas, headers, :bandit) do
[
pid: inspect(self()),
system_time: meas[:system_time],
host: meta.conn.host,
path: meta.conn.request_path,
remote_ip: meta.conn.remote_ip |> :inet_parse.ntoa() |> to_string(),
referer: headers["referer"],
user_agent: headers["user-agent"],
content_type: headers["content-type"],
request_method: meta.conn.method
]
|> NewRelic.add_attributes()
end
@kb 1024
defp add_stop_attrs(meas, meta, :cowboy) do
info = Process.info(self(), [:memory, :reductions])
[
duration: meas[:duration],
status: status_code(meta),
memory_kb: info[:memory] / @kb,
reductions: info[:reductions],
"cowboy.req_body_duration_ms": meas[:req_body_duration] |> to_ms,
"cowboy.resp_duration_ms": meas[:resp_duration] |> to_ms,
"cowboy.req_body_length": meas[:req_body_length],
"cowboy.resp_body_length": meas[:resp_body_length]
]
|> NewRelic.add_attributes()
end
defp add_stop_attrs(meas, meta, :bandit) do
info = Process.info(self(), [:memory, :reductions])
resp_duration_ms = meas[:resp_start_time] && to_ms(meas[:resp_end_time]) - to_ms(meas[:resp_start_time])
[
duration: meas[:duration] || 0,
status: status_code(meta) || 500,
memory_kb: info[:memory] / @kb,
reductions: info[:reductions],
"bandit.resp_duration_ms": resp_duration_ms,
"bandit.resp_body_bytes": meas[:resp_body_bytes]
]
|> NewRelic.add_attributes()
end
defp add_stop_error_attrs(%{resp_status: "5" <> _, error: {:socket_error, error, message}}) do
[
error: true,
"cowboy.socket_error": error,
"cowboy.socket_error.message": message
]
|> NewRelic.add_attributes()
end
# client timeout:
defp add_stop_error_attrs(%{error: {:socket_error, error, message}}) do
[
"cowboy.socket_error": error,
"cowboy.socket_error.message": message
]
|> NewRelic.add_attributes()
end
# server timeout:
defp add_stop_error_attrs(%{error: {:connection_error, error, message}}) do
[
"cowboy.connection_error": error,
"cowboy.connection_error.message": message
]
|> NewRelic.add_attributes()
end
defp add_stop_error_attrs(_meta) do
:ok
end
defp to_ms(duration),
do: System.convert_time_unit(duration, :native, :microsecond) / 1000
@request_start_header "x-request-start"
defp maybe_report_queueing(headers) do
with true <- NewRelic.Config.feature?(:request_queuing_metrics),
request_start when is_binary(request_start) <- headers[@request_start_header],
{:ok, request_start_s} <- Util.RequestStart.parse(request_start) do
NewRelic.add_attributes(request_start_s: request_start_s)
end
end
defp path(%{req: %{path: path}}, :cowboy), do: path
defp path(%{conn: %{request_path: path}}, :bandit), do: path
defp path(_, _), do: "unknown"
defp get_headers(%{conn: conn}, :bandit) do
Map.new(conn.req_headers)
end
defp get_headers(_, :bandit) do
%{}
end
defp get_headers(meta, :cowboy) do
meta.req.headers
end
defp status_code(%{resp_status: :undefined}) do
nil
end
defp status_code(%{resp_status: status})
when is_integer(status) do
status
end
defp status_code(%{resp_status: status})
when is_binary(status) do
String.split(status) |> List.first() |> String.to_integer()
end
defp status_code(%{conn: %{status: status}}) do
status
end
defp plug_name(conn, match_path) do
"/Plug/#{conn.method}/#{match_path}"
|> String.replace("/*glob", "")
|> String.replace("/*_path", "")
end
end