zebra/zebrad/src/components/tracing/endpoint.rs

154 lines
4.8 KiB
Rust

//! An HTTP endpoint for dynamically setting tracing filters.
use std::net::SocketAddr;
use abscissa_core::{Component, FrameworkError};
use crate::config::ZebradConfig;
#[cfg(feature = "filter-reload")]
use hyper::{Body, Request, Response};
#[cfg(feature = "filter-reload")]
use crate::{components::tokio::TokioComponent, prelude::*};
/// Abscissa component which runs a tracing filter endpoint.
#[derive(Debug, Component)]
#[cfg_attr(
feature = "filter-reload",
component(inject = "init_tokio(zebrad::components::tokio::TokioComponent)")
)]
pub struct TracingEndpoint {
#[allow(dead_code)]
addr: Option<SocketAddr>,
}
#[cfg(feature = "filter-reload")]
async fn read_filter(req: Request<Body>) -> Result<String, String> {
std::str::from_utf8(
&hyper::body::to_bytes(req.into_body())
.await
.map_err(|_| "Error reading body".to_owned())?,
)
.map(|s| s.to_owned())
.map_err(|_| "Filter must be UTF-8".to_owned())
}
impl TracingEndpoint {
/// Create the component.
pub fn new(config: &ZebradConfig) -> Result<Self, FrameworkError> {
if config.tracing.endpoint_addr.is_some() && !cfg!(feature = "filter-reload") {
warn!(addr = ?config.tracing.endpoint_addr,
"unable to activate configured tracing filter endpoint: \
enable the 'filter-reload' feature when compiling zebrad",
);
}
Ok(Self {
addr: config.tracing.endpoint_addr,
})
}
#[cfg(feature = "filter-reload")]
#[allow(clippy::unwrap_in_result)]
pub fn init_tokio(&mut self, tokio_component: &TokioComponent) -> Result<(), FrameworkError> {
use hyper::{
service::{make_service_fn, service_fn},
Server,
};
let addr = if let Some(addr) = self.addr {
addr
} else {
return Ok(());
};
let service =
make_service_fn(|_| async { Ok::<_, hyper::Error>(service_fn(request_handler)) });
info!("Trying to open tracing endpoint at {}...", addr);
tokio_component
.rt
.as_ref()
.expect("runtime should not be taken")
.spawn(async move {
// try_bind uses the tokio runtime, so we
// need to construct it inside the task.
let server = match Server::try_bind(&addr) {
Ok(s) => s,
Err(e) => panic!(
"Opening tracing endpoint listener {addr:?} failed: {e:?}. \
Hint: Check if another zebrad or zcashd process is running. \
Try changing the tracing endpoint_addr in the Zebra config.",
),
}
.serve(service);
info!("Opened tracing endpoint at {}", server.local_addr());
if let Err(e) = server.await {
error!("Server error: {}", e);
}
});
Ok(())
}
}
#[cfg(feature = "filter-reload")]
#[instrument]
async fn request_handler(req: Request<Body>) -> Result<Response<Body>, hyper::Error> {
use hyper::{Method, StatusCode};
use super::Tracing;
let rsp = match (req.method(), req.uri().path()) {
(&Method::GET, "/") => Response::new(Body::from(
r#"
This HTTP endpoint allows dynamic control of the filter applied to
tracing events.
To get the current filter, GET /filter:
curl -X GET localhost:3000/filter
To set the filter, POST the new filter string to /filter:
curl -X POST localhost:3000/filter -d "zebrad=trace"
"#,
)),
(&Method::GET, "/filter") => Response::builder()
.status(StatusCode::OK)
.body(Body::from(
APPLICATION
.state()
.components()
.get_downcast_ref::<Tracing>()
.expect("Tracing component should be available")
.filter(),
))
.expect("response with known status code cannot fail"),
(&Method::POST, "/filter") => match read_filter(req).await {
Ok(filter) => {
APPLICATION
.state()
.components()
.get_downcast_ref::<Tracing>()
.expect("Tracing component should be available")
.reload_filter(filter);
Response::new(Body::from(""))
}
Err(e) => Response::builder()
.status(StatusCode::BAD_REQUEST)
.body(Body::from(e))
.expect("response with known status code cannot fail"),
},
_ => Response::builder()
.status(StatusCode::NOT_FOUND)
.body(Body::from(""))
.expect("response with known status cannot fail"),
};
Ok(rsp)
}