diff --git a/Cargo.lock b/Cargo.lock index 58279ad1ec..f07cbcca59 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -728,6 +728,16 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "crossbeam-channel" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200" +dependencies = [ + "cfg-if", + "crossbeam-utils", +] + [[package]] name = "crossbeam-deque" version = "0.8.3" @@ -3016,6 +3026,7 @@ dependencies = [ "sys-info", "thiserror", "tokio", + "tracing-appender", "tracing-subscriber 0.3.17", "ureq", ] @@ -4667,6 +4678,17 @@ dependencies = [ "tracing-core", ] +[[package]] +name = "tracing-appender" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09d48f71a791638519505cefafe162606f706c25592e4bde4d97600c0195312e" +dependencies = [ + "crossbeam-channel", + "time", + "tracing-subscriber 0.3.17", +] + [[package]] name = "tracing-attributes" version = "0.1.27" diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 094a221b25..ca14f92d84 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -103,6 +103,9 @@ version = "1.0" version = "1.28" features = [ "rt" ] +[dependencies.tracing-appender] +version = "0.2" + [dependencies.tracing-subscriber] version = "0.3" features = [ "env-filter" ] diff --git a/cli/src/commands/start.rs b/cli/src/commands/start.rs index 9de382a8fb..259b78fa2e 100644 --- a/cli/src/commands/start.rs +++ b/cli/src/commands/start.rs @@ -121,8 +121,9 @@ pub struct Start { impl Start { /// Starts the snarkOS node. pub fn parse(self) -> Result { - // Initialize the logger. - let log_receiver = crate::helpers::initialize_logger(self.verbosity, self.nodisplay, self.logfile.clone()); + // Initialize the logger; the log guard is moved into the async block. + let (log_receiver, _log_guard) = + crate::helpers::initialize_logger(self.verbosity, self.nodisplay, self.logfile.clone()); // Initialize the runtime. Self::runtime().block_on(async move { // Clone the configurations. diff --git a/cli/src/helpers/log_writer.rs b/cli/src/helpers/log_writer.rs index b99b126ed2..def961afdf 100644 --- a/cli/src/helpers/log_writer.rs +++ b/cli/src/helpers/log_writer.rs @@ -47,3 +47,34 @@ impl io::Write for LogWriter { Ok(()) } } + +fn strip_newlines(buf: &[u8]) -> Vec { + // Remove all newlines and then append one if the buffer ends with one; + // it should always be the case, but it's cheap to make extra sure. + buf.iter() + .copied() + .filter(|&b| b != b'\n') + .chain(if matches!(buf.last(), Some(b'\n')) { Some(b'\n') } else { None }) + .collect() +} + +pub struct WriterWrapper(pub W); + +impl io::Write for WriterWrapper { + /// Writes the given buffer into the log writer. + fn write(&mut self, buf: &[u8]) -> io::Result { + let sanitized = strip_newlines(buf); + // Force all the bytes to be written at once, otherwise + // buffer accounting could fail, resulting in random + // artifacts being written additionally. + self.0.write_all(&sanitized)?; + // Report the unsanitized size as the number of bytes + // written for the same reason as above. + Ok(buf.len()) + } + + /// Flushes the log writer (no-op). + fn flush(&mut self) -> io::Result<()> { + self.0.flush() + } +} diff --git a/cli/src/helpers/logger.rs b/cli/src/helpers/logger.rs index 04c4bedbf7..875ec23da5 100644 --- a/cli/src/helpers/logger.rs +++ b/cli/src/helpers/logger.rs @@ -12,11 +12,12 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::helpers::LogWriter; +use crate::helpers::{LogWriter, WriterWrapper}; use crossterm::tty::IsTty; use std::{fs::File, io, path::Path}; use tokio::sync::mpsc; +use tracing_appender::non_blocking::{NonBlockingBuilder, WorkerGuard}; use tracing_subscriber::{ layer::{Layer, SubscriberExt}, util::SubscriberInitExt, @@ -34,7 +35,11 @@ use tracing_subscriber::{ /// 5 => info, debug, trace, snarkos_node_router=trace /// 6 => info, debug, trace, snarkos_node_tcp=trace /// ``` -pub fn initialize_logger>(verbosity: u8, nodisplay: bool, logfile: P) -> mpsc::Receiver> { +pub fn initialize_logger>( + verbosity: u8, + nodisplay: bool, + logfile: P, +) -> (mpsc::Receiver>, WorkerGuard) { match verbosity { 0 => std::env::set_var("RUST_LOG", "info"), 1 => std::env::set_var("RUST_LOG", "debug"), @@ -85,6 +90,7 @@ pub fn initialize_logger>(verbosity: u8, nodisplay: bool, logfile // Create a file to write logs to. let logfile = File::options().append(true).create(true).open(logfile).expect("Failed to open the file for writing logs"); + let (file_writer, guard) = NonBlockingBuilder::default().buffered_lines_limit(256).finish(WriterWrapper(logfile)); // Initialize the log channel. let (log_sender, log_receiver) = mpsc::channel(1024); @@ -109,13 +115,14 @@ pub fn initialize_logger>(verbosity: u8, nodisplay: bool, logfile // Add layer redirecting logs to the file tracing_subscriber::fmt::Layer::default() .with_ansi(false) - .with_writer(logfile) + .with_writer(file_writer) .with_target(verbosity > 2) .with_filter(filter2), ) .try_init(); - log_receiver + // The file logger guard must exist as long as the node. + (log_receiver, guard) } /// Returns the welcome message as a string.