Closed horseinthesky closed 4 months ago
This is more of a question for axum
, but it seems like you could simply merge the two routers, or even add the metrics route directly on the main app's router.
The example appears to launch two servers purely for the purpose of exposing metrics on different ports.
@tobz Could you an example please?
I've tried several approaches but failed every time:
fn setup_metrics_recorder() -> PrometheusHandle {
const EXPONENTIAL_SECONDS: &[f64] = &[0.005, 0.01, 0.025, 0.05, 0.1];
let b = PrometheusBuilder::new()
.set_buckets_for_metric(
Matcher::Full("myapp_request_duration_seconds".to_string()),
EXPONENTIAL_SECONDS,
)
.unwrap()
.install_recorder();
// .expect("failed to install recorder");
match b {
Ok(handle) => handle,
Err(e) => panic!("{e}"),
}
}
#[tokio::main]
async fn main() -> Result<(), axum::BoxError> {
let app = Router::new()
.route("/api/devices", get(devices))
.route("/api/images", get(images))
.layer(
TraceLayer::new_for_http().make_span_with(
|request: &Request<_>| {
let name =
format!("{} {}", request.method(), request.uri());
tracing::debug_span!(
"request",
otel.name = name,
method = %request.method(),
uri = %request.uri(),
headers = ?request.headers(),
version = ?request.version(),
)
},
),
)
.route("/health", get(health))
.route(
"/metrics",
get(|| ready(setup_metrics_recorder().render())),
);
let port = std::env::var("PORT").unwrap_or(DEFAULT_PORT.to_string());
let host = std::env::var("PORT").unwrap_or(DEFAULT_HOST.to_string());
let listener =
tokio::net::TcpListener::bind(format!("{}:{}", host, port)).await?;
axum::serve(listener, app).await?;
Ok(())
}
panics with:
thread 'tokio-runtime-worker' panicked at src/main.rs:43:19:
failed to install exporter as global recorder: attempted to set a recorder after the metrics system was already initialized
You're installing the global recorder every time the /metrics
route is hit. Don't do that.
Do it once, outside of the router, and pass the PrometheusHandle
as state to the route handler so it can call render()
.
@tobz Thanks. Everytihng works great
#[tokio::main]
async fn main() -> Result<(), axum::BoxError> {
let metric_handle = setup_metrics_recorder();
let app = Router::new()
.route("/api/devices", get(devices))
.route("/api/images", get(images))
.layer(
TraceLayer::new_for_http().make_span_with(
|request: &Request<_>| {
let name =
format!("{} {}", request.method(), request.uri());
tracing::debug_span!(
"request",
otel.name = name,
method = %request.method(),
uri = %request.uri(),
headers = ?request.headers(),
version = ?request.version(),
)
},
),
)
.route("/health", get(health))
.route(
"/metrics",
get(|| async move { metric_handle.render() }),
);
let port = std::env::var("PORT").unwrap_or(DEFAULT_PORT.to_string());
let host = std::env::var("PORT").unwrap_or(DEFAULT_HOST.to_string());
let listener =
tokio::net::TcpListener::bind(format!("{}:{}", host, port)).await?;
axum::serve(listener, app).await?;
Ok(())
}
Nice, glad you were able to get it working. 👍🏻
Hello.
I'm looking for an example of instrumenting axum app with a
/metrics
endpoint. The only example I see is https://github.com/tokio-rs/axum/blob/d703e6f97a0156177466b6741be0beac0c83d8c7/examples/prometheus-metrics/src/main.rs but it creates a very generic middleware for every handler of the app while launching a separate axum app with the/metrics
endpoint.Is there a way to add
/metrics
handler to the main app and provide some metrics to it.Here is a toy example: main.rs
images.rs I'm trying to add additional metrics to measure some parts of the handler
Thank you