Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Json structured logs #5784

Merged
merged 31 commits into from
Jan 9, 2024
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
9e14aa0
feat: first structure
loocapro Dec 14, 2023
63cec0c
feat: moving on
loocapro Dec 14, 2023
ae5310e
some naming
loocapro Dec 14, 2023
d687fe5
feat: docs default directive still needs some work
loocapro Dec 15, 2023
b10012f
feat: docs and doctest
loocapro Dec 15, 2023
71eab40
Merge remote-tracking branch 'origin' into json-structured-logs
loocapro Dec 22, 2023
e51d15e
Update crates/tracing/src/layers.rs
loocapro Dec 22, 2023
f24f3c7
Update crates/tracing/src/layers.rs
loocapro Dec 22, 2023
80f0f48
Update crates/tracing/src/layers.rs
loocapro Dec 22, 2023
258415f
Merge branch 'json-structured-logs' of https://github.com/loocapro/re…
loocapro Dec 22, 2023
d507250
fix: string reference for filter
loocapro Dec 22, 2023
55db389
feat: hooking up all components and remove old implementation
loocapro Dec 24, 2023
2913a82
feat: cli mod
loocapro Dec 24, 2023
9127c09
Merge remote-tracking branch 'origin' into json-structured-logs
loocapro Dec 24, 2023
eb8542a
fix: clippy
loocapro Dec 24, 2023
5a8dffe
fix: doctest
loocapro Dec 24, 2023
1ccb72b
fix: doctest
loocapro Dec 24, 2023
406d3d5
sync: merged main and aligned latest changes, removed test tracer imp…
loocapro Jan 1, 2024
d3d63ca
Update bin/reth/src/args/log_args.rs
loocapro Jan 3, 2024
498c0b1
Update bin/reth/src/args/log_args.rs
loocapro Jan 3, 2024
7eb80ad
Update bin/reth/src/args/log_args.rs
loocapro Jan 3, 2024
d3c82f5
Update crates/tracing/src/layers.rs
loocapro Jan 3, 2024
eb8f8fb
Update crates/tracing/src/lib.rs
loocapro Jan 3, 2024
df160e3
Update crates/tracing/src/lib.rs
loocapro Jan 3, 2024
96deb56
review comments
loocapro Jan 3, 2024
fa94619
Merge branch 'json-structured-logs' of https://github.com/loocapro/re…
loocapro Jan 3, 2024
1d2423f
fix: create log dir inside log writer
loocapro Jan 3, 2024
855220b
feat: log stdout filter empty as default
loocapro Jan 4, 2024
f6cccd4
fix: empty stdout filter to info
loocapro Jan 4, 2024
e36d317
Update crates/tracing/src/layers.rs
shekhirin Jan 8, 2024
4ef126c
Update crates/tracing/src/lib.rs
shekhirin Jan 8, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 9 additions & 1 deletion bin/reth/src/cli/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ use reth_primitives::ChainSpec;
use reth_tracing::{
tracing::{metadata::LevelFilter, Level, Subscriber},
tracing_subscriber::{filter::Directive, registry::LookupSpan, EnvFilter},
BoxedLayer, FileWorkerGuard,
BoxedLayer, FileWorkerGuard, LogFormat,
};
use std::{fmt, fmt::Display, sync::Arc};

