Packages
new_relic_agent
1.24.1
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 via configuration. See `NewRelic.Config` for details.
----
To prevent reporting an individual transaction:
```elixir
NewRelic.ignore_transaction()
```
----
Inside a Transaction, the agent will track work across processes that are spawned and linked.
You can signal to the agent not to track work done inside a spawned process, which will
exclude it from the current Transaction.
To exclude a process from the Transaction:
```elixir
Task.async(fn ->
NewRelic.exclude_from_transaction()
Work.wont_be_tracked()
end)
```
"""
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]
@plug_router_start [:plug, :router_dispatch, :start]
@plug_events [
@cowboy_start,
@cowboy_stop,
@cowboy_exception,
@plug_router_start
]
@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(
@cowboy_start,
%{system_time: system_time},
meta,
_config
) do
Transaction.Reporter.start_transaction(:web)
if NewRelic.Config.enabled?(),
do: DistributedTrace.start(:http, meta.req.headers)
add_start_attrs(meta, system_time)
maybe_report_queueing(meta)
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(
@cowboy_stop,
%{duration: duration} = meas,
meta,
_config
) do
add_stop_attrs(meas, meta, duration)
add_stop_error_attrs(meta)
Transaction.Reporter.stop_transaction(:web)
end
# Don't treat 404 as an exception
def handle_event(
@cowboy_exception,
%{duration: duration} = meas,
%{resp_status: "404" <> _} = meta,
_config
) do
add_stop_attrs(meas, meta, duration)
Transaction.Reporter.stop_transaction(:web)
end
def handle_event(
@cowboy_exception,
%{duration: duration} = meas,
%{kind: kind} = meta,
_config
) do
add_stop_attrs(meas, meta, duration)
{reason, stack} = 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, system_time) do
[
pid: inspect(self()),
system_time: system_time,
host: meta.req.host,
path: meta.req.path,
remote_ip: meta.req.peer |> elem(0) |> :inet_parse.ntoa() |> to_string(),
referer: meta.req.headers["referer"],
user_agent: meta.req.headers["user-agent"],
content_type: meta.req.headers["content-type"],
request_method: meta.req.method
]
|> NewRelic.add_attributes()
end
@kb 1024
defp add_stop_attrs(meas, meta, duration) do
info = Process.info(self(), [:memory, :reductions])
[
duration: 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_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, :millisecond)
@request_start_header "x-request-start"
defp maybe_report_queueing(meta) do
with true <- NewRelic.Config.feature?(:request_queuing_metrics),
request_start when is_binary(request_start) <- meta.req.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 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 reason_and_stack(%{reason: %{__exception__: true} = reason, stacktrace: stack}) do
{reason, stack}
end
defp reason_and_stack(%{reason: {{reason, stack}, _init_call}}) do
{reason, stack}
end
defp reason_and_stack(%{reason: {reason, _init_call}}) do
{reason, []}
end
defp reason_and_stack(unexpected_cowboy_exception) do
NewRelic.log(:debug, "unexpected_cowboy_exception: #{inspect(unexpected_cowboy_exception)}")
{:unexpected_cowboy_exception, []}
end
defp plug_name(conn, match_path) do
"/Plug/#{conn.method}/#{match_path}"
|> String.replace("/*glob", "")
|> String.replace("/*_path", "")
end
end