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 addr = addr,
95 err = err,
96 );
97 }
98 };
99 info!(
100 "Opened tracing endpoint at {}",
101 listener
102 .local_addr()
103 .expect("Local address must be available as the bind was successful")
104 );
105
106 while let Ok((stream, _)) = listener.accept().await {
107 let io = TokioIo::new(stream);
108 tokio::spawn(async move {
109 if let Err(err) = Builder::new(TokioExecutor::new())
110 .serve_connection(io, svc)
111 .await
112 {
113 error!(
114 "Serve connection in {addr:?} failed: {err:?}.",
115 addr = addr,
116 err = err
117 );
118 }
119 });
120 }
121 });
122
123 Ok(())
124 }
125}
126
127#[cfg(feature = "filter-reload")]
128#[instrument]
129async fn request_handler(req: Request<Incoming>) -> Result<Response<String>, hyper::Error> {
130 use super::Tracing;
131
132 let rsp = match (req.method(), req.uri().path()) {
133 (&Method::GET, "/") => Response::new(
134 r#"
135This HTTP endpoint allows dynamic control of the filter applied to
136tracing events.
137
138To get the current filter, GET /filter:
139
140 curl -X GET localhost:3000/filter
141
142To set the filter, POST the new filter string to /filter:
143
144 curl -X POST localhost:3000/filter -d "zebrad=trace"
145"#
146 .to_string(),
147 ),
148 (&Method::GET, "/filter") => Response::builder()
149 .status(StatusCode::OK)
150 .body(
151 APPLICATION
152 .state()
153 .components()
154 .get_downcast_ref::<Tracing>()
155 .expect("Tracing component should be available")
156 .filter(),
157 )
158 .expect("response with known status code cannot fail"),
159 (&Method::POST, "/filter") => match read_filter(req).await {
160 Ok(filter) => {
161 APPLICATION
162 .state()
163 .components()
164 .get_downcast_ref::<Tracing>()
165 .expect("Tracing component should be available")
166 .reload_filter(filter);
167
168 Response::new("".to_string())
169 }
170 Err(e) => Response::builder()
171 .status(StatusCode::BAD_REQUEST)
172 .body(e)
173 .expect("response with known status code cannot fail"),
174 },
175 _ => Response::builder()
176 .status(StatusCode::NOT_FOUND)
177 .body("".to_string())
178 .expect("response with known status cannot fail"),
179 };
180 Ok(rsp)
181}