Expand Down Expand Up @@ -179,6 +179,14 @@ impl<Ext: RethCliExt> Commands<Ext> {
#[derive(Debug, Args)]
#[command(next_help_heading = "Logging")]
pub struct Logs {
/// The format to use for logs written to std.
#[arg(long = "log.std.format", value_name = "FORMAT", global = true, default_value_t = LogFormat::Terminal)]
log_std_format: LogFormat,

/// The format to use for logs written to the log file.
#[arg(long = "log.file.format", value_name = "FORMAT", global = true, default_value_t = LogFormat::Terminal)]
log_file_format: LogFormat,

/// The path to put log files in.
#[arg(long = "log.file.directory", value_name = "PATH", global = true, default_value_t)]
log_file_directory: PlatformPath<LogsDir>,
Expand Down
5 changes: 4 additions & 1 deletion crates/tracing/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@ description = "tracing helpers"

[dependencies]
tracing.workspace = true
tracing-subscriber = { version = "0.3", default-features = false, features = ["env-filter", "fmt"] }
tracing-subscriber = { version = "0.3", default-features = false, features = ["env-filter", "fmt", "json"] }
tracing-appender.workspace = true
tracing-journald = "0.3"
tracing-logfmt = "0.3.3"
rolling-file = "0.2.0"
eyre.workspace = true
clap = { workspace = true, features = ["derive"] }
86 changes: 86 additions & 0 deletions crates/tracing/src/formatter.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
use crate::BoxedLayer;
use clap::ValueEnum;
use std::{fmt, fmt::Display};
use tracing_appender::non_blocking::NonBlocking;
use tracing_subscriber::{EnvFilter, Layer, Registry};

/// Represents the logging format.
///
/// This enum defines the supported formats for logging output.
/// It is used to configure the format layer of a tracing subscriber.
#[derive(Debug, Copy, Clone, ValueEnum, Eq, PartialEq)]
pub enum LogFormat {
/// Represents JSON formatting for logs.
/// This format outputs log records as JSON objects,
/// making it suitable for structured logging.
Json,

/// Represents logfmt (key=value) formatting for logs.
/// This format is concise and human-readable,
/// typically used in command-line applications.
LogFmt,

/// Represents terminal-friendly formatting for logs.
Terminal,
}

impl LogFormat {
/// Applies the specified logging format to create a new layer.
///
/// This method constructs a tracing layer with the selected format,
/// along with additional configurations for filtering and output.
///
/// # Arguments
/// * `filter` - An `EnvFilter` used to determine which log records to output.
/// * `color` - An optional string that enables or disables ANSI color codes in the logs.
/// * `file_writer` - An optional `NonBlocking` writer for directing logs to a file.
///
/// # Returns
/// A `BoxedLayer<Registry>` that can be added to a tracing subscriber.
pub fn apply(
&self,
filter: EnvFilter,
color: Option<String>,
file_writer: Option<NonBlocking>,
) -> BoxedLayer<Registry> {
let ansi = if let Some(color) = color {
std::env::var("RUST_LOG_STYLE").map(|val| val != "never").unwrap_or(color != "never")
} else {
false
};
let target = std::env::var("RUST_LOG_TARGET").map(|val| val != "0").unwrap_or(true);

match self {
LogFormat::Json => {
let layer =
tracing_subscriber::fmt::layer().json().with_ansi(ansi).with_target(target);

if let Some(writer) = file_writer {
layer.with_writer(writer).with_filter(filter).boxed()
} else {
layer.with_filter(filter).boxed()
}
}
LogFormat::LogFmt => tracing_logfmt::layer().with_filter(filter).boxed(),
LogFormat::Terminal => {
let layer = tracing_subscriber::fmt::layer().with_ansi(ansi).with_target(target);

if let Some(writer) = file_writer {
layer.with_writer(writer).with_filter(filter).boxed()
} else {
layer.with_filter(filter).boxed()
}
}
}
}
}

impl Display for LogFormat {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
LogFormat::Json => write!(f, "json"),
LogFormat::LogFmt => write!(f, "logfmt"),
LogFormat::Terminal => write!(f, "terminal"),
}
}
}
172 changes: 172 additions & 0 deletions crates/tracing/src/layers.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
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, BoxedLayer, FileWorkerGuard};

/// Default [directives](Directive) for [EnvFilter] which disables high-frequency debug logs from
/// `hyper` and `trust-dns`
const DEFAULT_ENV_FILTER_DIRECTIVES: [&str; 3] =
["hyper::proto::h1=off", "trust_dns_proto=off", "atrust_dns_resolver=off"];
shekhirin marked this conversation as resolved.
Show resolved Hide resolved

/// 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(','))
.try_fold(env_filter, |env_filter, directive| {
Ok(env_filter.add_directive(directive.parse()?))
})
}

/// 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,
directive: Directive,
filter: &str,
color: Option<String>,
) -> eyre::Result<()> {
let filter = build_env_filter(Some(directive), filter)?;
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: String,
loocapro marked this conversation as resolved.
Show resolved Hide resolved
file_info: FileInfo,
) -> eyre::Result<FileWorkerGuard> {
let log_dir = file_info.create_log_dir();
let (writer, guard) = file_info.create_log_writer(log_dir);

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.

loocapro marked this conversation as resolved.
Show resolved Hide resolved
#[derive(Debug)]
pub struct FileInfo {
dir: PathBuf,
file_name: String,
max_size_bytes: u64,
max_files: usize,
}

impl FileInfo {
/// 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.
///
/// # Arguments
/// * `log_dir` - Reference to the log directory path.
///
/// # Returns
/// A tuple containing the non-blocking writer and its associated worker guard.
fn create_log_writer(
&self,
log_dir: &Path,
loocapro marked this conversation as resolved.
Show resolved Hide resolved
) -> (tracing_appender::non_blocking::NonBlocking, WorkerGuard) {
let (writer, guard) = tracing_appender::non_blocking(
RollingFileAppender::new(
log_dir.join(AsRef::<Path>::as_ref(&self.file_name)),
loocapro marked this conversation as resolved.
Show resolved Hide resolved
RollingConditionBasic::new().max_size(self.max_size_bytes),
self.max_files,
)
.expect("Could not initialize file logging"),
);
(writer, guard)
}
}
Loading
Loading