#![cfg(feature = "api")]
use std::sync::{Arc, Mutex};
use axum::{
body::Body,
http::{Request, StatusCode},
};
use tower::ServiceExt;
use tracing_subscriber::{EnvFilter, layer::SubscriberExt as _};
use xberg::{ExtractionConfig, api::create_router};
#[derive(Clone, Default)]
struct EventCapture {
events: Arc<Mutex<Vec<CapturedEvent>>>,
}
#[derive(Debug, Clone)]
struct CapturedEvent {
target: String,
}
impl<S> tracing_subscriber::Layer<S> for EventCapture
where
S: tracing::Subscriber,
{
fn on_event(&self, event: &tracing::Event<'_>, _ctx: tracing_subscriber::layer::Context<'_, S>) {
let meta = event.metadata();
self.events.lock().unwrap().push(CapturedEvent {
target: meta.target().to_owned(),
});
}
}
fn captured_events(capture: &EventCapture) -> Vec<CapturedEvent> {
capture.events.lock().unwrap().clone()
}
async fn get_health(router: axum::Router) -> StatusCode {
let response = router
.oneshot(
Request::builder()
.method("GET")
.uri("/health")
.body(Body::empty())
.expect("Failed to build /health request"),
)
.await
.expect("Failed to send /health request");
response.status()
}
#[test]
fn tower_http_trace_events_suppressed_at_info() {
let capture = EventCapture::default();
let capture_clone = capture.clone();
let filter = EnvFilter::new("info,tower_http=info");
let subscriber = tracing_subscriber::registry().with(filter).with(capture_clone);
let status = tracing::subscriber::with_default(subscriber, || {
tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.unwrap()
.block_on(async {
let router = create_router(ExtractionConfig::default());
get_health(router).await
})
});
assert_eq!(status, StatusCode::OK, "/health must return 200");
let events = captured_events(&capture);
let tower_trace_events: Vec<_> = events
.iter()
.filter(|e| e.target.starts_with("tower_http::trace"))
.collect();
assert!(
tower_trace_events.is_empty(),
"expected zero tower_http::trace events at info filter; got: {tower_trace_events:?}"
);
}
#[test]
fn tower_http_trace_events_visible_at_debug() {
let capture = EventCapture::default();
let capture_clone = capture.clone();
let filter = EnvFilter::new("info,tower_http=debug");
let subscriber = tracing_subscriber::registry().with(filter).with(capture_clone);
let status = tracing::subscriber::with_default(subscriber, || {
tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.unwrap()
.block_on(async {
let router = create_router(ExtractionConfig::default());
get_health(router).await
})
});
assert_eq!(status, StatusCode::OK, "/health must return 200");
let events = captured_events(&capture);
let tower_trace_events: Vec<_> = events.iter().filter(|e| e.target.starts_with("tower_http")).collect();
assert!(
!tower_trace_events.is_empty(),
"expected at least one tower_http event at debug filter; got none (total events: {:?})",
events
);
}
#[tokio::test]
async fn health_route_works_without_subscriber() {
let router = create_router(ExtractionConfig::default());
let status = get_health(router).await;
assert_eq!(status, StatusCode::OK);
}