Packages
Prometheus monitoring system client Plugs. Http metrics collector and exporter.
Current section
Files
Jump to
Current section
Files
lib/prometheus/plug_exporter.ex
defmodule Prometheus.PlugExporter do
@moduledoc """
Exports Prometheus metrics via configurable endpoint:
``` elixir
# define plug
defmodule MetricsPlugExporter do
use Prometheus.PlugExporter
end
# on app startup (e.g. supervisor setup)
MetricsPlugExporter.setup()
# in your plugs pipeline
plug MetricsPlugExporter
```
**Do not add to Phoenix Router pipelines!** You will be getting 404!
```
Note that router pipelines are only invoked after a route is found.
No plug is invoked in case no matches were found.
```
### Metrics
Also maintains telemetry metrics:
- telemetry_scrape_duration_seconds
- telemetry_scrape_size_bytes
Do not forget to call `setup/0` before using plug, for example on application start!
### Configuration
Plugs exporter can be configured via PlugsExporter key of `:prometheus` app env.
Default configuration:
```elixir
config :prometheus, MetricsPlugExporter, # (you should replace this with the name of your plug)
path: "/metrics",
format: :text,
registry: :default,
auth: false
```
Export endpoint can be optionally secured using HTTP Basic Authentication:
```elixir
auth: {:basic, "username", "password"}
```
"""
require Logger
use Prometheus.Metric
use Prometheus.Config, [path: "/metrics",
format: :text,
registry: :default,
auth: false]
## TODO: support multiple endpoints [for example separate endpoint for each registry]
## endpoints: [[registry: :qwe,
## path: "/metrics1"],
## [registry: :default,
## path: "/metrics",
## format: :protobuf]]
defmacro __using__(_opts) do
module_name = __CALLER__.module
registry = Config.registry(module_name)
path = Plug.Router.Utils.split(Config.path(module_name))
format = normalize_format(Config.format(module_name))
auth = Config.auth(module_name)
content_type = format.content_type
labels = [registry, format.content_type]
quote do
@behaviour Plug
import Plug.Conn
use Prometheus.Metric
def setup() do
Summary.declare([name: :telemetry_scrape_duration_seconds,
help: "Scrape duration",
labels: ["registry", "content_type"],
registry: unquote(registry)])
Summary.declare([name: :telemetry_scrape_size_bytes,
help: "Scrape size, uncompressed",
labels: ["registry", "content_type"],
registry: unquote(registry)])
end
def init(_opts) do
end
def call(conn, _opts) do
case conn.path_info do
unquote(path) ->
unquote(handle_auth(auth, content_type))
_ ->
conn
end
end
defp scrape_data do
scrape = Summary.observe_duration(
[registry: unquote(registry),
name: :telemetry_scrape_duration_seconds,
labels: unquote(labels)],
fn () ->
unquote(format).format(unquote(registry))
end)
Summary.observe(
[registry: unquote(registry),
name: :telemetry_scrape_size_bytes,
labels: unquote(labels)],
:erlang.iolist_size(scrape))
scrape
end
unquote(
case auth do
{:basic, _, _} -> quote do
## borrowed from https://github.com/CultivateHQ/basic_auth
## The MIT License (MIT)
## Copyright (c) 2015 Mark Connell
defp valid_basic_credentials?(["Basic " <> encoded_string], username, password) do
Base.decode64!(encoded_string) == "#{username}:#{password}"
end
defp valid_basic_credentials?(_, _, _) do
false
end
## end of basic_auth code
end
_ -> :ok
end)
end
end
defp handle_auth(auth, content_type) do
case auth do
false ->
send_metrics(content_type)
{:basic, username, password} -> quote do
if valid_basic_credentials?(Plug.Conn.get_req_header(conn, "authorization"),
unquote(username),
unquote(password)) do
unquote(send_metrics(content_type))
else
unquote(send_unauthorized("metrics"))
end
end
end
end
defp send_metrics(content_type) do
quote do
scrape = scrape_data()
conn
|> put_resp_content_type(unquote(content_type), nil)
|> send_resp(200, scrape)
|> halt
end
end
defp send_unauthorized(realm) do
quote do
Plug.Conn.put_resp_header(conn, "www-authenticate", "Basic realm=\"#{unquote(realm)}\"")
|> Plug.Conn.send_resp(401, "401 Unauthorized")
|> Plug.Conn.halt
end
end
defp normalize_format(:text), do: :prometheus_text_format
defp normalize_format(:protobuf), do: :prometheus_protobuf_format
defp normalize_format(Prometheus.Format.Text), do: :prometheus_text_format
defp normalize_format(Prometheus.Format.Protobuf), do: :prometheus_protobuf_format
defp normalize_format(format), do: format
end