reth_tracing/layers.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185
use std::path::{Path, PathBuf};
use rolling_file::{RollingConditionBasic, RollingFileAppender};
use tracing_appender::non_blocking::WorkerGuard;
use tracing_subscriber::{filter::Directive, EnvFilter, Layer, Registry};
use crate::formatter::LogFormat;
/// A worker guard returned by the file layer.
///
/// When a guard is dropped, all events currently in-memory are flushed to the log file this guard
/// belongs to.
pub type FileWorkerGuard = tracing_appender::non_blocking::WorkerGuard;
/// A boxed tracing [Layer].
pub(crate) type BoxedLayer<S> = Box<dyn Layer<S> + Send + Sync>;
const RETH_LOG_FILE_NAME: &str = "reth.log";
/// Default [directives](Directive) for [`EnvFilter`] which disables high-frequency debug logs from
/// `hyper`, `hickory-resolver`, `jsonrpsee-server`, and `discv5`.
const DEFAULT_ENV_FILTER_DIRECTIVES: [&str; 5] = [
"hyper::proto::h1=off",
"hickory_resolver=off",
"hickory_proto=off",
"discv5=off",
"jsonrpsee-server=off",
];
/// Manages the collection of layers for a tracing subscriber.
///
/// `Layers` acts as a container for different logging layers such as stdout, file, or journald.
/// Each layer can be configured separately and then combined into a tracing subscriber.
pub(crate) struct Layers {
inner: Vec<BoxedLayer<Registry>>,
}
impl Layers {
/// Creates a new `Layers` instance.
pub(crate) fn new() -> Self {
Self { inner: vec![] }
}
/// Consumes the `Layers` instance, returning the inner vector of layers.
pub(crate) fn into_inner(self) -> Vec<BoxedLayer<Registry>> {
self.inner
}
/// Adds a journald layer to the layers collection.
///
/// # Arguments
/// * `filter` - A string containing additional filter directives for this layer.
///
/// # Returns
/// An `eyre::Result<()>` indicating the success or failure of the operation.
pub(crate) fn journald(&mut self, filter: &str) -> eyre::Result<()> {
let journald_filter = build_env_filter(None, filter)?;
let layer = tracing_journald::layer()?.with_filter(journald_filter).boxed();
self.inner.push(layer);
Ok(())
}
/// Adds a stdout layer with specified formatting and filtering.
///
/// # Type Parameters
/// * `S` - The type of subscriber that will use these layers.
///
/// # Arguments
/// * `format` - The log message format.
/// * `directive` - Directive for the default logging level.
/// * `filter` - Additional filter directives as a string.
/// * `color` - Optional color configuration for the log messages.
///
/// # Returns
/// An `eyre::Result<()>` indicating the success or failure of the operation.
pub(crate) fn stdout(
&mut self,
format: LogFormat,
default_directive: Directive,
filters: &str,
color: Option<String>,
) -> eyre::Result<()> {
let filter = build_env_filter(Some(default_directive), filters)?;
let layer = format.apply(filter, color, None);
self.inner.push(layer.boxed());
Ok(())
}
/// Adds a file logging layer to the layers collection.
///
/// # Arguments
/// * `format` - The format for log messages.
/// * `filter` - Additional filter directives as a string.
/// * `file_info` - Information about the log file including path and rotation strategy.
///
/// # Returns
/// An `eyre::Result<FileWorkerGuard>` representing the file logging worker.
pub(crate) fn file(
&mut self,
format: LogFormat,
filter: &str,
file_info: FileInfo,
) -> eyre::Result<FileWorkerGuard> {
let (writer, guard) = file_info.create_log_writer();
let file_filter = build_env_filter(None, filter)?;
let layer = format.apply(file_filter, None, Some(writer));
self.inner.push(layer);
Ok(guard)
}
}
/// Holds configuration information for file logging.
///
/// Contains details about the log file's path, name, size, and rotation strategy.
#[derive(Debug, Clone)]
pub struct FileInfo {
dir: PathBuf,
file_name: String,
max_size_bytes: u64,
max_files: usize,
}
impl FileInfo {
/// Creates a new `FileInfo` instance.
pub fn new(dir: PathBuf, max_size_bytes: u64, max_files: usize) -> Self {
Self { dir, file_name: RETH_LOG_FILE_NAME.to_string(), max_size_bytes, max_files }
}
/// Creates the log directory if it doesn't exist.
///
/// # Returns
/// A reference to the path of the log directory.
fn create_log_dir(&self) -> &Path {
let log_dir: &Path = self.dir.as_ref();
if !log_dir.exists() {
std::fs::create_dir_all(log_dir).expect("Could not create log directory");
}
log_dir
}
/// Creates a non-blocking writer for the log file.
///
/// # Returns
/// A tuple containing the non-blocking writer and its associated worker guard.
fn create_log_writer(&self) -> (tracing_appender::non_blocking::NonBlocking, WorkerGuard) {
let log_dir = self.create_log_dir();
let (writer, guard) = tracing_appender::non_blocking(
RollingFileAppender::new(
log_dir.join(&self.file_name),
RollingConditionBasic::new().max_size(self.max_size_bytes),
self.max_files,
)
.expect("Could not initialize file logging"),
);
(writer, guard)
}
}
/// Builds an environment filter for logging.
///
/// The events are filtered by `default_directive`, unless overridden by `RUST_LOG`.
///
/// # Arguments
/// * `default_directive` - An optional `Directive` that sets the default directive.
/// * `directives` - Additional directives as a comma-separated string.
///
/// # Returns
/// An `eyre::Result<EnvFilter>` that can be used to configure a tracing subscriber.
fn build_env_filter(
default_directive: Option<Directive>,
directives: &str,
) -> eyre::Result<EnvFilter> {
let env_filter = if let Some(default_directive) = default_directive {
EnvFilter::builder().with_default_directive(default_directive).from_env_lossy()
} else {
EnvFilter::builder().from_env_lossy()
};
DEFAULT_ENV_FILTER_DIRECTIVES
.into_iter()
.chain(directives.split(',').filter(|d| !d.is_empty()))
.try_fold(env_filter, |env_filter, directive| {
Ok(env_filter.add_directive(directive.parse()?))
})
}