1
0
mirror of https://github.com/matrix-org/matrix-authentication-service.git synced 2025-08-06 06:02:40 +03:00

Export Prometheus metrics on regular listeners

This commit is contained in:
Quentin Gliech
2022-10-04 10:52:52 +02:00
parent 84ac87f551
commit 014a8366ed
6 changed files with 101 additions and 83 deletions

View File

@@ -15,6 +15,7 @@
use std::time::Duration;
use anyhow::{bail, Context as _};
use hyper::{header::CONTENT_TYPE, Body, Response};
use mas_config::{
JaegerExporterProtocolConfig, MetricsExporterConfig, Propagator, TelemetryConfig,
TracingExporterConfig,
@@ -33,13 +34,18 @@ use opentelemetry::{
};
#[cfg(feature = "jaeger")]
use opentelemetry_jaeger::Propagator as JaegerPropagator;
#[cfg(feature = "prometheus")]
use opentelemetry_prometheus::PrometheusExporter;
use opentelemetry_semantic_conventions as semcov;
#[cfg(feature = "zipkin")]
use opentelemetry_zipkin::{B3Encoding, Propagator as ZipkinPropagator};
use tokio::sync::OnceCell;
use url::Url;
static METRICS_BASIC_CONTROLLER: OnceCell<Option<BasicController>> = OnceCell::const_new();
static METRICS_BASIC_CONTROLLER: OnceCell<BasicController> = OnceCell::const_new();
#[cfg(feature = "prometheus")]
static PROMETHEUS_EXPORTER: OnceCell<PrometheusExporter> = OnceCell::const_new();
pub async fn setup(
config: &TelemetryConfig,
@@ -55,8 +61,11 @@ pub async fn setup(
let tracer = tracer(&config.tracing.exporter)
.await
.context("Failed to configure traces exporter")?;
let meter = meter(&config.metrics.exporter).context("Failed to configure metrics exporter")?;
METRICS_BASIC_CONTROLLER.set(meter.clone())?;
if let Some(meter) = meter.as_ref() {
METRICS_BASIC_CONTROLLER.set(meter.clone())?;
}
Ok((tracer, meter))
}
@@ -64,7 +73,7 @@ pub async fn setup(
pub fn shutdown() {
global::shutdown_tracer_provider();
if let Some(Some(controller)) = METRICS_BASIC_CONTROLLER.get() {
if let Some(controller) = METRICS_BASIC_CONTROLLER.get() {
let cx = Context::new();
controller.stop(&cx).unwrap();
}
@@ -103,7 +112,7 @@ fn propagator(propagators: &[Propagator]) -> anyhow::Result<impl TextMapPropagat
Ok(TextMapCompositePropagator::new(propagators?))
}
#[cfg(any(feature = "otlp", feature = "jaeger"))]
#[cfg(any(feature = "zipkin", feature = "jaeger"))]
async fn http_client() -> anyhow::Result<impl opentelemetry_http::HttpClient + 'static> {
let client = mas_http::make_untraced_client()
.await
@@ -303,21 +312,67 @@ fn stdout_meter() -> anyhow::Result<BasicController> {
}
#[cfg(not(feature = "prometheus"))]
fn prometheus_meter(address: &str) -> anyhow::Result<BasicController> {
let _ = address;
pub fn prometheus_service<T>() -> tower::util::ServiceFn<
impl FnMut(T) -> std::future::Ready<Result<Response<Body>, std::convert::Infallible>> + Clone,
> {
tracing::warn!("Prometheus exporter was not enabled at compilation time, but the Prometheus resource was mounted on a listener");
tower::service_fn(move |_req| {
let response = Response::builder()
.status(500)
.header(CONTENT_TYPE, "text/plain")
.body(Body::from(
"Prometheus exporter was not enabled at compilation time",
))
.unwrap();
std::future::ready(Ok(response))
})
}
#[cfg(feature = "prometheus")]
pub fn prometheus_service<T>() -> tower::util::ServiceFn<
impl FnMut(T) -> std::future::Ready<Result<Response<Body>, std::convert::Infallible>> + Clone,
> {
use prometheus::{Encoder, TextEncoder};
if !PROMETHEUS_EXPORTER.initialized() {
tracing::warn!("A Prometheus resource was mounted on a listener, but the Prometheus exporter was not setup in the config");
}
tower::service_fn(move |_req| {
let response = if let Some(exporter) = PROMETHEUS_EXPORTER.get() {
let mut buffer = vec![];
let encoder = TextEncoder::new();
let metric_families = exporter.registry().gather();
// That shouldn't panic, unless we're constructing invalid labels
encoder.encode(&metric_families, &mut buffer).unwrap();
Response::builder()
.status(200)
.header(CONTENT_TYPE, encoder.format_type())
.body(Body::from(buffer))
.unwrap()
} else {
Response::builder()
.status(500)
.header(CONTENT_TYPE, "text/plain")
.body(Body::from("Prometheus exporter was not enabled in config"))
.unwrap()
};
std::future::ready(Ok(response))
})
}
#[cfg(not(feature = "prometheus"))]
fn prometheus_meter() -> anyhow::Result<BasicController> {
anyhow::bail!("The service was compiled without Prometheus exporter support, but config exports metrics via Prometheus.")
}
#[cfg(feature = "prometheus")]
fn prometheus_meter(address: &str) -> anyhow::Result<BasicController> {
use std::{
convert::Infallible,
net::{SocketAddr, TcpListener},
};
use hyper::{header::CONTENT_TYPE, service::make_service_fn, Body, Method, Request, Response};
use prometheus::{Encoder, TextEncoder};
fn prometheus_meter() -> anyhow::Result<BasicController> {
let controller = sdk::metrics::controllers::basic(
sdk::metrics::processors::factory(
sdk::metrics::selectors::simple::histogram([
@@ -331,52 +386,7 @@ fn prometheus_meter(address: &str) -> anyhow::Result<BasicController> {
.build();
let exporter = opentelemetry_prometheus::exporter(controller.clone()).try_init()?;
let make_svc = make_service_fn(move |_conn| {
let exporter = exporter.clone();
async move {
Ok::<_, Infallible>(tower::service_fn(move |req: Request<Body>| {
let exporter = exporter.clone();
async move {
let response = match (req.method(), req.uri().path()) {
(&Method::GET, "/metrics") => {
let mut buffer = vec![];
let encoder = TextEncoder::new();
let metric_families = exporter.registry().gather();
encoder.encode(&metric_families, &mut buffer).unwrap();
Response::builder()
.status(200)
.header(CONTENT_TYPE, encoder.format_type())
.body(Body::from(buffer))
.unwrap()
}
_ => Response::builder()
.status(404)
.body(Body::from("Metrics are exposed on /metrics"))
.unwrap(),
};
Ok::<_, Infallible>(response)
}
}))
}
});
let address: SocketAddr = address
.parse()
.context("could not parse listener address")?;
let listener = TcpListener::bind(address).context("could not bind address")?;
tracing::info!(
"Prometheus exporter listening on on http://{}/metrics",
listener.local_addr().unwrap()
);
let server = hyper::server::Server::from_tcp(listener)
.context("Failed to start HTTP server for the Prometheus metrics exporter")?
.serve(make_svc);
tokio::spawn(server);
PROMETHEUS_EXPORTER.set(exporter)?;
Ok(controller)
}
@@ -386,7 +396,7 @@ fn meter(config: &MetricsExporterConfig) -> anyhow::Result<Option<BasicControlle
MetricsExporterConfig::None => None,
MetricsExporterConfig::Stdout => Some(stdout_meter()?),
MetricsExporterConfig::Otlp { endpoint } => Some(otlp_meter(endpoint)?),
MetricsExporterConfig::Prometheus { address } => Some(prometheus_meter(address)?),
MetricsExporterConfig::Prometheus => Some(prometheus_meter()?),
};
Ok(controller)