Skip to content

Commit 5e9f6d4

Browse files
committed
metrics-exporter-prometheus: Add API to initialize HTTP listener from TCP listener
This commit adds a new API to the `PrometheusBuilder` that allows users to provide their own configured TCP listener. The use case is to enable users to configure the TCP listener before passing it to the exporter - for example, to set socket options or bind to port 0 and retrieve the port number assigned by the OS. Signed-off-by: Michel Heily <michel.heily@wiz.io>
1 parent 6a3e14e commit 5e9f6d4

File tree

3 files changed

+47
-21
lines changed

3 files changed

+47
-21
lines changed

metrics-exporter-prometheus/src/exporter/builder.rs

Lines changed: 38 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,25 @@ impl PrometheusBuilder {
105105
self
106106
}
107107

108+
/// Same as `with_http_listener`, but allows to bring your own configured tcp listener.
109+
#[cfg(feature = "http-listener")]
110+
#[cfg_attr(docsrs, doc(cfg(feature = "http-listener")))]
111+
#[must_use]
112+
pub fn with_http_listener_from_existing_listener(
113+
mut self,
114+
listener: tokio::net::TcpListener,
115+
) -> Self {
116+
use std::sync::Arc;
117+
118+
// We need to wrap the listener in an Arc to allow for cloning.
119+
let listener = Arc::new(listener);
120+
121+
self.exporter_config = ExporterConfig::HttpListener {
122+
destination: super::ListenDestination::ExistingListener(listener),
123+
};
124+
self
125+
}
126+
108127
/// Configures the exporter to push periodic requests to a Prometheus [push gateway].
109128
///
110129
/// Running in push gateway mode is mutually exclusive with the HTTP listener i.e. enabling the push gateway will
@@ -485,11 +504,25 @@ impl PrometheusBuilder {
485504
#[cfg(feature = "http-listener")]
486505
ExporterConfig::HttpListener { destination } => match destination {
487506
super::ListenDestination::Tcp(listen_address) => {
488-
super::http_listener::new_http_listener(
489-
handle,
490-
listen_address,
491-
allowed_addresses,
492-
)?
507+
let listener = std::net::TcpListener::bind(listen_address)
508+
.and_then(|listener| {
509+
listener.set_nonblocking(true)?;
510+
Ok(listener)
511+
})
512+
.map_err(|e| BuildError::FailedToCreateHTTPListener(e.to_string()))?;
513+
let listener = tokio::net::TcpListener::from_std(listener).unwrap();
514+
super::http_listener::new_http_listener(handle, listener, allowed_addresses)
515+
}
516+
super::ListenDestination::ExistingListener(listener) => {
517+
use std::sync::Arc;
518+
519+
// Should always succeed as we only created the Arc so the TcpListener can be stored in a Clone
520+
let listener = Arc::try_unwrap(listener).map_err(|_| {
521+
BuildError::FailedToCreateHTTPListener(
522+
"Failed to unwrap Arc<TcpListener>".to_string(),
523+
)
524+
})?;
525+
super::http_listener::new_http_listener(handle, listener, allowed_addresses)
493526
}
494527
#[cfg(feature = "uds-listener")]
495528
super::ListenDestination::Uds(listen_path) => {

metrics-exporter-prometheus/src/exporter/http_listener.rs

Lines changed: 4 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
use std::net::SocketAddr;
2-
31
use http_body_util::Full;
42
use hyper::{
53
body::{Bytes, Incoming},
@@ -17,7 +15,7 @@ use tokio::net::{TcpListener, TcpStream};
1715
use tokio::net::{UnixListener, UnixStream};
1816
use tracing::warn;
1917

20-
use crate::{common::BuildError, ExporterFuture, PrometheusHandle};
18+
use crate::{ExporterFuture, PrometheusHandle};
2119

2220
struct HttpListeningExporter {
2321
handle: PrometheusHandle,
@@ -154,24 +152,16 @@ impl HttpListeningExporter {
154152
/// Will return Err if it cannot bind to the listen address
155153
pub(crate) fn new_http_listener(
156154
handle: PrometheusHandle,
157-
listen_address: SocketAddr,
155+
listener: TcpListener,
158156
allowed_addresses: Option<Vec<IpNet>>,
159-
) -> Result<ExporterFuture, BuildError> {
160-
let listener = std::net::TcpListener::bind(listen_address)
161-
.and_then(|listener| {
162-
listener.set_nonblocking(true)?;
163-
Ok(listener)
164-
})
165-
.map_err(|e| BuildError::FailedToCreateHTTPListener(e.to_string()))?;
166-
let listener = TcpListener::from_std(listener).unwrap();
167-
157+
) -> ExporterFuture {
168158
let exporter = HttpListeningExporter {
169159
handle,
170160
allowed_addresses,
171161
listener_type: ListenerType::Tcp(listener),
172162
};
173163

174-
Ok(Box::pin(async move { exporter.serve().await.map_err(super::ExporterError::HttpListener) }))
164+
Box::pin(async move { exporter.serve().await.map_err(super::ExporterError::HttpListener) })
175165
}
176166

177167
/// Creates an `ExporterFuture` implementing a http listener that serves prometheus metrics.

metrics-exporter-prometheus/src/exporter/mod.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,14 @@
22
use http_listener::HttpListeningError;
33
#[cfg(any(feature = "http-listener", feature = "push-gateway"))]
44
use std::future::Future;
5-
#[cfg(feature = "http-listener")]
6-
use std::net::SocketAddr;
75
#[cfg(any(feature = "http-listener", feature = "push-gateway"))]
86
use std::pin::Pin;
97
#[cfg(feature = "push-gateway")]
108
use std::time::Duration;
9+
#[cfg(feature = "http-listener")]
10+
use std::{net::SocketAddr, sync::Arc};
11+
#[cfg(feature = "http-listener")]
12+
use tokio::net::TcpListener;
1113

1214
#[cfg(feature = "push-gateway")]
1315
use hyper::Uri;
@@ -28,6 +30,7 @@ pub type ExporterFuture = Pin<Box<dyn Future<Output = Result<(), ExporterError>>
2830
#[derive(Clone, Debug)]
2931
enum ListenDestination {
3032
Tcp(SocketAddr),
33+
ExistingListener(Arc<TcpListener>),
3134
#[cfg(feature = "uds-listener")]
3235
Uds(std::path::PathBuf),
3336
}

0 commit comments

Comments
 (0)