zebrad/components/
tracing.rs

1//! Tracing and logging infrastructure for Zebra.
2
3use std::{
4    net::SocketAddr,
5    ops::{Deref, DerefMut},
6    path::PathBuf,
7};
8
9use serde::{Deserialize, Serialize};
10
11mod component;
12mod endpoint;
13
14#[cfg(feature = "flamegraph")]
15mod flame;
16
17pub use component::Tracing;
18pub use endpoint::TracingEndpoint;
19
20#[cfg(feature = "flamegraph")]
21pub use flame::{layer, Grapher};
22
23/// Tracing configuration section: outer config after cross-field defaults are applied.
24///
25/// This is a wrapper type that dereferences to the inner config type.
26///
27//
28// TODO: replace with serde's finalizer attribute when that feature is implemented.
29//       we currently use the recommended workaround of a wrapper struct with from/into attributes.
30//       https://github.com/serde-rs/serde/issues/642#issuecomment-525432907
31#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)]
32#[serde(
33    deny_unknown_fields,
34    default,
35    from = "InnerConfig",
36    into = "InnerConfig"
37)]
38pub struct Config {
39    inner: InnerConfig,
40}
41
42impl Deref for Config {
43    type Target = InnerConfig;
44
45    fn deref(&self) -> &Self::Target {
46        &self.inner
47    }
48}
49
50impl DerefMut for Config {
51    fn deref_mut(&mut self) -> &mut Self::Target {
52        &mut self.inner
53    }
54}
55
56impl From<InnerConfig> for Config {
57    fn from(mut inner: InnerConfig) -> Self {
58        inner.log_file = runtime_default_log_file(inner.log_file, inner.progress_bar);
59
60        Self { inner }
61    }
62}
63
64impl From<Config> for InnerConfig {
65    fn from(mut config: Config) -> Self {
66        config.log_file = disk_default_log_file(config.log_file.clone(), config.progress_bar);
67
68        config.inner
69    }
70}
71
72/// Tracing configuration section: inner config used to deserialize and apply cross-field defaults.
73#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
74#[serde(deny_unknown_fields, default)]
75pub struct InnerConfig {
76    /// Whether to use colored terminal output, if available.
77    ///
78    /// Colored terminal output is automatically disabled if an output stream
79    /// is connected to a file. (Or another non-terminal device.)
80    ///
81    /// Defaults to `true`, which automatically enables colored output to
82    /// terminals.
83    pub use_color: bool,
84
85    /// Whether to force the use of colored terminal output, even if it's not available.
86    ///
87    /// Will force Zebra to use colored terminal output even if it does not detect that the output
88    /// is a terminal that supports colors.
89    ///
90    /// Defaults to `false`, which keeps the behavior of `use_color`.
91    pub force_use_color: bool,
92
93    /// The filter used for tracing events.
94    ///
95    /// The filter is used to create a `tracing-subscriber`
96    /// [`EnvFilter`](https://docs.rs/tracing-subscriber/0.2.10/tracing_subscriber/filter/struct.EnvFilter.html#directives),
97    /// and more details on the syntax can be found there or in the examples
98    /// below.
99    ///
100    /// If no filter is specified (`None`), the filter is set to `info` if the
101    /// `-v` flag is given and `warn` if it is not given.
102    ///
103    /// # Examples
104    ///
105    /// `warn,zebrad=info,zebra_network=debug` sets a global `warn` level, an
106    /// `info` level for the `zebrad` crate, and a `debug` level for the
107    /// `zebra_network` crate.
108    ///
109    /// ```ascii,no_run
110    /// [block_verify{height=Some\(block::Height\(.*000\)\)}]=trace
111    /// ```
112    /// sets `trace` level for all events occurring in the context of a
113    /// `block_verify` span whose `height` field ends in `000`, i.e., traces the
114    /// verification of every 1000th block.
115    pub filter: Option<String>,
116
117    /// The buffer_limit size sets the number of log lines that can be queued by the tracing subscriber
118    /// to be written to stdout before logs are dropped.
119    ///
120    /// Defaults to 128,000 with a minimum of 100.
121    pub buffer_limit: usize,
122
123    /// The address used for an ad-hoc RPC endpoint allowing dynamic control of the tracing filter.
124    ///
125    /// Install Zebra using `cargo install --features=filter-reload` to enable this config.
126    ///
127    /// If this is set to None, the endpoint is disabled.
128    pub endpoint_addr: Option<SocketAddr>,
129
130    /// Controls whether to write a flamegraph of tracing spans.
131    ///
132    /// Install Zebra using `cargo install --features=flamegraph` to enable this config.
133    ///
134    /// If this is set to None, flamegraphs are disabled. Otherwise, it specifies
135    /// an output file path, as described below.
136    ///
137    /// This path is not used verbatim when writing out the flamegraph. This is
138    /// because the flamegraph is written out as two parts. First the flamegraph
139    /// is constantly persisted to the disk in a "folded" representation that
140    /// records collapsed stack traces of the tracing spans that are active.
141    /// Then, when the application is finished running the destructor will flush
142    /// the flamegraph output to the folded file and then read that file and
143    /// generate the final flamegraph from it as an SVG.
144    ///
145    /// The need to create two files means that we will slightly manipulate the
146    /// path given to us to create the two representations.
147    ///
148    /// # Security
149    ///
150    /// If you are running Zebra with elevated permissions ("root"), create the
151    /// directory for this file before running Zebra, and make sure the Zebra user
152    /// account has exclusive access to that directory, and other users can't modify
153    /// its parent directories.
154    ///
155    /// # Example
156    ///
157    /// Given `flamegraph = "flamegraph"` we will generate a `flamegraph.svg` and
158    /// a `flamegraph.folded` file in the current directory.
159    ///
160    /// If you provide a path with an extension the extension will be ignored and
161    /// replaced with `.folded` and `.svg` for the respective files.
162    pub flamegraph: Option<PathBuf>,
163
164    /// Shows progress bars for block syncing, and mempool transactions, and peer networking.
165    /// Also sends logs to the default log file path.
166    ///
167    /// This config field is ignored unless the `progress-bar` feature is enabled.
168    pub progress_bar: Option<ProgressConfig>,
169
170    /// If set to a path, write the tracing logs to that path.
171    ///
172    /// By default, logs are sent to the terminal standard output.
173    /// But if the `progress_bar` config is activated, logs are sent to the standard log file path:
174    /// - Linux: `$XDG_STATE_HOME/zebrad.log` or `$HOME/.local/state/zebrad.log`
175    /// - macOS: `$HOME/Library/Application Support/zebrad.log`
176    /// - Windows: `%LOCALAPPDATA%\zebrad.log` or `C:\Users\%USERNAME%\AppData\Local\zebrad.log`
177    ///
178    /// # Security
179    ///
180    /// If you are running Zebra with elevated permissions ("root"), create the
181    /// directory for this file before running Zebra, and make sure the Zebra user
182    /// account has exclusive access to that directory, and other users can't modify
183    /// its parent directories.
184    pub log_file: Option<PathBuf>,
185
186    /// The use_journald flag sends tracing events to systemd-journald, on Linux
187    /// distributions that use systemd.
188    ///
189    /// Install Zebra using `cargo install --features=journald` to enable this config.
190    pub use_journald: bool,
191}
192
193/// The progress bars that Zebra will show while running.
194#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)]
195#[serde(rename_all = "lowercase")]
196pub enum ProgressConfig {
197    /// Show a lot of progress bars.
198    Detailed,
199
200    /// Show a few important progress bars.
201    //
202    // TODO: actually hide some progress bars in this mode.
203    #[default]
204    #[serde(other)]
205    Summary,
206}
207
208impl Config {
209    /// Returns `true` if standard output should use color escapes.
210    /// Automatically checks if Zebra is running in a terminal.
211    pub fn use_color_stdout(&self) -> bool {
212        self.force_use_color || (self.use_color && atty::is(atty::Stream::Stdout))
213    }
214
215    /// Returns `true` if standard error should use color escapes.
216    /// Automatically checks if Zebra is running in a terminal.
217    pub fn use_color_stderr(&self) -> bool {
218        self.force_use_color || (self.use_color && atty::is(atty::Stream::Stderr))
219    }
220
221    /// Returns `true` if output that could go to standard output or standard error
222    /// should use color escapes. Automatically checks if Zebra is running in a terminal.
223    pub fn use_color_stdout_and_stderr(&self) -> bool {
224        self.force_use_color
225            || (self.use_color && atty::is(atty::Stream::Stdout) && atty::is(atty::Stream::Stderr))
226    }
227}
228
229impl Default for InnerConfig {
230    fn default() -> Self {
231        // TODO: enable progress bars by default once they have been tested
232        let progress_bar = None;
233
234        Self {
235            use_color: true,
236            force_use_color: false,
237            filter: None,
238            buffer_limit: 128_000,
239            endpoint_addr: None,
240            flamegraph: None,
241            progress_bar,
242            log_file: runtime_default_log_file(None, progress_bar),
243            use_journald: false,
244        }
245    }
246}
247
248/// Returns the runtime default log file path based on the `log_file` and `progress_bar` configs.
249fn runtime_default_log_file(
250    log_file: Option<PathBuf>,
251    progress_bar: Option<ProgressConfig>,
252) -> Option<PathBuf> {
253    if let Some(log_file) = log_file {
254        return Some(log_file);
255    }
256
257    // If the progress bar is active, we want to use a log file regardless of the config.
258    // (Logging to a terminal erases parts of the progress bars, making both unreadable.)
259    if progress_bar.is_some() {
260        return default_log_file();
261    }
262
263    None
264}
265
266/// Returns the configured log file path using the runtime `log_file` and `progress_bar` config.
267///
268/// This is the inverse of [`runtime_default_log_file()`].
269fn disk_default_log_file(
270    log_file: Option<PathBuf>,
271    progress_bar: Option<ProgressConfig>,
272) -> Option<PathBuf> {
273    // If the progress bar is active, and we've likely substituted the default log file path,
274    // don't write that substitute to the config on disk.
275    if progress_bar.is_some() && log_file == default_log_file() {
276        return None;
277    }
278
279    log_file
280}
281
282/// Returns the default log file path.
283fn default_log_file() -> Option<PathBuf> {
284    dirs::state_dir()
285        .or_else(dirs::data_local_dir)
286        .map(|dir| dir.join("zebrad.log"))
287}