zebrad/components/tracing/
endpoint.rs
1#![allow(non_local_definitions)]
4
5use std::net::SocketAddr;
6
7use abscissa_core::{Component, FrameworkError};
8
9use crate::config::ZebradConfig;
10
11#[cfg(feature = "filter-reload")]
12use hyper::{
13 body::{Body, Incoming},
14 Method, Request, Response, StatusCode,
15};
16#[cfg(feature = "filter-reload")]
17use hyper_util::{
18 rt::{TokioExecutor, TokioIo},
19 server::conn::auto::Builder,
20};
21
22#[cfg(feature = "filter-reload")]
23use crate::{components::tokio::TokioComponent, prelude::*};
24
25#[derive(Debug, Component)]
27#[cfg_attr(
28 feature = "filter-reload",
29 component(inject = "init_tokio(zebrad::components::tokio::TokioComponent)")
30)]
31pub struct TracingEndpoint {
32 #[allow(dead_code)]
33 addr: Option<SocketAddr>,
34}
35
36#[cfg(feature = "filter-reload")]
37async fn read_filter(req: Request<impl Body>) -> Result<String, String> {
38 use http_body_util::BodyExt;
39
40 std::str::from_utf8(
41 req.into_body()
42 .collect()
43 .await
44 .map_err(|_| "Error reading body".to_owned())?
45 .to_bytes()
46 .as_ref(),
47 )
48 .map(|s| s.to_owned())
49 .map_err(|_| "Filter must be UTF-8".to_owned())
50}
51
52impl TracingEndpoint {
53 pub fn new(config: &ZebradConfig) -> Result<Self, FrameworkError> {
55 if config.tracing.endpoint_addr.is_some() && !cfg!(feature = "filter-reload") {
56 warn!(addr = ?config.tracing.endpoint_addr,
57 "unable to activate configured tracing filter endpoint: \
58 enable the 'filter-reload' feature when compiling zebrad",
59 );
60 }
61
62 Ok(Self {
63 addr: config.tracing.endpoint_addr,
64 })
65 }
66
67 #[cfg(feature = "filter-reload")]
68 #[allow(clippy::unwrap_in_result)]
69 pub fn init_tokio(&mut self, tokio_component: &TokioComponent) -> Result<(), FrameworkError> {
70 let addr = if let Some(addr) = self.addr {
71 addr
72 } else {
73 return Ok(());
74 };
75
76 info!("Trying to open tracing endpoint at {}...", addr);
77
78 let svc = hyper::service::service_fn(|req: Request<Incoming>| async move {
79 request_handler(req).await
80 });
81
82 tokio_component
83 .rt
84 .as_ref()
85 .expect("runtime should not be taken")
86 .spawn(async move {
87 let listener = match tokio::net::TcpListener::bind(addr).await {
88 Ok(listener) => listener,
89 Err(err) => {
90 panic!(
91 "Opening tracing endpoint listener {addr:?} failed: {err:?}. \
92 Hint: Check if another zebrad or zcashd process is running. \
93 Try changing the tracing endpoint_addr in the Zebra config.",
94 );
95 }
96 };
97 info!(
98 "Opened tracing endpoint at {}",
99 listener
100 .local_addr()
101 .expect("Local address must be available as the bind was successful")
102 );
103
104 while let Ok((stream, _)) = listener.accept().await {
105 let io = TokioIo::new(stream);
106 tokio::spawn(async move {
107 if let Err(err) = Builder::new(TokioExecutor::new())
108 .serve_connection(io, svc)
109 .await
110 {
111 error!(
112 "Serve connection in {addr:?} failed: {err:?}.",
113 addr = addr,
114 err = err
115 );
116 }
117 });
118 }
119 });
120
121 Ok(())
122 }
123}
124
125#[cfg(feature = "filter-reload")]
126#[instrument]
127async fn request_handler(req: Request<Incoming>) -> Result<Response<String>, hyper::Error> {
128 use super::Tracing;
129
130 let rsp = match (req.method(), req.uri().path()) {
131 (&Method::GET, "/") => Response::new(
132 r#"
133This HTTP endpoint allows dynamic control of the filter applied to
134tracing events.
135
136To get the current filter, GET /filter:
137
138 curl -X GET localhost:3000/filter
139
140To set the filter, POST the new filter string to /filter:
141
142 curl -X POST localhost:3000/filter -d "zebrad=trace"
143"#
144 .to_string(),
145 ),
146 (&Method::GET, "/filter") => Response::builder()
147 .status(StatusCode::OK)
148 .body(
149 APPLICATION
150 .state()
151 .components()
152 .get_downcast_ref::<Tracing>()
153 .expect("Tracing component should be available")
154 .filter(),
155 )
156 .expect("response with known status code cannot fail"),
157 (&Method::POST, "/filter") => match read_filter(req).await {
158 Ok(filter) => {
159 APPLICATION
160 .state()
161 .components()
162 .get_downcast_ref::<Tracing>()
163 .expect("Tracing component should be available")
164 .reload_filter(filter);
165
166 Response::new("".to_string())
167 }
168 Err(e) => Response::builder()
169 .status(StatusCode::BAD_REQUEST)
170 .body(e)
171 .expect("response with known status code cannot fail"),
172 },
173 _ => Response::builder()
174 .status(StatusCode::NOT_FOUND)
175 .body("".to_string())
176 .expect("response with known status cannot fail"),
177 };
178 Ok(rsp)
179}