//! Tracing and logging infrastructure for Zebra. use std::{ net::SocketAddr, ops::{Deref, DerefMut}, path::PathBuf, }; use serde::{Deserialize, Serialize}; mod component; mod endpoint; #[cfg(feature = "flamegraph")] mod flame; pub use component::Tracing; pub use endpoint::TracingEndpoint; #[cfg(feature = "flamegraph")] pub use flame::{layer, Grapher}; /// Tracing configuration section: outer config after cross-field defaults are applied. /// /// This is a wrapper type that dereferences to the inner config type. /// // // TODO: replace with serde's finalizer attribute when that feature is implemented. // we currently use the recommended workaround of a wrapper struct with from/into attributes. // https://github.com/serde-rs/serde/issues/642#issuecomment-525432907 #[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)] #[serde( deny_unknown_fields, default, from = "InnerConfig", into = "InnerConfig" )] pub struct Config { inner: InnerConfig, } impl Deref for Config { type Target = InnerConfig; fn deref(&self) -> &Self::Target { &self.inner } } impl DerefMut for Config { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.inner } } impl From for Config { fn from(mut inner: InnerConfig) -> Self { inner.log_file = runtime_default_log_file(inner.log_file, inner.progress_bar); Self { inner } } } impl From for InnerConfig { fn from(mut config: Config) -> Self { config.log_file = disk_default_log_file(config.log_file.clone(), config.progress_bar); config.inner } } /// Tracing configuration section: inner config used to deserialize and apply cross-field defaults. #[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)] #[serde(deny_unknown_fields, default)] pub struct InnerConfig { /// Whether to use colored terminal output, if available. /// /// Colored terminal output is automatically disabled if an output stream /// is connected to a file. (Or another non-terminal device.) /// /// Defaults to `true`, which automatically enables colored output to /// terminals. pub use_color: bool, /// Whether to force the use of colored terminal output, even if it's not available. /// /// Will force Zebra to use colored terminal output even if it does not detect that the output /// is a terminal that supports colors. /// /// Defaults to `false`, which keeps the behavior of `use_color`. pub force_use_color: bool, /// The filter used for tracing events. /// /// The filter is used to create a `tracing-subscriber` /// [`EnvFilter`](https://docs.rs/tracing-subscriber/0.2.10/tracing_subscriber/filter/struct.EnvFilter.html#directives), /// and more details on the syntax can be found there or in the examples /// below. /// /// If no filter is specified (`None`), the filter is set to `info` if the /// `-v` flag is given and `warn` if it is not given. /// /// # Examples /// /// `warn,zebrad=info,zebra_network=debug` sets a global `warn` level, an /// `info` level for the `zebrad` crate, and a `debug` level for the /// `zebra_network` crate. /// /// ```ascii,no_run /// [block_verify{height=Some\(block::Height\(.*000\)\)}]=trace /// ``` /// sets `trace` level for all events occurring in the context of a /// `block_verify` span whose `height` field ends in `000`, i.e., traces the /// verification of every 1000th block. pub filter: Option, /// The buffer_limit size sets the number of log lines that can be queued by the tracing subscriber /// to be written to stdout before logs are dropped. /// /// Defaults to 128,000 with a minimum of 100. pub buffer_limit: usize, /// The address used for an ad-hoc RPC endpoint allowing dynamic control of the tracing filter. /// /// Install Zebra using `cargo install --features=filter-reload` to enable this config. /// /// If this is set to None, the endpoint is disabled. pub endpoint_addr: Option, /// Controls whether to write a flamegraph of tracing spans. /// /// Install Zebra using `cargo install --features=flamegraph` to enable this config. /// /// If this is set to None, flamegraphs are disabled. Otherwise, it specifies /// an output file path, as described below. /// /// This path is not used verbatim when writing out the flamegraph. This is /// because the flamegraph is written out as two parts. First the flamegraph /// is constantly persisted to the disk in a "folded" representation that /// records collapsed stack traces of the tracing spans that are active. /// Then, when the application is finished running the destructor will flush /// the flamegraph output to the folded file and then read that file and /// generate the final flamegraph from it as an SVG. /// /// The need to create two files means that we will slightly manipulate the /// path given to us to create the two representations. /// /// # Security /// /// If you are running Zebra with elevated permissions ("root"), create the /// directory for this file before running Zebra, and make sure the Zebra user /// account has exclusive access to that directory, and other users can't modify /// its parent directories. /// /// # Example /// /// Given `flamegraph = "flamegraph"` we will generate a `flamegraph.svg` and /// a `flamegraph.folded` file in the current directory. /// /// If you provide a path with an extension the extension will be ignored and /// replaced with `.folded` and `.svg` for the respective files. pub flamegraph: Option, /// Shows progress bars for block syncing, and mempool transactions, and peer networking. /// Also sends logs to the default log file path. /// /// This config field is ignored unless the `progress-bar` feature is enabled. pub progress_bar: Option, /// If set to a path, write the tracing logs to that path. /// /// By default, logs are sent to the terminal standard output. /// But if the `progress_bar` config is activated, logs are sent to the standard log file path: /// - Linux: `$XDG_STATE_HOME/zebrad.log` or `$HOME/.local/state/zebrad.log` /// - macOS: `$HOME/Library/Application Support/zebrad.log` /// - Windows: `%LOCALAPPDATA%\zebrad.log` or `C:\Users\%USERNAME%\AppData\Local\zebrad.log` /// /// # Security /// /// If you are running Zebra with elevated permissions ("root"), create the /// directory for this file before running Zebra, and make sure the Zebra user /// account has exclusive access to that directory, and other users can't modify /// its parent directories. pub log_file: Option, /// The use_journald flag sends tracing events to systemd-journald, on Linux /// distributions that use systemd. /// /// Install Zebra using `cargo install --features=journald` to enable this config. pub use_journald: bool, } /// The progress bars that Zebra will show while running. #[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "lowercase")] pub enum ProgressConfig { /// Show a lot of progress bars. Detailed, /// Show a few important progress bars. // // TODO: actually hide some progress bars in this mode. #[default] #[serde(other)] Summary, } impl Config { /// Returns `true` if standard output should use color escapes. /// Automatically checks if Zebra is running in a terminal. pub fn use_color_stdout(&self) -> bool { self.force_use_color || (self.use_color && atty::is(atty::Stream::Stdout)) } /// Returns `true` if standard error should use color escapes. /// Automatically checks if Zebra is running in a terminal. pub fn use_color_stderr(&self) -> bool { self.force_use_color || (self.use_color && atty::is(atty::Stream::Stderr)) } /// Returns `true` if output that could go to standard output or standard error /// should use color escapes. Automatically checks if Zebra is running in a terminal. pub fn use_color_stdout_and_stderr(&self) -> bool { self.force_use_color || (self.use_color && atty::is(atty::Stream::Stdout) && atty::is(atty::Stream::Stderr)) } } impl Default for InnerConfig { fn default() -> Self { // TODO: enable progress bars by default once they have been tested let progress_bar = None; Self { use_color: true, force_use_color: false, filter: None, buffer_limit: 128_000, endpoint_addr: None, flamegraph: None, progress_bar, log_file: runtime_default_log_file(None, progress_bar), use_journald: false, } } } /// Returns the runtime default log file path based on the `log_file` and `progress_bar` configs. fn runtime_default_log_file( log_file: Option, progress_bar: Option, ) -> Option { if let Some(log_file) = log_file { return Some(log_file); } // If the progress bar is active, we want to use a log file regardless of the config. // (Logging to a terminal erases parts of the progress bars, making both unreadable.) if progress_bar.is_some() { return default_log_file(); } None } /// Returns the configured log file path using the runtime `log_file` and `progress_bar` config. /// /// This is the inverse of [`runtime_default_log_file()`]. fn disk_default_log_file( log_file: Option, progress_bar: Option, ) -> Option { // If the progress bar is active, and we've likely substituted the default log file path, // don't write that substitute to the config on disk. if progress_bar.is_some() && log_file == default_log_file() { return None; } log_file } /// Returns the default log file path. fn default_log_file() -> Option { dirs::state_dir() .or_else(dirs::data_local_dir) .map(|dir| dir.join("zebrad.log")) }