Axum으로 Prometheus 메트릭 내보내기
가시성(Observability)은 정말 중요해요! 저는 보통 Rust에서 HTTP 프레임워크로 Rust의 Axum을 사용해요. 꽤 쓰기 편하고 빠르거든요.
tower-http는 여러 프로젝트에서 쓰이는 유용한 HTTP 미들웨어들을 제공해요. 현재는 메트릭 미들웨어를 제공하지 않지만, 언젠가는 제공하게 될지도 몰라요!
이슈에서 추적할 수 있어요
이 과정을 자동으로 처리해주는 크레이트도 꽤 많지만, Axum 예제에서는 metrics를 사용하라고 권장하고 있어요. 솔직히 저는 그렇게 복잡한 건 필요하지 않고, 대부분 그냥 카운터 같은 게 포함된 /metrics 엔드포인트만 있으면 충분해서 — 그래서 metrics를 쓰기로 했어요!
어쨌든 먼저 prometheus exporter를 설정해야 해요. 이게 바로 /metrics의 내용을 생성하는 부분이에요. metrics-exporter-prometheus 크레이트를 사용해요. 전역 recorder는 실행 파일에서만 설정하는 게 좋아요. 라이브러리라면 이 부분은 사용자에게 맡기면 돼요.
이 코드는 위에 링크한 Axum 예제에서 거의 그대로 가져왔어요 😇
fn setup_metrics_recorder() -> PrometheusHandle {
const EXPONENTIAL_SECONDS: &[f64] = &[
0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0,
];
PrometheusBuilder::new()
.set_buckets_for_metric(
Matcher::Full("http_requests_duration_seconds".to_string()),
EXPONENTIAL_SECONDS,
)
.unwrap()
.install_recorder()
.unwrap()
}사실 오늘은 두 개의 메트릭을 설정하는데, 그중 history_requests_duration_seconds만 별도 설정이 필요해요. 히스토그램이라 exporter에게 데이터를 어떻게 버킷으로 나눌지 알려줘야 하기 때문이에요.
이 설정이 끝나면 Axum 미들웨어를 작성할 수 있어요! (예제에서 가져와 제대로 컴파일되도록 수정했어요. PR을 올려둘게요)
/// Middleware to record some common HTTP metrics
/// Generic over B to allow for arbitrary body types (eg Vec<u8>, Streams, a deserialized thing, etc)
/// Someday tower-http might provide a metrics middleware: https://github.com/tower-rs/tower-http/issues/57
pub async fn track_metrics<B>(req: Request<B>, next: Next<B>) -> impl IntoResponse {
let start = Instant::now();
let path = if let Some(matched_path) = req.extensions().get::<MatchedPath>() {
matched_path.as_str().to_owned()
} else {
req.uri().path().to_owned()
};
let method = req.method().clone();
// Run the rest of the request handling first, so we can measure it and get response
// codes.
let response = next.run(req).await;
let latency = start.elapsed().as_secs_f64();
let status = response.status().as_u16().to_string();
let labels = [
("method", method.to_string()),
("path", path),
("status", status),
];
metrics::increment_counter!("http_requests_total", &labels);
metrics::histogram!("http_requests_duration_seconds", latency, &labels);
response
}그런 다음 Axum 라우터를 설정하는 곳에서 /metrics 라우트를 연결해주면 돼요! 외부에 공개되지 않도록 꼭 확인해 주세요.
여기서
std::future::ready에 대해 알게 됐어요! 이 함수는 값을 담아 즉시 사용할 수 있는 future를 만들어 줘요. 예를 들면:let f = std::future::ready(1); assert_eq!(a.await, 1);
let recorder_handle = setup_metrics_recorder();
let router = Router::new()
.route("/metrics", get(move || ready(recorder_handle.render())))
.layer(axum::middleware::from_fn(track_metrics));정말 이게 다예요!
글을 무작위로 읽기