From 3c8b365702ad98df36b61b9fafbe347ef525e5f9 Mon Sep 17 00:00:00 2001 From: psteinroe Date: Thu, 15 Oct 2026 08:36:20 +0000 Subject: [PATCH 1/2] feat(metrics): make the listener address configurable --- docs/reference/configuration.md | 8 ++++ src/metrics/prometheus.rs | 82 +++++++++++++++++++++++++++++---- 2 files changed, 82 insertions(+), 8 deletions(-) diff --git a/docs/reference/configuration.md b/docs/reference/configuration.md index b67681a..0b53703 100644 --- a/docs/reference/configuration.md +++ b/docs/reference/configuration.md @@ -221,3 +221,11 @@ Override with the `CONFIG_PATH` environment variable: ```bash CONFIG_PATH=/etc/postgres-stream/config.yaml postgres-stream ``` + +## Metrics + +Prometheus metrics are served at `/metrics` on `[::]:9000`. Set `PGSTREAM_METRICS_ADDR` to listen elsewhere, for example to run several instances on one host network: + +```bash +PGSTREAM_METRICS_ADDR=127.0.0.1:9187 postgres-stream +``` diff --git a/src/metrics/prometheus.rs b/src/metrics/prometheus.rs index e05c822..5604fbf 100644 --- a/src/metrics/prometheus.rs +++ b/src/metrics/prometheus.rs @@ -1,13 +1,52 @@ +use std::net::{AddrParseError, IpAddr, Ipv6Addr, SocketAddr}; + use metrics_exporter_prometheus::{BuildError, PrometheusBuilder}; use super::{register_failover_metrics, register_maintenance_metrics, register_stream_metrics}; -/// Initializes metrics with an automatic HTTP server on port 9000. +/// Environment variable that overrides the metrics listener address. +pub const METRICS_LISTEN_ADDRESS_ENV: &str = "PGSTREAM_METRICS_ADDR"; + +/// Default metrics listener address: all interfaces, port 9000. +const DEFAULT_METRICS_LISTEN_ADDRESS: SocketAddr = + SocketAddr::new(IpAddr::V6(Ipv6Addr::UNSPECIFIED), 9000); + +/// Errors raised while starting the metrics exporter. +#[derive(Debug, thiserror::Error)] +pub enum MetricsError { + /// `PGSTREAM_METRICS_ADDR` is not a socket address. + #[error( + "{METRICS_LISTEN_ADDRESS_ENV} must be a socket address such as 127.0.0.1:9000 (got {value:?})" + )] + InvalidListenAddress { + value: String, + #[source] + source: AddrParseError, + }, + /// The Prometheus exporter could not be installed. + #[error(transparent)] + Build(#[from] BuildError), +} + +/// Resolves the metrics listener address from an optional override. +pub fn metrics_listen_address(value: Option<&str>) -> Result { + match value { + None => Ok(DEFAULT_METRICS_LISTEN_ADDRESS), + Some(value) => value + .parse() + .map_err(|source| MetricsError::InvalidListenAddress { + value: value.to_owned(), + source, + }), + } +} + +/// Initializes metrics with an automatic HTTP server. /// /// This function is designed for standalone services where metrics should be exposed /// automatically without manual endpoint management. It installs a global metrics recorder -/// and starts an HTTP server that listens on `[::]:9000/metrics`, making metrics available -/// for Prometheus scraping. +/// and starts an HTTP server that listens on `[::]:9000/metrics` by default, or on the +/// address in `PGSTREAM_METRICS_ADDR`, making metrics available for Prometheus scraping. /// /// # Use Case /// @@ -15,11 +54,11 @@ use super::{register_failover_metrics, register_maintenance_metrics, register_st /// - Expose metrics from a standalone service. /// - Automatically start a dedicated metrics endpoint without custom routing. /// - Let Prometheus scrape metrics directly from a fixed port. -pub fn init_metrics() -> Result<(), BuildError> { - let builder = PrometheusBuilder::new().with_http_listener(std::net::SocketAddr::new( - std::net::IpAddr::V6(std::net::Ipv6Addr::UNSPECIFIED), - 9000, - )); +/// - Run several instances on one host network, each on its own port. +pub fn init_metrics() -> Result<(), MetricsError> { + let address = + metrics_listen_address(std::env::var(METRICS_LISTEN_ADDRESS_ENV).ok().as_deref())?; + let builder = PrometheusBuilder::new().with_http_listener(address); builder.install()?; @@ -30,3 +69,30 @@ pub fn init_metrics() -> Result<(), BuildError> { Ok(()) } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn defaults_to_all_interfaces_on_port_9000() { + assert_eq!( + metrics_listen_address(None).unwrap(), + "[::]:9000".parse::().unwrap() + ); + } + + #[test] + fn accepts_an_override() { + assert_eq!( + metrics_listen_address(Some("127.0.0.1:9187")).unwrap(), + "127.0.0.1:9187".parse::().unwrap() + ); + } + + #[test] + fn rejects_an_invalid_override() { + let error = metrics_listen_address(Some("9187")).unwrap_err(); + assert!(error.to_string().contains("PGSTREAM_METRICS_ADDR")); + } +} From de1d6464484bff21d4580ad249bbb50e0a235c82 Mon Sep 17 00:00:00 2001 From: psteinroe Date: Thu, 15 Oct 2026 10:42:56 +0000 Subject: [PATCH 2/2] ci: install taplo from release binaries --- .github/workflows/pull_request.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 41de9f5..912c7bb 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -24,11 +24,15 @@ jobs: uses: moonrepo/setup-rust@v1 with: components: rustfmt - bins: taplo-cli cache-base: main env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Install taplo + uses: taiki-e/install-action@v2 + with: + tool: taplo + - name: Run cargo fmt run: cargo fmt --all --check