Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,20 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]
## [Unreleased] — superfly Bandit-only fork

### Changed

- **Breaking:** the standalone metrics server is now Bandit-only. `Plug.Cowboy` support has been removed
along with the `:cowboy_opts` and `:pool_size` config keys. `:bandit` is now a required dependency.
- Adds a new `:bandit_opts` keyword in `metrics_server` config that is spliced into `Bandit.child_spec/1`.
Use it to pass through Thousand Island transport options, e.g. `bandit_opts: [thousand_island_options:
[transport_options: [reuseport: true, reuseaddr: true]]]` to allow the listener to bind alongside an
existing peer during a blue/green hot deploy.

### Merged from upstream

- [#266](https://github.com/akoutmos/prom_ex/pull/266) — initial Bandit support for the metrics server.

## [1.11.0] - 2024-10-24

Expand Down
20 changes: 10 additions & 10 deletions lib/prom_ex.ex
Original file line number Diff line number Diff line change
Expand Up @@ -503,9 +503,6 @@ defmodule PromEx do

@doc false
def metrics_server_child_spec(acc, config, prom_ex_module, process_name) when is_map(config) do
transport_options = [num_acceptors: config.pool_size]
cowboy_opts = Keyword.drop(config.cowboy_opts, [:port, :transport_options])

port =
case Map.fetch(config, :port) do
{:ok, port} when is_integer(port) ->
Expand Down Expand Up @@ -552,13 +549,7 @@ defmodule PromEx do

plug_definition = {PromEx.MetricsServer.Plug, plug_opts}

spec =
Plug.Cowboy.child_spec(
ref: process_name,
scheme: scheme,
plug: plug_definition,
options: [{:port, port}, {:transport_options, transport_options} | cowboy_opts]
)
spec = server_spec(plug_definition, port, scheme, process_name, config)

Logger.info(
"PromEx is starting a standalone metrics server on port #{inspect(port)} over #{Atom.to_string(scheme)}"
Expand All @@ -571,6 +562,15 @@ defmodule PromEx do
acc
end

@doc false
def server_spec(plug_definition, port, scheme, _process_name, config) do
bandit_opts = Keyword.drop(config.bandit_opts, [:scheme, :plug, :port])

Bandit.child_spec(
[scheme: scheme, plug: plug_definition, port: port] ++ bandit_opts
)
end

@doc false
def metric_prefix(otp_app, plug_in) do
[otp_app, :prom_ex, plug_in]
Expand Down
27 changes: 12 additions & 15 deletions lib/prom_ex/config.ex
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,7 @@ defmodule PromEx.Config do
port: 4021,
path: "/metrics", # This is an optional setting and will default to `"/metrics"`
protocol: :http, # This is an optional setting and will default to `:http`
pool_size: 5, # This is an optional setting and will default to `5`
cowboy_opts: [], # This is an optional setting and will default to `[]`
bandit_opts: [], # This is an optional setting and will default to `[]`
auth_strategy: :none # This is an optional and will default to `:none`
]
```
Expand Down Expand Up @@ -198,18 +197,15 @@ defmodule PromEx.Config do
* all of these keys, and any additional ones, will be provided as EEx vars to the template file.

* `:metrics_server` - This key contains the configuration information needed to run a standalone
HTTP server powered by Cowboy. This server provides a lightweight solution to serving up PromEx
metrics. In order to use this standalone metrics server plug you need to have `:plug` and `:plug_cowboy`
as dependencies in your project. Its configuration options are:
HTTP server powered by Bandit. This server provides a lightweight solution to serving up PromEx
metrics. Bandit is required as a dependency in your project. Its configuration options are:

* `:port` - The port that the Cowboy HTTP server should run on.
* `:port` - The port that the Bandit HTTP server should run on.

* `:path` - The path that the metrics should be accessible at.

* `:protocol` - The protocol that the metrics should be accessible over (`:http` or `:https`).

* `:pool_size` - How many Cowboy processes should be in the pool to handle metrics related requests.

* `:auth_strategy` - What authentication strategy should be used to authorize requests to your metrics. The
Supported strategies are `:none`, `:bearer`, and `:basic`. Depending on what strategy is selected, you
will need to also add additional config values. For `:none` (which is the default), no additional
Expand All @@ -225,10 +221,12 @@ defmodule PromEx.Config do
* `:auth_password` - When using a `:bearer` authentication strategy, this field is required to validate the
incoming request against a valid password.

* `:cowboy_opts` - A keyword list of any additional options that should be passed to `Plug.Cowboy` (see
docs for more information https://hexdocs.pm/plug_cowboy/Plug.Cowboy.html). The `:port` and
`:transport_options` options are handled by PromEx via the aforementioned config settings and so
adding them again here has no effect.
* `:bandit_opts` - A keyword list of any additional options that should be passed to `Bandit.child_spec/1`
(see https://hexdocs.pm/bandit/Bandit.html#t:options/0). The `:scheme`, `:plug`, and `:port` options
are handled by PromEx via the aforementioned config settings and so adding them again here has no
effect. Useful for passing through transport-level options, e.g.
`bandit_opts: [thousand_island_options: [transport_options: [reuseport: true, reuseaddr: true]]]`
to allow the metrics listener to bind alongside an existing peer during a blue/green hot deploy.
"""

@typedoc """
Expand All @@ -238,7 +236,7 @@ defmodule PromEx.Config do
- `ets_flush_interval`: How often should the ETS buffer table be compacted.
- `grafana_config`: A map containing all the relevant settings to connect to Grafana.
- `grafana_agent_config`: A map containing all the relevant settings to connect to GrafanaAgent.
- `metrics_server_config`: A map containing all the relevant settings to start a standalone HTTP Cowboy server for metrics.
- `metrics_server_config`: A map containing all the relevant settings to start a standalone HTTP Bandit server for metrics.
"""

alias PromEx.GrafanaAgent.Downloader
Expand Down Expand Up @@ -379,8 +377,7 @@ defmodule PromEx.Config do
port: get_metrics_server_config(metrics_server_opts, :port),
path: Keyword.get(metrics_server_opts, :path, "/metrics"),
protocol: Keyword.get(metrics_server_opts, :protocol, :http),
pool_size: Keyword.get(metrics_server_opts, :pool_size, 5),
cowboy_opts: Keyword.get(metrics_server_opts, :cowboy_opts, []),
bandit_opts: Keyword.get(metrics_server_opts, :bandit_opts, []),
auth_strategy: Keyword.get(metrics_server_opts, :auth_strategy, :none)
}

Expand Down
4 changes: 2 additions & 2 deletions mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,8 @@ defmodule PromEx.MixProject do
# Optional dependencies depending on what telemetry events the user is interested in capturing
{:phoenix, ">= 1.7.0", optional: true},
{:phoenix_live_view, ">= 0.20.0", optional: true},
{:plug, ">= 1.16.0", optional: true},
{:plug_cowboy, ">= 2.6.0", optional: true},
{:plug, ">= 1.16.0"},
{:bandit, ">= 1.0.0"},
{:ecto, ">= 3.11.0", optional: true},
{:oban, ">= 2.10.0", optional: true},
{:absinthe, ">= 1.7.0", optional: true},
Expand Down
2 changes: 2 additions & 0 deletions mix.lock
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
%{
"absinthe": {:hex, :absinthe, "1.7.8", "43443d12ad2b4fcce60e257ac71caf3081f3d5c4ddd5eac63a02628bcaf5b556", [:mix], [{:dataloader, "~> 1.0.0 or ~> 2.0", [hex: :dataloader, repo: "hexpm", optional: true]}, {:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}, {:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}, {:opentelemetry_process_propagator, "~> 0.2.1 or ~> 0.3", [hex: :opentelemetry_process_propagator, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c4085df201892a498384f997649aedb37a4ce8a726c170d5b5617ed3bf45d40b"},
"bandit": {:hex, :bandit, "1.6.11", "2fbadd60c95310eefb4ba7f1e58810aa8956e18c664a3b2029d57edb7d28d410", [:mix], [{:hpax, "~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:thousand_island, "~> 1.0", [hex: :thousand_island, repo: "hexpm", optional: false]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "543f3f06b4721619a1220bed743aa77bf7ecc9c093ba9fab9229ff6b99eacc65"},
"broadway": {:hex, :broadway, "1.1.0", "8ed3aea01fd6f5640b3e1515b90eca51c4fc1fac15fb954cdcf75dc054ae719c", [:mix], [{:gen_stage, "~> 1.0", [hex: :gen_stage, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.3.7 or ~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "25e315ef1afe823129485d981dcc6d9b221cea30e625fd5439e9b05f44fb60e4"},
"bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"},
"bypass": {:hex, :bypass, "2.1.0", "909782781bf8e20ee86a9cabde36b259d44af8b9f38756173e8f5e2e1fabb9b1", [:mix], [{:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.0", [hex: :plug_cowboy, repo: "hexpm", optional: false]}, {:ranch, "~> 1.3", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "d9b5df8fa5b7a6efa08384e9bbecfe4ce61c77d28a4282f79e02f1ef78d96b80"},
Expand Down Expand Up @@ -48,6 +49,7 @@
"telemetry_metrics": {:hex, :telemetry_metrics, "1.0.0", "29f5f84991ca98b8eb02fc208b2e6de7c95f8bb2294ef244a176675adc7775df", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "f23713b3847286a534e005126d4c959ebcca68ae9582118ce436b521d1d47d5d"},
"telemetry_metrics_prometheus_core": {:hex, :telemetry_metrics_prometheus_core, "1.2.1", "c9755987d7b959b557084e6990990cb96a50d6482c683fb9622a63837f3cd3d8", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6 or ~> 1.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "5e2c599da4983c4f88a33e9571f1458bf98b0cf6ba930f1dc3a6e8cf45d5afb6"},
"telemetry_poller": {:hex, :telemetry_poller, "1.1.0", "58fa7c216257291caaf8d05678c8d01bd45f4bdbc1286838a28c4bb62ef32999", [:rebar3], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "9eb9d9cbfd81cbd7cdd24682f8711b6e2b691289a0de6826e58452f28c103c8f"},
"thousand_island": {:hex, :thousand_island, "1.3.14", "ad45ebed2577b5437582bcc79c5eccd1e2a8c326abf6a3464ab6c06e2055a34a", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "d0d24a929d31cdd1d7903a4fe7f2409afeedff092d277be604966cd6aa4307ef"},
"websock": {:hex, :websock, "0.5.3", "2f69a6ebe810328555b6fe5c831a851f485e303a7c8ce6c5f675abeb20ebdadc", [:mix], [], "hexpm", "6105453d7fac22c712ad66fab1d45abdf049868f253cf719b625151460b8b453"},
"websock_adapter": {:hex, :websock_adapter, "0.5.6", "0437fe56e093fd4ac422de33bf8fc89f7bc1416a3f2d732d8b2c8fd54792fe60", [:mix], [{:bandit, ">= 0.6.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "e04378d26b0af627817ae84c92083b7e97aca3121196679b73c73b99d0d133ea"},
}
21 changes: 21 additions & 0 deletions test/prom_ex/config_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -156,5 +156,26 @@ defmodule PromEx.ConfigTest do
]
)
end

test "metrics_server defaults to empty bandit_opts" do
%PromEx.Config{metrics_server_config: server_config} = Config.build(metrics_server: [port: 8080])

assert server_config.bandit_opts == []
refute Map.has_key?(server_config, :cowboy_opts)
refute Map.has_key?(server_config, :pool_size)
end

test "metrics_server forwards bandit_opts verbatim" do
bandit_opts = [
thousand_island_options: [
transport_options: [reuseport: true, reuseaddr: true]
]
]

%PromEx.Config{metrics_server_config: server_config} =
Config.build(metrics_server: [port: 8080, bandit_opts: bandit_opts])

assert server_config.bandit_opts == bandit_opts
end
end
end
82 changes: 82 additions & 0 deletions test/prom_ex/metrics_server/startup_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
defmodule PromEx.MetricsServer.StartupTest do
use ExUnit.Case, async: true

@moduletag :capture_log

defmodule TestPromEx do
use PromEx, otp_app: :prom_ex

@impl true
def plugins, do: []

@impl true
def dashboards, do: []
end

defp build_config(extra \\ %{}) do
Map.merge(
%{
port: 9999,
path: "/metrics",
protocol: :http,
auth_strategy: :none,
bandit_opts: []
},
extra
)
end

defp bandit_opts_from_spec(spec) do
{Bandit, :start_link, [opts]} = spec.start
opts
end

describe "metrics_server_child_spec/4" do
test "produces a Bandit child spec with the configured port and scheme" do
[spec | []] = PromEx.metrics_server_child_spec([], build_config(), TestPromEx, :test_server)

opts = bandit_opts_from_spec(spec)
assert opts[:port] == 9999
assert opts[:scheme] == :http
assert {PromEx.MetricsServer.Plug, _plug_opts} = opts[:plug]
end

test "splices bandit_opts into the child spec" do
bandit_opts = [
thousand_island_options: [
transport_options: [reuseport: true, reuseaddr: true]
]
]

[spec | []] =
PromEx.metrics_server_child_spec(
[],
build_config(%{bandit_opts: bandit_opts}),
TestPromEx,
:test_server
)

opts = bandit_opts_from_spec(spec)
assert opts[:thousand_island_options] == [transport_options: [reuseport: true, reuseaddr: true]]
end

test "ignores attempts to override scheme/plug/port via bandit_opts" do
[spec | []] =
PromEx.metrics_server_child_spec(
[],
build_config(%{bandit_opts: [port: 1, scheme: :https, plug: :evil]}),
TestPromEx,
:test_server
)

opts = bandit_opts_from_spec(spec)
assert opts[:port] == 9999
assert opts[:scheme] == :http
assert {PromEx.MetricsServer.Plug, _} = opts[:plug]
end

test "skips child spec when config is :disabled" do
assert PromEx.metrics_server_child_spec([], :disabled, TestPromEx, :test_server) == []
end
end
end