aster_forge_observability/actix/
mod.rs1#[cfg(feature = "prometheus")]
4use actix_web::{HttpRequest, HttpResponse, Resource, http::header, web};
5
6#[cfg(feature = "prometheus")]
9pub fn prometheus_resource(path: &str) -> Resource {
10 web::resource(path).route(web::get().to(prometheus_metrics))
11}
12
13#[cfg(feature = "prometheus")]
14async fn prometheus_metrics(request: HttpRequest) -> HttpResponse {
15 let Some(provider) = request.app_data::<web::Data<aster_forge_metrics::MetricsProvider>>()
16 else {
17 return HttpResponse::ServiceUnavailable()
18 .insert_header((header::CACHE_CONTROL, "no-store"))
19 .body("metrics provider is unavailable");
20 };
21 match provider
22 .export_prometheus(std::time::Duration::from_secs(5))
23 .await
24 {
25 Ok(body) => HttpResponse::Ok()
26 .insert_header((header::CACHE_CONTROL, "no-store"))
27 .content_type("text/plain; version=0.0.4; charset=utf-8")
28 .body(body),
29 Err(_) => HttpResponse::ServiceUnavailable()
30 .insert_header((header::CACHE_CONTROL, "no-store"))
31 .body("metrics export is unavailable"),
32 }
33}
34
35#[cfg(all(test, feature = "prometheus"))]
36mod tests {
37 use super::*;
38 use actix_web::{App, http::StatusCode, test};
39 use aster_forge_metrics::{MetricDescriptor, MetricsProvider, ProductCounter};
40
41 #[actix_web::test]
42 async fn explicit_instance_injection_preserves_status_format_and_no_store() {
43 let app = test::init_service(App::new().service(prometheus_resource("/metrics"))).await;
44 let response =
45 test::call_service(&app, test::TestRequest::get().uri("/metrics").to_request()).await;
46 assert_eq!(response.status(), StatusCode::SERVICE_UNAVAILABLE);
47 assert_eq!(
48 response.headers().get(header::CACHE_CONTROL).unwrap(),
49 "no-store"
50 );
51
52 let provider = MetricsProvider::builder()
53 .with_prometheus()
54 .unwrap()
55 .build()
56 .unwrap();
57 let handle = provider
58 .register_batch(&[MetricDescriptor::counter(
59 "test",
60 "requests_total",
61 "Requests.",
62 &[],
63 )])
64 .unwrap()
65 .remove(0);
66 ProductCounter::from_handle(handle).unwrap().inc(&[], 2);
67 let app = test::init_service(
68 App::new()
69 .app_data(web::Data::new(provider.clone()))
70 .service(prometheus_resource("/custom")),
71 )
72 .await;
73 let response =
74 test::call_service(&app, test::TestRequest::get().uri("/custom").to_request()).await;
75 assert_eq!(response.status(), StatusCode::OK);
76 assert_eq!(
77 response.headers().get(header::CONTENT_TYPE).unwrap(),
78 "text/plain; version=0.0.4; charset=utf-8"
79 );
80 assert_eq!(
81 response.headers().get(header::CACHE_CONTROL).unwrap(),
82 "no-store"
83 );
84 assert!(
85 String::from_utf8(test::read_body(response).await.to_vec())
86 .unwrap()
87 .contains("test_requests_total 2")
88 );
89 provider
90 .shutdown(std::time::Duration::from_secs(1))
91 .await
92 .unwrap();
93 let response =
94 test::call_service(&app, test::TestRequest::get().uri("/custom").to_request()).await;
95 assert_eq!(response.status(), StatusCode::SERVICE_UNAVAILABLE);
96 }
97}