An Elixir Plugin Agent for New Relic Custom Metrics.
Forked from newrelix.ex, this library conforms to the New Relic Plugin API for custom metric monitoring. It does not publish any APM stats to New Relic.
The main changes are that this library targets the Plugin API specifically and
doesn't use any legacy code or aggregation logic from newrelic-erlang
. It also
is designed to work with the instrumentation API from Phoenix.
New Relic Plugins documentation.
See a sample application using NewRelix here for an implementation example of all the below instrumentation methods.
Add new_relix
to your list of dependencies in mix.exs
:
def deps do
[{:new_relix, github: "wfgilman/NewRelix"}]
end
Add the following configuration options to config.exs
:
config :new_relix,
license_key: System.get_env("NEW_RELIC_LICENSE_KEY"),
plugin_guid: "com.mycompany.elixir",
application_name: "Test",
poll_interval: 60_000,
retry_options: [retries: 3, jitter: 0.2],
httpoison_options: [timeout: 8000, recv_timeout: 8000]
poll_interval
, retry_options
and httpoison_options
are optional and default to
the values above.
If you want to use NewRelix to instrument Phoenix and Ecto, you need to follow an additional step.
For Phoenix, you need to add the name of your instrumentation module to the Endpoint config.
config :my_app, MyApp.Endpoint,
instrumenters: [MyApp.Instrument]
For Ecto, if you want to instrument all database queries, add your instrumentation module to the Repo config:
config :my_app, MyApp.Repo,
loggers: [{Ecto.LogEntry, :log, []}, {MyApp.Instrument, :log_entry, []}]
Note that if you take this step, be sure to include {Ecto.LogEntry, :log, []}
.
Create a module in your application that imports the instrumentation functions. This is where you will put any additional instrumentation callbacks for Phoenix. It will also be the entry point for your instrumentation.
defmodule MyApp.Instrument do
use NewRelix.Instrumenter
end
Generic instrumentation is achieved using MyApp.Instrument.measure/2
:
For example, you can replace any function in your application:
result = MyModule.my_function(my_arg)
with the following:
result = MyApp.Instrument.measure({MyModule, :my_function, [my_arg]})
The time it takes to execute the function will be recorded and sent to New
Relic. The label associated with the measurement defaults to "Other/#{Mod}/#{fun}[ms|]"
,
but can be overridden by providing a Keyword list to measure/2
.
opt = [label: "Database/ETL", count_unit: "query"]
result = MyApp.Instrument.measure(mfa, opts)
Phoenix instrumentation is achieved by implementing callback functions which are
called by Phoenix.Endpoint.instrument/3
. Two callbacks are implemented by
default: :phoenix_controller_call
and :phoenix_controller_render
. The labels
associated with these events are "Web/#{Mod}/#{fun}[ms|call]"
and "Web/#{Mod}/#{fun}[ms|render]"
,
respectively. These functions are overridable if you want to change the labels.
Any Phoenix function can be instrumented with Phoenix's extensible API. Just add
the callbacks to MyApp.Instrument
like so:
defmodule MyApp.Instrument do
use NewRelix.Instrumenter
render_view(:start, _compile_metadata, %{view: name}) do
"Web/#{name}[ms|render]"
end
render_view(:stop, time_diff, label) do
NewRelix.Collector.record_value(label, time_diff / 1_000_000)
end
end
The callback arguments are specified in the Phoenix Instrumentation API docs.
If you don't want to use this, simply don't configure the Phoenix endpoint.
Ecto instrumentation is a little more crude as it piggybacks off the logger
extensibility. This limits the metadata available for instrumentation to the
fields in the Ecto.LogEntry
struct. However, if you do want to measure all
database query execution times, simply configure Ecto :loggers
as described above.
log_entry/1
simply records the query time under the label "Database/Query[ms|query]"
.
However, this function can be overridden to provide more detail. For example:
defmodule MyApp.Instrument do
use NewRelix.Instrumenter
def log_entry(%{query_time: time, result: {:ok, %{command: command}}} = entry) do
str_command = command |> to_string() |> String.upcase()
label = "Database/#{to_string(str_command)}[ms|query]"
NewRelix.Collector.record_value(label, time / 1_000_000)
end
end
This will provide some additional detail about the query:
%{"Database/SELECT[ms|query]" => [2.5567]}
If you want more detail than this, it's better to wrap your query in measure/2
so you can specify the name explicitly.
Ecto Logger specification is documented here.
Details on the Ecto.LogEntry
struct are here.
Again, if you're not interested in this, just don't configure the Ecto loggers.
The metrics themselves are pretty simple, but creating a taxonomy for your metrics is an individualized thing so I've tried to leave it open to customization. Just follow the conventions specified by New Relic.
All metric names are prefixed with "Component/#{YourAppName}"
prior to being
pushed to New Relic.
The NewRelix architecture follows romul's newrelix.ex project. All credit goes to him!
Metrics are recored in NewRelix.Collector
, a GenServer which maintains them
in state. NewRelix.Poller
retrieves the state at the poll interval and spawns
a Task
to execute the call to the New Relic API. The Task
is managed by
GenRetry. NewRelix uses HTTPoison
as its HTTP client.
The library uses bypass
to test the pushing agent and coveralls
and credo
for coverage and style.
----------------
COV FILE LINES RELEVANT MISSED
100.0% lib/new_relix.ex 47 7 0
100.0% lib/new_relix/agent.ex 83 18 0
100.0% lib/new_relix/aggregator.ex 70 16 0
100.0% lib/new_relix/collector.ex 56 5 0
0.0% lib/new_relix/instrumenter.ex 96 0 0
100.0% lib/new_relix/poller.ex 75 16 0
100.0% test/support/mock.ex 61 2 0
[TOTAL] 100.0%
----------------
Distributed under the MIT License.