change(ui): Enable the progress bar feature by default, but only show progress bars when the config is enabled (#7615)
* Add a progress bar config that is disabled unless the feature is on * Simplify the default config * Enable the progress bar feature by default, but require the config * Rename progress bars config to avoid merge conflicts * Use a log file when the progress bar is activated * Document how to configure progress bars * Handle log files in config_tests and check config path * Fix doc link * Fix path check * Fix config log matching * Fix clippy warning * Add tracing to config tests * It's zebrad not zebra * cargo fmt --all * Update release for config file changes * Fix config test failures * Allow printing to stdout in a method
This commit is contained in:
parent
0cffae5dd0
commit
ae52e3d23d
|
@ -5779,6 +5779,7 @@ dependencies = [
|
||||||
"humantime",
|
"humantime",
|
||||||
"indexmap 2.0.1",
|
"indexmap 2.0.1",
|
||||||
"insta",
|
"insta",
|
||||||
|
"itertools 0.11.0",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"owo-colors",
|
"owo-colors",
|
||||||
|
|
17
README.md
17
README.md
|
@ -11,7 +11,7 @@
|
||||||
- [Getting Started](#getting-started)
|
- [Getting Started](#getting-started)
|
||||||
- [Docker](#docker)
|
- [Docker](#docker)
|
||||||
- [Building Zebra](#building-zebra)
|
- [Building Zebra](#building-zebra)
|
||||||
- [Optional Features](#optional-features)
|
- [Optional Configs & Features](#optional-features)
|
||||||
- [Known Issues](#known-issues)
|
- [Known Issues](#known-issues)
|
||||||
- [Future Work](#future-work)
|
- [Future Work](#future-work)
|
||||||
- [Documentation](#documentation)
|
- [Documentation](#documentation)
|
||||||
|
@ -116,13 +116,24 @@ zebrad start
|
||||||
See the [Installing Zebra](https://zebra.zfnd.org/user/install.html) and [Running Zebra](https://zebra.zfnd.org/user/run.html)
|
See the [Installing Zebra](https://zebra.zfnd.org/user/install.html) and [Running Zebra](https://zebra.zfnd.org/user/run.html)
|
||||||
sections in the book for more details.
|
sections in the book for more details.
|
||||||
|
|
||||||
#### Optional Features
|
#### Optional Configs & Features
|
||||||
|
|
||||||
|
##### Configuring Progress Bars
|
||||||
|
|
||||||
|
Configure `tracing.progress_bar` in your `zebrad.toml` to
|
||||||
|
[show key metrics in the terminal using progress bars](https://zfnd.org/experimental-zebra-progress-bars/).
|
||||||
|
When progress bars are active, Zebra automatically sends logs to a file.
|
||||||
|
|
||||||
|
In future releases, the `progress_bar = "summary"` config will show a few key metrics,
|
||||||
|
and the "detailed" config will show all available metrics. Please let us know which metrics are
|
||||||
|
important to you!
|
||||||
|
|
||||||
|
##### Custom Build Features
|
||||||
|
|
||||||
You can also build Zebra with additional [Cargo features](https://doc.rust-lang.org/cargo/reference/features.html#command-line-feature-options):
|
You can also build Zebra with additional [Cargo features](https://doc.rust-lang.org/cargo/reference/features.html#command-line-feature-options):
|
||||||
|
|
||||||
- `getblocktemplate-rpcs` for [mining support](https://zebra.zfnd.org/user/mining.html)
|
- `getblocktemplate-rpcs` for [mining support](https://zebra.zfnd.org/user/mining.html)
|
||||||
- `prometheus` for [Prometheus metrics](https://zebra.zfnd.org/user/metrics.html)
|
- `prometheus` for [Prometheus metrics](https://zebra.zfnd.org/user/metrics.html)
|
||||||
- `progress-bar` [experimental progress bars](https://zfnd.org/experimental-zebra-progress-bars/)
|
|
||||||
- `sentry` for [Sentry monitoring](https://zebra.zfnd.org/user/tracing.html#sentry-production-monitoring)
|
- `sentry` for [Sentry monitoring](https://zebra.zfnd.org/user/tracing.html#sentry-production-monitoring)
|
||||||
- `elasticsearch` for [experimental Elasticsearch support](https://zebra.zfnd.org/user/elasticsearch.html)
|
- `elasticsearch` for [experimental Elasticsearch support](https://zebra.zfnd.org/user/elasticsearch.html)
|
||||||
|
|
||||||
|
|
|
@ -19,6 +19,7 @@ hex = "0.4.3"
|
||||||
indexmap = "2.0.1"
|
indexmap = "2.0.1"
|
||||||
lazy_static = "1.4.0"
|
lazy_static = "1.4.0"
|
||||||
insta = "1.33.0"
|
insta = "1.33.0"
|
||||||
|
itertools = "0.11.0"
|
||||||
proptest = "1.3.1"
|
proptest = "1.3.1"
|
||||||
once_cell = "1.18.0"
|
once_cell = "1.18.0"
|
||||||
rand = "0.8.5"
|
rand = "0.8.5"
|
||||||
|
|
|
@ -21,10 +21,11 @@ use tracing::instrument;
|
||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
mod arguments;
|
mod arguments;
|
||||||
|
|
||||||
pub mod to_regex;
|
pub mod to_regex;
|
||||||
|
|
||||||
pub use self::arguments::Arguments;
|
pub use self::arguments::Arguments;
|
||||||
use self::to_regex::{CollectRegexSet, ToRegex, ToRegexSet};
|
use self::to_regex::{CollectRegexSet, ToRegexSet};
|
||||||
|
|
||||||
/// A super-trait for [`Iterator`] + [`Debug`].
|
/// A super-trait for [`Iterator`] + [`Debug`].
|
||||||
pub trait IteratorDebug: Iterator + Debug {}
|
pub trait IteratorDebug: Iterator + Debug {}
|
||||||
|
@ -791,7 +792,7 @@ impl<T> TestChild<T> {
|
||||||
#[allow(clippy::unwrap_in_result)]
|
#[allow(clippy::unwrap_in_result)]
|
||||||
pub fn expect_stdout_line_matches<R>(&mut self, success_regex: R) -> Result<String>
|
pub fn expect_stdout_line_matches<R>(&mut self, success_regex: R) -> Result<String>
|
||||||
where
|
where
|
||||||
R: ToRegex + Debug,
|
R: ToRegexSet + Debug,
|
||||||
{
|
{
|
||||||
self.apply_failure_regexes_to_outputs();
|
self.apply_failure_regexes_to_outputs();
|
||||||
|
|
||||||
|
@ -823,7 +824,7 @@ impl<T> TestChild<T> {
|
||||||
#[allow(clippy::unwrap_in_result)]
|
#[allow(clippy::unwrap_in_result)]
|
||||||
pub fn expect_stderr_line_matches<R>(&mut self, success_regex: R) -> Result<String>
|
pub fn expect_stderr_line_matches<R>(&mut self, success_regex: R) -> Result<String>
|
||||||
where
|
where
|
||||||
R: ToRegex + Debug,
|
R: ToRegexSet + Debug,
|
||||||
{
|
{
|
||||||
self.apply_failure_regexes_to_outputs();
|
self.apply_failure_regexes_to_outputs();
|
||||||
|
|
||||||
|
@ -855,7 +856,7 @@ impl<T> TestChild<T> {
|
||||||
#[allow(clippy::unwrap_in_result)]
|
#[allow(clippy::unwrap_in_result)]
|
||||||
pub fn expect_stdout_line_matches_silent<R>(&mut self, success_regex: R) -> Result<String>
|
pub fn expect_stdout_line_matches_silent<R>(&mut self, success_regex: R) -> Result<String>
|
||||||
where
|
where
|
||||||
R: ToRegex + Debug,
|
R: ToRegexSet + Debug,
|
||||||
{
|
{
|
||||||
self.apply_failure_regexes_to_outputs();
|
self.apply_failure_regexes_to_outputs();
|
||||||
|
|
||||||
|
@ -887,7 +888,7 @@ impl<T> TestChild<T> {
|
||||||
#[allow(clippy::unwrap_in_result)]
|
#[allow(clippy::unwrap_in_result)]
|
||||||
pub fn expect_stderr_line_matches_silent<R>(&mut self, success_regex: R) -> Result<String>
|
pub fn expect_stderr_line_matches_silent<R>(&mut self, success_regex: R) -> Result<String>
|
||||||
where
|
where
|
||||||
R: ToRegex + Debug,
|
R: ToRegexSet + Debug,
|
||||||
{
|
{
|
||||||
self.apply_failure_regexes_to_outputs();
|
self.apply_failure_regexes_to_outputs();
|
||||||
|
|
||||||
|
@ -1246,9 +1247,9 @@ impl<T> TestOutput<T> {
|
||||||
#[allow(clippy::unwrap_in_result)]
|
#[allow(clippy::unwrap_in_result)]
|
||||||
pub fn stdout_matches<R>(&self, regex: R) -> Result<&Self>
|
pub fn stdout_matches<R>(&self, regex: R) -> Result<&Self>
|
||||||
where
|
where
|
||||||
R: ToRegex + Debug,
|
R: ToRegexSet + Debug,
|
||||||
{
|
{
|
||||||
let re = regex.to_regex().expect("regex must be valid");
|
let re = regex.to_regex_set().expect("regex must be valid");
|
||||||
|
|
||||||
self.output_check(
|
self.output_check(
|
||||||
|stdout| re.is_match(stdout),
|
|stdout| re.is_match(stdout),
|
||||||
|
@ -1270,9 +1271,9 @@ impl<T> TestOutput<T> {
|
||||||
#[allow(clippy::unwrap_in_result)]
|
#[allow(clippy::unwrap_in_result)]
|
||||||
pub fn stdout_line_matches<R>(&self, regex: R) -> Result<&Self>
|
pub fn stdout_line_matches<R>(&self, regex: R) -> Result<&Self>
|
||||||
where
|
where
|
||||||
R: ToRegex + Debug,
|
R: ToRegexSet + Debug,
|
||||||
{
|
{
|
||||||
let re = regex.to_regex().expect("regex must be valid");
|
let re = regex.to_regex_set().expect("regex must be valid");
|
||||||
|
|
||||||
self.any_output_line(
|
self.any_output_line(
|
||||||
|line| re.is_match(line),
|
|line| re.is_match(line),
|
||||||
|
@ -1300,9 +1301,9 @@ impl<T> TestOutput<T> {
|
||||||
#[allow(clippy::unwrap_in_result)]
|
#[allow(clippy::unwrap_in_result)]
|
||||||
pub fn stderr_matches<R>(&self, regex: R) -> Result<&Self>
|
pub fn stderr_matches<R>(&self, regex: R) -> Result<&Self>
|
||||||
where
|
where
|
||||||
R: ToRegex + Debug,
|
R: ToRegexSet + Debug,
|
||||||
{
|
{
|
||||||
let re = regex.to_regex().expect("regex must be valid");
|
let re = regex.to_regex_set().expect("regex must be valid");
|
||||||
|
|
||||||
self.output_check(
|
self.output_check(
|
||||||
|stderr| re.is_match(stderr),
|
|stderr| re.is_match(stderr),
|
||||||
|
@ -1324,9 +1325,9 @@ impl<T> TestOutput<T> {
|
||||||
#[allow(clippy::unwrap_in_result)]
|
#[allow(clippy::unwrap_in_result)]
|
||||||
pub fn stderr_line_matches<R>(&self, regex: R) -> Result<&Self>
|
pub fn stderr_line_matches<R>(&self, regex: R) -> Result<&Self>
|
||||||
where
|
where
|
||||||
R: ToRegex + Debug,
|
R: ToRegexSet + Debug,
|
||||||
{
|
{
|
||||||
let re = regex.to_regex().expect("regex must be valid");
|
let re = regex.to_regex_set().expect("regex must be valid");
|
||||||
|
|
||||||
self.any_output_line(
|
self.any_output_line(
|
||||||
|line| re.is_match(line),
|
|line| re.is_match(line),
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
use std::iter;
|
use std::iter;
|
||||||
|
|
||||||
|
use itertools::Itertools;
|
||||||
use regex::{Error, Regex, RegexBuilder, RegexSet, RegexSetBuilder};
|
use regex::{Error, Regex, RegexBuilder, RegexSet, RegexSetBuilder};
|
||||||
|
|
||||||
/// A trait for converting a value to a [`Regex`].
|
/// A trait for converting a value to a [`Regex`].
|
||||||
|
@ -135,15 +136,17 @@ pub trait CollectRegexSet {
|
||||||
impl<I> CollectRegexSet for I
|
impl<I> CollectRegexSet for I
|
||||||
where
|
where
|
||||||
I: IntoIterator,
|
I: IntoIterator,
|
||||||
I::Item: ToRegex,
|
I::Item: ToRegexSet,
|
||||||
{
|
{
|
||||||
fn collect_regex_set(self) -> Result<RegexSet, Error> {
|
fn collect_regex_set(self) -> Result<RegexSet, Error> {
|
||||||
let regexes: Result<Vec<Regex>, Error> =
|
let regexes: Result<Vec<RegexSet>, Error> = self
|
||||||
self.into_iter().map(|item| item.to_regex()).collect();
|
.into_iter()
|
||||||
|
.map(|item| item.to_regex_set())
|
||||||
|
.try_collect();
|
||||||
let regexes = regexes?;
|
let regexes = regexes?;
|
||||||
|
|
||||||
// This conversion discards flags and limits from Regex and RegexBuilder.
|
// This conversion discards flags and limits from Regex and RegexBuilder.
|
||||||
let regexes = regexes.iter().map(|regex| regex.as_str());
|
let regexes = regexes.iter().flat_map(|regex_set| regex_set.patterns());
|
||||||
|
|
||||||
RegexSet::new(regexes)
|
RegexSet::new(regexes)
|
||||||
}
|
}
|
||||||
|
|
|
@ -52,7 +52,7 @@ features = [
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
# In release builds, don't compile debug logging code, to improve performance.
|
# In release builds, don't compile debug logging code, to improve performance.
|
||||||
default = ["release_max_level_info"]
|
default = ["release_max_level_info", "progress-bar"]
|
||||||
|
|
||||||
# Default features for official ZF binary release builds
|
# Default features for official ZF binary release builds
|
||||||
default-release-binaries = ["default", "sentry"]
|
default-release-binaries = ["default", "sentry"]
|
||||||
|
|
|
@ -434,6 +434,7 @@ impl Application for ZebradApp {
|
||||||
// Override the default tracing filter based on the command-line verbosity.
|
// Override the default tracing filter based on the command-line verbosity.
|
||||||
tracing_config.filter = tracing_config
|
tracing_config.filter = tracing_config
|
||||||
.filter
|
.filter
|
||||||
|
.clone()
|
||||||
.or_else(|| Some(default_filter.to_owned()));
|
.or_else(|| Some(default_filter.to_owned()));
|
||||||
} else {
|
} else {
|
||||||
// Don't apply the configured filter for short-lived commands.
|
// Don't apply the configured filter for short-lived commands.
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
//! Tracing and logging infrastructure for Zebra.
|
//! Tracing and logging infrastructure for Zebra.
|
||||||
|
|
||||||
use std::{net::SocketAddr, path::PathBuf};
|
use std::{
|
||||||
|
net::SocketAddr,
|
||||||
|
ops::{Deref, DerefMut},
|
||||||
|
path::PathBuf,
|
||||||
|
};
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
@ -16,10 +20,59 @@ pub use endpoint::TracingEndpoint;
|
||||||
#[cfg(feature = "flamegraph")]
|
#[cfg(feature = "flamegraph")]
|
||||||
pub use flame::{layer, Grapher};
|
pub use flame::{layer, Grapher};
|
||||||
|
|
||||||
/// Tracing configuration section.
|
/// Tracing configuration section: outer config after cross-field defaults are applied.
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
///
|
||||||
#[serde(deny_unknown_fields, default)]
|
/// 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 {
|
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<InnerConfig> 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<Config> 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.
|
/// Whether to use colored terminal output, if available.
|
||||||
///
|
///
|
||||||
/// Colored terminal output is automatically disabled if an output stream
|
/// Colored terminal output is automatically disabled if an output stream
|
||||||
|
@ -108,10 +161,16 @@ pub struct Config {
|
||||||
/// replaced with `.folded` and `.svg` for the respective files.
|
/// replaced with `.folded` and `.svg` for the respective files.
|
||||||
pub flamegraph: Option<PathBuf>,
|
pub flamegraph: Option<PathBuf>,
|
||||||
|
|
||||||
|
/// 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<ProgressConfig>,
|
||||||
|
|
||||||
/// If set to a path, write the tracing logs to that path.
|
/// If set to a path, write the tracing logs to that path.
|
||||||
///
|
///
|
||||||
/// By default, logs are sent to the terminal standard output.
|
/// By default, logs are sent to the terminal standard output.
|
||||||
/// But if the `progress-bar` feature is activated, logs are sent to the standard log file path:
|
/// 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`
|
/// - Linux: `$XDG_STATE_HOME/zebrad.log` or `$HOME/.local/state/zebrad.log`
|
||||||
/// - macOS: `$HOME/Library/Application Support/zebrad.log`
|
/// - macOS: `$HOME/Library/Application Support/zebrad.log`
|
||||||
/// - Windows: `%LOCALAPPDATA%\zebrad.log` or `C:\Users\%USERNAME%\AppData\Local\zebrad.log`
|
/// - Windows: `%LOCALAPPDATA%\zebrad.log` or `C:\Users\%USERNAME%\AppData\Local\zebrad.log`
|
||||||
|
@ -131,6 +190,21 @@ pub struct Config {
|
||||||
pub use_journald: bool,
|
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 {
|
impl Config {
|
||||||
/// Returns `true` if standard output should use color escapes.
|
/// Returns `true` if standard output should use color escapes.
|
||||||
/// Automatically checks if Zebra is running in a terminal.
|
/// Automatically checks if Zebra is running in a terminal.
|
||||||
|
@ -152,12 +226,10 @@ impl Config {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Config {
|
impl Default for InnerConfig {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
#[cfg(feature = "progress-bar")]
|
// TODO: enable progress bars by default once they have been tested
|
||||||
let default_log_file = dirs::state_dir()
|
let progress_bar = None;
|
||||||
.or_else(dirs::data_local_dir)
|
|
||||||
.map(|dir| dir.join("zebrad.log"));
|
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
use_color: true,
|
use_color: true,
|
||||||
|
@ -166,11 +238,50 @@ impl Default for Config {
|
||||||
buffer_limit: 128_000,
|
buffer_limit: 128_000,
|
||||||
endpoint_addr: None,
|
endpoint_addr: None,
|
||||||
flamegraph: None,
|
flamegraph: None,
|
||||||
#[cfg(not(feature = "progress-bar"))]
|
progress_bar,
|
||||||
log_file: None,
|
log_file: runtime_default_log_file(None, progress_bar),
|
||||||
#[cfg(feature = "progress-bar")]
|
|
||||||
log_file: default_log_file,
|
|
||||||
use_journald: false,
|
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<PathBuf>,
|
||||||
|
progress_bar: Option<ProgressConfig>,
|
||||||
|
) -> Option<PathBuf> {
|
||||||
|
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<PathBuf>,
|
||||||
|
progress_bar: Option<ProgressConfig>,
|
||||||
|
) -> Option<PathBuf> {
|
||||||
|
// 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<PathBuf> {
|
||||||
|
dirs::state_dir()
|
||||||
|
.or_else(dirs::data_local_dir)
|
||||||
|
.map(|dir| dir.join("zebrad.log"))
|
||||||
|
}
|
||||||
|
|
|
@ -79,14 +79,14 @@ impl Tracing {
|
||||||
/// and the Zebra logo on startup. (If the terminal supports it.)
|
/// and the Zebra logo on startup. (If the terminal supports it.)
|
||||||
//
|
//
|
||||||
// This method should only print to stderr, because stdout is for tracing logs.
|
// This method should only print to stderr, because stdout is for tracing logs.
|
||||||
#[allow(clippy::print_stderr, clippy::unwrap_in_result)]
|
#[allow(clippy::print_stdout, clippy::print_stderr, clippy::unwrap_in_result)]
|
||||||
pub fn new(network: Network, config: Config, uses_intro: bool) -> Result<Self, FrameworkError> {
|
pub fn new(network: Network, config: Config, uses_intro: bool) -> Result<Self, FrameworkError> {
|
||||||
// Only use color if tracing output is being sent to a terminal or if it was explicitly
|
// Only use color if tracing output is being sent to a terminal or if it was explicitly
|
||||||
// forced to.
|
// forced to.
|
||||||
let use_color = config.use_color_stdout();
|
let use_color = config.use_color_stdout();
|
||||||
let use_color_stderr = config.use_color_stderr();
|
let use_color_stderr = config.use_color_stderr();
|
||||||
|
|
||||||
let filter = config.filter.unwrap_or_default();
|
let filter = config.filter.clone().unwrap_or_default();
|
||||||
let flame_root = &config.flamegraph;
|
let flame_root = &config.flamegraph;
|
||||||
|
|
||||||
// Only show the intro for user-focused node server commands like `start`
|
// Only show the intro for user-focused node server commands like `start`
|
||||||
|
@ -139,7 +139,8 @@ impl Tracing {
|
||||||
}
|
}
|
||||||
|
|
||||||
if uses_intro {
|
if uses_intro {
|
||||||
eprintln!("Sending logs to {log_file:?}...");
|
// We want this to appear on stdout instead of the usual log messages.
|
||||||
|
println!("Sending logs to {log_file:?}...");
|
||||||
}
|
}
|
||||||
let log_file = File::options().append(true).create(true).open(log_file)?;
|
let log_file = File::options().append(true).create(true).open(log_file)?;
|
||||||
Box::new(log_file) as BoxWrite
|
Box::new(log_file) as BoxWrite
|
||||||
|
@ -305,7 +306,7 @@ impl Tracing {
|
||||||
//
|
//
|
||||||
// TODO: move this to its own module?
|
// TODO: move this to its own module?
|
||||||
#[cfg(feature = "progress-bar")]
|
#[cfg(feature = "progress-bar")]
|
||||||
{
|
if let Some(progress_bar_config) = config.progress_bar.as_ref() {
|
||||||
use howudoin::consumers::TermLine;
|
use howudoin::consumers::TermLine;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
|
@ -315,7 +316,11 @@ impl Tracing {
|
||||||
let terminal_consumer = TermLine::with_debounce(PROGRESS_BAR_DEBOUNCE);
|
let terminal_consumer = TermLine::with_debounce(PROGRESS_BAR_DEBOUNCE);
|
||||||
howudoin::init(terminal_consumer);
|
howudoin::init(terminal_consumer);
|
||||||
|
|
||||||
info!("activated progress bar");
|
info!(?progress_bar_config, "activated progress bars");
|
||||||
|
} else {
|
||||||
|
info!(
|
||||||
|
"set 'tracing.progress_bar =\"summary\"' in zebrad.toml to activate progress bars"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
|
|
|
@ -61,9 +61,10 @@
|
||||||
//!
|
//!
|
||||||
//! ### Metrics
|
//! ### Metrics
|
||||||
//!
|
//!
|
||||||
//! * `prometheus`: export metrics to prometheus.
|
//! * configuring a `tracing.progress_bar`: shows key metrics in the terminal using progress bars,
|
||||||
//! * `progress-bar`: shows key metrics in the terminal using progress bars,
|
|
||||||
//! and automatically configures Zebra to send logs to a file.
|
//! and automatically configures Zebra to send logs to a file.
|
||||||
|
//! (The `progress-bar` feature is activated by default.)
|
||||||
|
//! * `prometheus`: export metrics to prometheus.
|
||||||
//!
|
//!
|
||||||
//! Read the [metrics](https://zebra.zfnd.org/user/metrics.html) section of the book
|
//! Read the [metrics](https://zebra.zfnd.org/user/metrics.html) section of the book
|
||||||
//! for more details.
|
//! for more details.
|
||||||
|
|
|
@ -161,7 +161,12 @@ use zebra_network::constants::PORT_IN_USE_ERROR;
|
||||||
use zebra_node_services::rpc_client::RpcRequestClient;
|
use zebra_node_services::rpc_client::RpcRequestClient;
|
||||||
use zebra_state::{constants::LOCK_FILE_ERROR, database_format_version_in_code};
|
use zebra_state::{constants::LOCK_FILE_ERROR, database_format_version_in_code};
|
||||||
|
|
||||||
use zebra_test::{args, command::ContextFrom, net::random_known_port, prelude::*};
|
use zebra_test::{
|
||||||
|
args,
|
||||||
|
command::{to_regex::CollectRegexSet, ContextFrom},
|
||||||
|
net::random_known_port,
|
||||||
|
prelude::*,
|
||||||
|
};
|
||||||
|
|
||||||
mod common;
|
mod common;
|
||||||
|
|
||||||
|
@ -605,7 +610,7 @@ fn config_tests() -> Result<()> {
|
||||||
// Check that Zebra's previous configurations still work
|
// Check that Zebra's previous configurations still work
|
||||||
stored_configs_work()?;
|
stored_configs_work()?;
|
||||||
|
|
||||||
// Runs `zebrad` serially to avoid potential port conflicts
|
// We run the `zebrad` app test after the config tests, to avoid potential port conflicts
|
||||||
app_no_args()?;
|
app_no_args()?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -619,6 +624,8 @@ fn app_no_args() -> Result<()> {
|
||||||
// start caches state, so run one of the start tests with persistent state
|
// start caches state, so run one of the start tests with persistent state
|
||||||
let testdir = testdir()?.with_config(&mut persistent_test_config()?)?;
|
let testdir = testdir()?.with_config(&mut persistent_test_config()?)?;
|
||||||
|
|
||||||
|
tracing::info!(?testdir, "running zebrad with no config (default settings)");
|
||||||
|
|
||||||
let mut child = testdir.spawn_child(args![])?;
|
let mut child = testdir.spawn_child(args![])?;
|
||||||
|
|
||||||
// Run the program and kill it after a few seconds
|
// Run the program and kill it after a few seconds
|
||||||
|
@ -651,6 +658,8 @@ fn valid_generated_config(command: &str, expect_stdout_line_contains: &str) -> R
|
||||||
// Add a config file name to tempdir path
|
// Add a config file name to tempdir path
|
||||||
let generated_config_path = testdir.path().join("zebrad.toml");
|
let generated_config_path = testdir.path().join("zebrad.toml");
|
||||||
|
|
||||||
|
tracing::info!(?generated_config_path, "generating valid config");
|
||||||
|
|
||||||
// Generate configuration in temp dir path
|
// Generate configuration in temp dir path
|
||||||
let child =
|
let child =
|
||||||
testdir.spawn_child(args!["generate", "-o": generated_config_path.to_str().unwrap()])?;
|
testdir.spawn_child(args!["generate", "-o": generated_config_path.to_str().unwrap()])?;
|
||||||
|
@ -664,6 +673,8 @@ fn valid_generated_config(command: &str, expect_stdout_line_contains: &str) -> R
|
||||||
"generated config file not found"
|
"generated config file not found"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
tracing::info!(?generated_config_path, "testing valid config parsing");
|
||||||
|
|
||||||
// Run command using temp dir and kill it after a few seconds
|
// Run command using temp dir and kill it after a few seconds
|
||||||
let mut child = testdir.spawn_child(args![command])?;
|
let mut child = testdir.spawn_child(args![command])?;
|
||||||
std::thread::sleep(LAUNCH_DELAY);
|
std::thread::sleep(LAUNCH_DELAY);
|
||||||
|
@ -702,6 +713,8 @@ fn last_config_is_stored() -> Result<()> {
|
||||||
// Add a config file name to tempdir path
|
// Add a config file name to tempdir path
|
||||||
let generated_config_path = testdir.path().join("zebrad.toml");
|
let generated_config_path = testdir.path().join("zebrad.toml");
|
||||||
|
|
||||||
|
tracing::info!(?generated_config_path, "generated current config");
|
||||||
|
|
||||||
// Generate configuration in temp dir path
|
// Generate configuration in temp dir path
|
||||||
let child =
|
let child =
|
||||||
testdir.spawn_child(args!["generate", "-o": generated_config_path.to_str().unwrap()])?;
|
testdir.spawn_child(args!["generate", "-o": generated_config_path.to_str().unwrap()])?;
|
||||||
|
@ -715,6 +728,11 @@ fn last_config_is_stored() -> Result<()> {
|
||||||
"generated config file not found"
|
"generated config file not found"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
tracing::info!(
|
||||||
|
?generated_config_path,
|
||||||
|
"testing current config is in stored configs"
|
||||||
|
);
|
||||||
|
|
||||||
// Get the contents of the generated config file
|
// Get the contents of the generated config file
|
||||||
let generated_content =
|
let generated_content =
|
||||||
fs::read_to_string(generated_config_path).expect("Should have been able to read the file");
|
fs::read_to_string(generated_config_path).expect("Should have been able to read the file");
|
||||||
|
@ -815,6 +833,11 @@ fn invalid_generated_config() -> Result<()> {
|
||||||
// Add a config file name to tempdir path.
|
// Add a config file name to tempdir path.
|
||||||
let config_path = testdir.path().join("zebrad.toml");
|
let config_path = testdir.path().join("zebrad.toml");
|
||||||
|
|
||||||
|
tracing::info!(
|
||||||
|
?config_path,
|
||||||
|
"testing invalid config parsing: generating valid config"
|
||||||
|
);
|
||||||
|
|
||||||
// Generate a valid config file in the temp dir.
|
// Generate a valid config file in the temp dir.
|
||||||
let child = testdir.spawn_child(args!["generate", "-o": config_path.to_str().unwrap()])?;
|
let child = testdir.spawn_child(args!["generate", "-o": config_path.to_str().unwrap()])?;
|
||||||
|
|
||||||
|
@ -849,10 +872,14 @@ fn invalid_generated_config() -> Result<()> {
|
||||||
secs = 3600
|
secs = 3600
|
||||||
";
|
";
|
||||||
|
|
||||||
|
tracing::info!(?config_path, "writing invalid config");
|
||||||
|
|
||||||
// Write the altered config file so that Zebra can pick it up.
|
// Write the altered config file so that Zebra can pick it up.
|
||||||
fs::write(config_path.to_str().unwrap(), config_file.as_bytes())
|
fs::write(config_path.to_str().unwrap(), config_file.as_bytes())
|
||||||
.expect("Could not write the altered config file.");
|
.expect("Could not write the altered config file.");
|
||||||
|
|
||||||
|
tracing::info!(?config_path, "testing invalid config parsing");
|
||||||
|
|
||||||
// Run Zebra in a temp dir so that it loads the config.
|
// Run Zebra in a temp dir so that it loads the config.
|
||||||
let mut child = testdir.spawn_child(args!["start"])?;
|
let mut child = testdir.spawn_child(args!["start"])?;
|
||||||
|
|
||||||
|
@ -883,6 +910,8 @@ fn invalid_generated_config() -> Result<()> {
|
||||||
fn stored_configs_work() -> Result<()> {
|
fn stored_configs_work() -> Result<()> {
|
||||||
let old_configs_dir = configs_dir();
|
let old_configs_dir = configs_dir();
|
||||||
|
|
||||||
|
tracing::info!(?old_configs_dir, "testing older config parsing");
|
||||||
|
|
||||||
for config_file in old_configs_dir
|
for config_file in old_configs_dir
|
||||||
.read_dir()
|
.read_dir()
|
||||||
.expect("read_dir call failed")
|
.expect("read_dir call failed")
|
||||||
|
@ -892,10 +921,10 @@ fn stored_configs_work() -> Result<()> {
|
||||||
let config_file_name = config_file_path
|
let config_file_name = config_file_path
|
||||||
.file_name()
|
.file_name()
|
||||||
.expect("config files must have a file name")
|
.expect("config files must have a file name")
|
||||||
.to_string_lossy();
|
.to_str()
|
||||||
|
.expect("config file names are valid unicode");
|
||||||
|
|
||||||
if config_file_name.as_ref().starts_with('.') || config_file_name.as_ref().starts_with('#')
|
if config_file_name.starts_with('.') || config_file_name.starts_with('#') {
|
||||||
{
|
|
||||||
// Skip editor files and other invalid config paths
|
// Skip editor files and other invalid config paths
|
||||||
tracing::info!(
|
tracing::info!(
|
||||||
?config_file_path,
|
?config_file_path,
|
||||||
|
@ -907,10 +936,7 @@ fn stored_configs_work() -> Result<()> {
|
||||||
// ignore files starting with getblocktemplate prefix
|
// ignore files starting with getblocktemplate prefix
|
||||||
// if we were not built with the getblocktemplate-rpcs feature.
|
// if we were not built with the getblocktemplate-rpcs feature.
|
||||||
#[cfg(not(feature = "getblocktemplate-rpcs"))]
|
#[cfg(not(feature = "getblocktemplate-rpcs"))]
|
||||||
if config_file_name
|
if config_file_name.starts_with(GET_BLOCK_TEMPLATE_CONFIG_PREFIX) {
|
||||||
.as_ref()
|
|
||||||
.starts_with(GET_BLOCK_TEMPLATE_CONFIG_PREFIX)
|
|
||||||
{
|
|
||||||
tracing::info!(
|
tracing::info!(
|
||||||
?config_file_path,
|
?config_file_path,
|
||||||
"skipping getblocktemplate-rpcs config file path"
|
"skipping getblocktemplate-rpcs config file path"
|
||||||
|
@ -921,12 +947,41 @@ fn stored_configs_work() -> Result<()> {
|
||||||
let run_dir = testdir()?;
|
let run_dir = testdir()?;
|
||||||
let stored_config_path = config_file_full_path(config_file.path());
|
let stored_config_path = config_file_full_path(config_file.path());
|
||||||
|
|
||||||
|
tracing::info!(
|
||||||
|
?stored_config_path,
|
||||||
|
"testing old config can be parsed by current zebrad"
|
||||||
|
);
|
||||||
|
|
||||||
// run zebra with stored config
|
// run zebra with stored config
|
||||||
let mut child =
|
let mut child =
|
||||||
run_dir.spawn_child(args!["-c", stored_config_path.to_str().unwrap(), "start"])?;
|
run_dir.spawn_child(args!["-c", stored_config_path.to_str().unwrap(), "start"])?;
|
||||||
|
|
||||||
// zebra was able to start with the stored config
|
let success_regexes = [
|
||||||
child.expect_stdout_line_matches("Starting zebrad".to_string())?;
|
// When logs are sent to the terminal, we see the config loading message and path.
|
||||||
|
format!(
|
||||||
|
"loaded zebrad config.*config_path.*=.*{}",
|
||||||
|
regex::escape(config_file_name)
|
||||||
|
),
|
||||||
|
// If they are sent to a file, we see a log file message on stdout,
|
||||||
|
// and a logo, welcome message, and progress bar on stderr.
|
||||||
|
"Sending logs to".to_string(),
|
||||||
|
// TODO: add expect_stdout_or_stderr_line_matches() and check for this instead:
|
||||||
|
//"Thank you for running a mainnet zebrad".to_string(),
|
||||||
|
];
|
||||||
|
|
||||||
|
tracing::info!(
|
||||||
|
?stored_config_path,
|
||||||
|
?success_regexes,
|
||||||
|
"waiting for zebrad to parse config and start logging"
|
||||||
|
);
|
||||||
|
|
||||||
|
let success_regexes = success_regexes
|
||||||
|
.iter()
|
||||||
|
.collect_regex_set()
|
||||||
|
.expect("regexes are valid");
|
||||||
|
|
||||||
|
// Zebra was able to start with the stored config.
|
||||||
|
child.expect_stdout_line_matches(success_regexes)?;
|
||||||
|
|
||||||
// finish
|
// finish
|
||||||
child.kill(false)?;
|
child.kill(false)?;
|
||||||
|
|
|
@ -58,10 +58,9 @@ pub fn default_test_config() -> Result<ZebradConfig> {
|
||||||
env::var("ZEBRA_FORCE_USE_COLOR"),
|
env::var("ZEBRA_FORCE_USE_COLOR"),
|
||||||
Err(env::VarError::NotPresent)
|
Err(env::VarError::NotPresent)
|
||||||
);
|
);
|
||||||
let tracing = tracing::Config {
|
|
||||||
force_use_color,
|
let mut tracing = tracing::Config::default();
|
||||||
..tracing::Config::default()
|
tracing.force_use_color = force_use_color;
|
||||||
};
|
|
||||||
|
|
||||||
let mut state = zebra_state::Config::ephemeral();
|
let mut state = zebra_state::Config::ephemeral();
|
||||||
state.debug_validity_check_interval = Some(DATABASE_FORMAT_CHECK_INTERVAL);
|
state.debug_validity_check_interval = Some(DATABASE_FORMAT_CHECK_INTERVAL);
|
||||||
|
|
|
@ -0,0 +1,73 @@
|
||||||
|
# Default configuration for zebrad.
|
||||||
|
#
|
||||||
|
# This file can be used as a skeleton for custom configs.
|
||||||
|
#
|
||||||
|
# Unspecified fields use default values. Optional fields are Some(field) if the
|
||||||
|
# field is present and None if it is absent.
|
||||||
|
#
|
||||||
|
# This file is generated as an example using zebrad's current defaults.
|
||||||
|
# You should set only the config options you want to keep, and delete the rest.
|
||||||
|
# Only a subset of fields are present in the skeleton, since optional values
|
||||||
|
# whose default is None are omitted.
|
||||||
|
#
|
||||||
|
# The config format (including a complete list of sections and fields) is
|
||||||
|
# documented here:
|
||||||
|
# https://doc.zebra.zfnd.org/zebrad/config/struct.ZebradConfig.html
|
||||||
|
#
|
||||||
|
# zebrad attempts to load configs in the following order:
|
||||||
|
#
|
||||||
|
# 1. The -c flag on the command line, e.g., `zebrad -c myconfig.toml start`;
|
||||||
|
# 2. The file `zebrad.toml` in the users's preference directory (platform-dependent);
|
||||||
|
# 3. The default config.
|
||||||
|
|
||||||
|
[consensus]
|
||||||
|
checkpoint_sync = true
|
||||||
|
debug_skip_parameter_preload = false
|
||||||
|
|
||||||
|
[mempool]
|
||||||
|
eviction_memory_time = "1h"
|
||||||
|
tx_cost_limit = 80000000
|
||||||
|
|
||||||
|
[metrics]
|
||||||
|
|
||||||
|
[network]
|
||||||
|
cache_dir = true
|
||||||
|
crawl_new_peer_interval = "1m 1s"
|
||||||
|
initial_mainnet_peers = [
|
||||||
|
"dnsseed.z.cash:8233",
|
||||||
|
"dnsseed.str4d.xyz:8233",
|
||||||
|
"mainnet.seeder.zfnd.org:8233",
|
||||||
|
"mainnet.is.yolo.money:8233",
|
||||||
|
]
|
||||||
|
initial_testnet_peers = [
|
||||||
|
"dnsseed.testnet.z.cash:18233",
|
||||||
|
"testnet.seeder.zfnd.org:18233",
|
||||||
|
"testnet.is.yolo.money:18233",
|
||||||
|
]
|
||||||
|
listen_addr = "0.0.0.0:8233"
|
||||||
|
max_connections_per_ip = 1
|
||||||
|
network = "Mainnet"
|
||||||
|
peerset_initial_target_size = 25
|
||||||
|
|
||||||
|
[rpc]
|
||||||
|
debug_force_finished_sync = false
|
||||||
|
parallel_cpu_threads = 1
|
||||||
|
|
||||||
|
[state]
|
||||||
|
cache_dir = "cache_dir"
|
||||||
|
delete_old_database = true
|
||||||
|
ephemeral = false
|
||||||
|
|
||||||
|
[sync]
|
||||||
|
checkpoint_verify_concurrency_limit = 1000
|
||||||
|
download_concurrency_limit = 50
|
||||||
|
full_verify_concurrency_limit = 20
|
||||||
|
parallel_cpu_threads = 0
|
||||||
|
|
||||||
|
[tracing]
|
||||||
|
buffer_limit = 128000
|
||||||
|
force_use_color = false
|
||||||
|
progress_bar = "summary"
|
||||||
|
use_color = true
|
||||||
|
use_journald = false
|
||||||
|
|
Loading…
Reference in New Issue