TLDR; We use telemetry_ui to display telemetry based metrics in our Phoenix apps.
At Mirego, we used to deploy a lot of web applications on Heroku. We then had access to the Heroku metrics page that could tell us, at a glance, if our app was doing okay, responding fast, consuming anormal amount of memory, etc.
Nowadays, we deploy our web applications on a lot of platforms: Azure, AWS, Google Cloud Platform… Seeing global metrics for our apps is now trickier since all platforms have their own way of dealing with telemetry. We can also rely on external tool like New Relic, Honeycomb or Sentry to get insights out of those events. That means paying for a product, adding the integration to your app, controlling who on your team has access to the tool and sending user data on another platform.
In Elixir, our most used packages come with a built-in integration of Erlang’s telemetry
package. This means that they expose metrics through a uniform interface: telemetry
. Phoenix, Absinthe, Ecto, Oban, Tesla etc. They emit useful events, with measurements and metadata that can be listened to by our system. The Phoenix documentation shows how simple it is to use telemetry. The Reporter part is where things gets interesting. We can implement our own reporter that will record those events to query them later. PhoenixLiveDashboard has this goal with the Metrics
section. But it does not support time filtering, other graphs beside a line chart and the persistence is not baked in the package.
Enters Telemetry UI: an Elixir package that stores events in PostgreSQL and displays them inside our Phoenix application. The data stays on the same infrastructure and you can control how you display it. The configuration is not far from the official Phoenix example:
# application.ex
def start(_type, _args) do
children = [
MyApp.Repo,
MyAppWeb.Endpoint,
{Phoenix.PubSub, [name: MyApp.PubSub, adapter: Phoenix.PubSub.PG2]},
{TelemetryUI, telemetry_config()}
]
opts = [strategy: :one_for_one, name: MyApp.Supervisor]
Supervisor.start_link(children, opts)
end
def telemetry_config do
import Telemetry.Metrics
metrics = [
last_value("vm.memory.total"),
counter("phoenix.router_dispatch.stop.duration", description: "Number of requests"),
summary("phoenix.router_dispatch.stop.duration",
description: "Average HTTP request duration",
unit: {:native, :millisecond},
),
summary("phoenix.router_dispatch.stop.duration",
description: "HTTP request count",
unit: {:native, :millisecond},
reporter_options: [value_field: "count"]
),
summary("phoenix.router_dispatch.stop.duration",
tags: [:route],
description: "HTTP requests count by route",
reporter_options: [value_field: "count"],
unit: {:native, :millisecond}
)
]
[
metrics: metrics,
backend: %TelemetryUI.Backend.EctoPostgres{
repo: MyApp.Repo,
pruner_threshold: [months: -1],
pruner_interval: 84_000,
max_buffer_size: 10_000,
flush_interval_ms: 10_000
}
]
end