From e8cc948a25af35f2c06b60d251242f7c2a45b10b Mon Sep 17 00:00:00 2001 From: Chris Olszewski Date: Tue, 13 Aug 2024 17:42:42 -0400 Subject: [PATCH 1/7] chore(tracing): remove majority of custom type defs --- crates/turborepo-lib/src/tracing.rs | 38 +++++++++-------------------- 1 file changed, 12 insertions(+), 26 deletions(-) diff --git a/crates/turborepo-lib/src/tracing.rs b/crates/turborepo-lib/src/tracing.rs index 28b54768e1f91..93eed053905e9 100644 --- a/crates/turborepo-lib/src/tracing.rs +++ b/crates/turborepo-lib/src/tracing.rs @@ -7,7 +7,6 @@ use owo_colors::{ }; use tracing::{field::Visit, metadata::LevelFilter, trace, Event, Level, Subscriber}; use tracing_appender::{non_blocking::NonBlocking, rolling::RollingFileAppender}; -use tracing_chrome::ChromeLayer; pub use tracing_subscriber::reload::Error; use tracing_subscriber::{ filter::Filtered, @@ -48,36 +47,20 @@ type StdErrLogFiltered = Filtered; /// `StdErrLogLayered`, which forms the base for the next layer. type StdErrLogLayered = layer::Layered; -/// A logger that spits lines into a file, using the standard formatter. -/// It is applied on top of the `StdErrLogLayered` layer. -type DaemonLog = fmt::Layer; -/// This layer can be reloaded. `None` means the layer is disabled. -type DaemonReload = reload::Layer, StdErrLogLayered>; -/// We filter this using a custom filter that only logs events -/// - with evel `TRACE` or higher for the `turborepo` target -/// - with level `INFO` or higher for all other targets -type DaemonLogFiltered = Filtered; /// When the `DaemonLogFiltered` is applied to the `StdErrLogLayered`, we get a /// `DaemonLogLayered`, which forms the base for the next layer. -type DaemonLogLayered = layer::Layered; +type DaemonLogLayered = layer::Layered, StdErrLogLayered>; -/// A logger that converts events to chrome tracing format and writes them -/// to a file. It is applied on top of the `DaemonLogLayered` layer. -type ChromeLog = ChromeLayer; -/// This layer can be reloaded. `None` means the layer is disabled. -type ChromeReload = reload::Layer, DaemonLogLayered>; -/// When the `ChromeLogFiltered` is applied to the `DaemonLogLayered`, we get a -/// `ChromeLogLayered`, which forms the base for the next layer. -type ChromeLogLayered = layer::Layered; +type DynamicLayer = Box + Send + Sync + 'static>; pub struct TurboSubscriber { - daemon_update: Handle, StdErrLogLayered>, + daemon_update: Handle>, StdErrLogLayered>, /// The non-blocking file logger only continues to log while this guard is /// held. We keep it here so that it doesn't get dropped. daemon_guard: Mutex>, - chrome_update: Handle, DaemonLogLayered>, + chrome_update: Handle>, DaemonLogLayered>, chrome_guard: Mutex>, #[cfg(feature = "pprof")] @@ -134,10 +117,10 @@ impl TurboSubscriber { .with_filter(env_filter(LevelFilter::WARN)); // we set this layer to None to start with, effectively disabling it - let (logrotate, daemon_update) = reload::Layer::new(Option::::None); - let logrotate: DaemonLogFiltered = logrotate.with_filter(env_filter(LevelFilter::INFO)); + let (logrotate, daemon_update) = reload::Layer::new(Option::<_>::None); + let logrotate = logrotate.with_filter(env_filter(LevelFilter::INFO)).boxed(); - let (chrome, chrome_update) = reload::Layer::new(Option::::None); + let (chrome, chrome_update) = reload::Layer::new(Option::>::None); let registry = Registry::default() .with(stderr) @@ -171,9 +154,10 @@ impl TurboSubscriber { let (file_writer, guard) = tracing_appender::non_blocking(appender); trace!("created non-blocking file writer"); - let layer: DaemonLog = tracing_subscriber::fmt::layer() + let layer: DynamicLayer = tracing_subscriber::fmt::layer() .with_writer(file_writer) - .with_ansi(false); + .with_ansi(false) + .boxed(); self.daemon_update.reload(Some(layer))?; self.daemon_guard @@ -198,6 +182,8 @@ impl TurboSubscriber { .trace_style(tracing_chrome::TraceStyle::Async) .build(); + let layer: DynamicLayer = layer.boxed(); + self.chrome_update.reload(Some(layer))?; self.chrome_guard .lock() From ec5654fbc1dd78264024c70f724c5755d6b3d20c Mon Sep 17 00:00:00 2001 From: Chris Olszewski Date: Tue, 13 Aug 2024 18:10:20 -0400 Subject: [PATCH 2/7] chore(tracing): simplify subscriber types --- crates/turborepo-lib/src/tracing.rs | 62 ++++++++++------------------- 1 file changed, 22 insertions(+), 40 deletions(-) diff --git a/crates/turborepo-lib/src/tracing.rs b/crates/turborepo-lib/src/tracing.rs index 93eed053905e9..eaea16eb1eb30 100644 --- a/crates/turborepo-lib/src/tracing.rs +++ b/crates/turborepo-lib/src/tracing.rs @@ -1,4 +1,4 @@ -use std::{io::Stderr, marker::PhantomData, path::Path, sync::Mutex}; +use std::{io, marker::PhantomData, path::Path, sync::Mutex}; use chrono::Local; use owo_colors::{ @@ -6,15 +6,10 @@ use owo_colors::{ Color, OwoColorize, }; use tracing::{field::Visit, metadata::LevelFilter, trace, Event, Level, Subscriber}; -use tracing_appender::{non_blocking::NonBlocking, rolling::RollingFileAppender}; +use tracing_appender::rolling::RollingFileAppender; pub use tracing_subscriber::reload::Error; use tracing_subscriber::{ - filter::Filtered, - fmt::{ - self, - format::{DefaultFields, Writer}, - FmtContext, FormatEvent, FormatFields, MakeWriter, - }, + fmt::{self, format::Writer, FmtContext, FormatEvent, FormatFields}, layer, prelude::*, registry::LookupSpan, @@ -23,44 +18,30 @@ use tracing_subscriber::{ }; use turborepo_ui::ColorConfig; -// a lot of types to make sure we record the right relationships - -/// Note that we cannot express the type of `std::io::stderr` directly, so -/// use zero-size wrapper to call the function. -struct StdErrWrapper {} - -impl<'a> MakeWriter<'a> for StdErrWrapper { - type Writer = Stderr; - - fn make_writer(&'a self) -> Self::Writer { - std::io::stderr() - } -} +/// Helper type definition for hiding exact type information +/// It still leaks the underlying subscriber type that the layer targets +type DynamicLayer = Box + Send + Sync + 'static>; -/// A basic logger that logs to stderr using the TurboFormatter. -/// The first generic parameter refers to the previous layer, which -/// is in this case the default layer (`Registry`). -type StdErrLog = fmt::Layer; -/// We filter this using an EnvFilter. -type StdErrLogFiltered = Filtered; -/// When the `StdErrLogFiltered` is applied to the `Registry`, we get a -/// `StdErrLogLayered`, which forms the base for the next layer. -type StdErrLogLayered = layer::Layered; +/// Expressing layer::Layered by hand results in us specifying the subscriber +/// twice even though they will always be the same. +/// These are created by using `with` on a subscriber hence the nesting that +/// happens. +type DynamicLayered = layer::Layered, S>; -/// When the `DaemonLogFiltered` is applied to the `StdErrLogLayered`, we get a -/// `DaemonLogLayered`, which forms the base for the next layer. -type DaemonLogLayered = layer::Layered, StdErrLogLayered>; +/// The subscriber we set up for logs written to stderr +type StdErrSubscriber = DynamicLayered; -type DynamicLayer = Box + Send + Sync + 'static>; +/// The subscriber for daemon +type DaemonLogSubscriber = DynamicLayered; pub struct TurboSubscriber { - daemon_update: Handle>, StdErrLogLayered>, + daemon_update: Handle>, StdErrSubscriber>, /// The non-blocking file logger only continues to log while this guard is /// held. We keep it here so that it doesn't get dropped. daemon_guard: Mutex>, - chrome_update: Handle>, DaemonLogLayered>, + chrome_update: Handle>, DaemonLogSubscriber>, chrome_guard: Mutex>, #[cfg(feature = "pprof")] @@ -110,11 +91,12 @@ impl TurboSubscriber { }; let stderr = fmt::layer() - .with_writer(StdErrWrapper {}) + .with_writer(io::stderr) .event_format(TurboFormatter::new_with_ansi( !color_config.should_strip_ansi, )) - .with_filter(env_filter(LevelFilter::WARN)); + .with_filter(env_filter(LevelFilter::WARN)) + .boxed(); // we set this layer to None to start with, effectively disabling it let (logrotate, daemon_update) = reload::Layer::new(Option::<_>::None); @@ -154,7 +136,7 @@ impl TurboSubscriber { let (file_writer, guard) = tracing_appender::non_blocking(appender); trace!("created non-blocking file writer"); - let layer: DynamicLayer = tracing_subscriber::fmt::layer() + let layer: DynamicLayer = tracing_subscriber::fmt::layer() .with_writer(file_writer) .with_ansi(false) .boxed(); @@ -182,7 +164,7 @@ impl TurboSubscriber { .trace_style(tracing_chrome::TraceStyle::Async) .build(); - let layer: DynamicLayer = layer.boxed(); + let layer: DynamicLayer = layer.boxed(); self.chrome_update.reload(Some(layer))?; self.chrome_guard From bec4ba313ad99fcebdb155615233a08b331b8086 Mon Sep 17 00:00:00 2001 From: Chris Olszewski Date: Tue, 13 Aug 2024 18:16:01 -0400 Subject: [PATCH 3/7] chore(tracing): remove unused type hints --- crates/turborepo-lib/src/tracing.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/turborepo-lib/src/tracing.rs b/crates/turborepo-lib/src/tracing.rs index eaea16eb1eb30..ed1420ba91d4c 100644 --- a/crates/turborepo-lib/src/tracing.rs +++ b/crates/turborepo-lib/src/tracing.rs @@ -99,10 +99,10 @@ impl TurboSubscriber { .boxed(); // we set this layer to None to start with, effectively disabling it - let (logrotate, daemon_update) = reload::Layer::new(Option::<_>::None); + let (logrotate, daemon_update) = reload::Layer::new(None); let logrotate = logrotate.with_filter(env_filter(LevelFilter::INFO)).boxed(); - let (chrome, chrome_update) = reload::Layer::new(Option::>::None); + let (chrome, chrome_update) = reload::Layer::new(None); let registry = Registry::default() .with(stderr) @@ -136,7 +136,7 @@ impl TurboSubscriber { let (file_writer, guard) = tracing_appender::non_blocking(appender); trace!("created non-blocking file writer"); - let layer: DynamicLayer = tracing_subscriber::fmt::layer() + let layer = tracing_subscriber::fmt::layer() .with_writer(file_writer) .with_ansi(false) .boxed(); @@ -164,7 +164,7 @@ impl TurboSubscriber { .trace_style(tracing_chrome::TraceStyle::Async) .build(); - let layer: DynamicLayer = layer.boxed(); + let layer = layer.boxed(); self.chrome_update.reload(Some(layer))?; self.chrome_guard From d37d376a2f6d7480755ed68fd6c22e537ecc5783 Mon Sep 17 00:00:00 2001 From: Chris Olszewski Date: Mon, 19 Aug 2024 09:16:35 -0400 Subject: [PATCH 4/7] chore(tracing): move env filter creation to helper fn --- crates/turborepo-lib/src/tracing.rs | 40 ++++++++++++++++------------- 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/crates/turborepo-lib/src/tracing.rs b/crates/turborepo-lib/src/tracing.rs index ed1420ba91d4c..4f552b99f65a9 100644 --- a/crates/turborepo-lib/src/tracing.rs +++ b/crates/turborepo-lib/src/tracing.rs @@ -35,6 +35,7 @@ type StdErrSubscriber = DynamicLayered; type DaemonLogSubscriber = DynamicLayered; pub struct TurboSubscriber { + level_override: Option, daemon_update: Handle>, StdErrSubscriber>, /// The non-blocking file logger only continues to log while this guard is @@ -74,33 +75,19 @@ impl TurboSubscriber { _ => Some(LevelFilter::TRACE), }; - let env_filter = |level: LevelFilter| { - let filter = EnvFilter::builder() - .with_default_directive(level.into()) - .with_env_var("TURBO_LOG_VERBOSITY") - .from_env_lossy() - .add_directive("reqwest=error".parse().unwrap()) - .add_directive("hyper=warn".parse().unwrap()) - .add_directive("h2=warn".parse().unwrap()); - - if let Some(max_level) = level_override { - filter.add_directive(max_level.into()) - } else { - filter - } - }; - let stderr = fmt::layer() .with_writer(io::stderr) .event_format(TurboFormatter::new_with_ansi( !color_config.should_strip_ansi, )) - .with_filter(env_filter(LevelFilter::WARN)) + .with_filter(Self::env_filter(LevelFilter::WARN, level_override)) .boxed(); // we set this layer to None to start with, effectively disabling it let (logrotate, daemon_update) = reload::Layer::new(None); - let logrotate = logrotate.with_filter(env_filter(LevelFilter::INFO)).boxed(); + let logrotate = logrotate + .with_filter(Self::env_filter(LevelFilter::INFO, level_override)) + .boxed(); let (chrome, chrome_update) = reload::Layer::new(None); @@ -119,6 +106,7 @@ impl TurboSubscriber { registry.init(); Self { + level_override, daemon_update, daemon_guard: Mutex::new(None), chrome_update, @@ -174,6 +162,22 @@ impl TurboSubscriber { Ok(()) } + + fn env_filter(level: LevelFilter, level_override: Option) -> EnvFilter { + let filter = EnvFilter::builder() + .with_default_directive(level.into()) + .with_env_var("TURBO_LOG_VERBOSITY") + .from_env_lossy() + .add_directive("reqwest=error".parse().unwrap()) + .add_directive("hyper=warn".parse().unwrap()) + .add_directive("h2=warn".parse().unwrap()); + + if let Some(max_level) = level_override { + filter.add_directive(max_level.into()) + } else { + filter + } + } } impl Drop for TurboSubscriber { From f3938894ab70006f3499703088ebbdca9dd2ea46 Mon Sep 17 00:00:00 2001 From: Chris Olszewski Date: Mon, 19 Aug 2024 09:29:05 -0400 Subject: [PATCH 5/7] chore(tracing): move configuration to own struct --- crates/turborepo-lib/src/tracing.rs | 67 ++++++++++++++++++++++------- 1 file changed, 51 insertions(+), 16 deletions(-) diff --git a/crates/turborepo-lib/src/tracing.rs b/crates/turborepo-lib/src/tracing.rs index 4f552b99f65a9..f22b36a08c821 100644 --- a/crates/turborepo-lib/src/tracing.rs +++ b/crates/turborepo-lib/src/tracing.rs @@ -35,7 +35,7 @@ type StdErrSubscriber = DynamicLayered; type DaemonLogSubscriber = DynamicLayered; pub struct TurboSubscriber { - level_override: Option, + config: TracingConfig, daemon_update: Handle>, StdErrSubscriber>, /// The non-blocking file logger only continues to log while this guard is @@ -49,6 +49,13 @@ pub struct TurboSubscriber { pprof_guard: pprof::ProfilerGuard<'static>, } +/// Configuration options for the TurboSubscriber +#[derive(Debug, Clone, Copy)] +struct TracingConfig { + level_override: Option, + emit_ansi: bool, +} + impl TurboSubscriber { /// Sets up the tracing subscriber, with a default stderr layer using the /// TurboFormatter. @@ -68,25 +75,14 @@ impl TurboSubscriber { /// - `enable_chrome_tracing` enables logging to a file, using the chrome /// tracing formatter. pub fn new_with_verbosity(verbosity: usize, color_config: &ColorConfig) -> Self { - let level_override = match verbosity { - 0 => None, - 1 => Some(LevelFilter::INFO), - 2 => Some(LevelFilter::DEBUG), - _ => Some(LevelFilter::TRACE), - }; + let config = TracingConfig::new(verbosity, color_config); - let stderr = fmt::layer() - .with_writer(io::stderr) - .event_format(TurboFormatter::new_with_ansi( - !color_config.should_strip_ansi, - )) - .with_filter(Self::env_filter(LevelFilter::WARN, level_override)) - .boxed(); + let stderr = config.stderr_subscriber(); // we set this layer to None to start with, effectively disabling it let (logrotate, daemon_update) = reload::Layer::new(None); let logrotate = logrotate - .with_filter(Self::env_filter(LevelFilter::INFO, level_override)) + .with_filter(config.env_filter(LevelFilter::INFO)) .boxed(); let (chrome, chrome_update) = reload::Layer::new(None); @@ -106,7 +102,7 @@ impl TurboSubscriber { registry.init(); Self { - level_override, + config, daemon_update, daemon_guard: Mutex::new(None), chrome_update, @@ -208,6 +204,45 @@ impl Drop for TurboSubscriber { } } +impl TracingConfig { + pub fn new(verbosity: usize, color_config: &ColorConfig) -> Self { + let level_override = match verbosity { + 0 => None, + 1 => Some(LevelFilter::INFO), + 2 => Some(LevelFilter::DEBUG), + _ => Some(LevelFilter::TRACE), + }; + Self { + level_override, + emit_ansi: !color_config.should_strip_ansi, + } + } + + pub fn env_filter(&self, level: LevelFilter) -> EnvFilter { + let filter = EnvFilter::builder() + .with_default_directive(level.into()) + .with_env_var("TURBO_LOG_VERBOSITY") + .from_env_lossy() + .add_directive("reqwest=error".parse().unwrap()) + .add_directive("hyper=warn".parse().unwrap()) + .add_directive("h2=warn".parse().unwrap()); + + if let Some(max_level) = self.level_override { + filter.add_directive(max_level.into()) + } else { + filter + } + } + + pub fn stderr_subscriber(&self) -> DynamicLayer { + fmt::layer() + .with_writer(io::stderr) + .event_format(TurboFormatter::new_with_ansi(self.emit_ansi)) + .with_filter(self.env_filter(LevelFilter::WARN)) + .boxed() + } +} + /// The formatter for TURBOREPO /// /// This is a port of the go formatter, which follows a few main rules: From eb0af601f1d4a79364cbc215cfcdd74c654ab578 Mon Sep 17 00:00:00 2001 From: Chris Olszewski Date: Mon, 19 Aug 2024 10:34:59 -0400 Subject: [PATCH 6/7] feat(tracing): add ability to redirect logs to file --- Cargo.lock | 1 + Cargo.toml | 1 + crates/turborepo-lib/Cargo.toml | 1 + crates/turborepo-lib/src/tracing.rs | 84 ++++++++++++++++++++++++++--- 4 files changed, 79 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 663eef320bd18..9874366dee52a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5509,6 +5509,7 @@ dependencies = [ "turborepo-vercel-api-mock", "twox-hash", "uds_windows", + "uuid", "wax", "webbrowser", "which", diff --git a/Cargo.toml b/Cargo.toml index 2c5d4623019f6..78bde944610e8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -188,6 +188,7 @@ triomphe = "0.1.13" tui-term = { version = "0.1.9", default-features = false } url = "2.2.2" urlencoding = "2.1.2" +uuid = "1.5.0" webbrowser = "0.8.7" which = "4.4.0" unicode-segmentation = "1.10.1" diff --git a/crates/turborepo-lib/Cargo.toml b/crates/turborepo-lib/Cargo.toml index 98eeb115c7835..6fdc912a463b2 100644 --- a/crates/turborepo-lib/Cargo.toml +++ b/crates/turborepo-lib/Cargo.toml @@ -131,6 +131,7 @@ turborepo-unescape = { workspace = true } turborepo-vercel-api = { path = "../turborepo-vercel-api" } twox-hash = "1.6.3" uds_windows = "1.0.2" +uuid = { workspace = true } wax = { workspace = true } webbrowser = { workspace = true } which = { workspace = true } diff --git a/crates/turborepo-lib/src/tracing.rs b/crates/turborepo-lib/src/tracing.rs index f22b36a08c821..ed61a8bccc9d6 100644 --- a/crates/turborepo-lib/src/tracing.rs +++ b/crates/turborepo-lib/src/tracing.rs @@ -1,4 +1,4 @@ -use std::{io, marker::PhantomData, path::Path, sync::Mutex}; +use std::{fs, io, marker::PhantomData, path::Path, sync::Mutex}; use chrono::Local; use owo_colors::{ @@ -16,7 +16,9 @@ use tracing_subscriber::{ reload::{self, Handle}, EnvFilter, Layer, Registry, }; +use turbopath::{AbsoluteSystemPath, AbsoluteSystemPathBuf}; use turborepo_ui::ColorConfig; +use uuid::Uuid; /// Helper type definition for hiding exact type information /// It still leaks the underlying subscriber type that the layer targets @@ -36,6 +38,9 @@ type DaemonLogSubscriber = DynamicLayered; pub struct TurboSubscriber { config: TracingConfig, + stderr_update: Handle, Registry>, + file_guard: Mutex>, + daemon_update: Handle>, StdErrSubscriber>, /// The non-blocking file logger only continues to log while this guard is @@ -56,6 +61,14 @@ struct TracingConfig { emit_ansi: bool, } +/// Guard for writing logs to a given file +/// Will print log file location on drop +#[derive(Debug)] +struct LogFileGuard { + log_path: AbsoluteSystemPathBuf, + guard: tracing_appender::non_blocking::WorkerGuard, +} + impl TurboSubscriber { /// Sets up the tracing subscriber, with a default stderr layer using the /// TurboFormatter. @@ -77,7 +90,13 @@ impl TurboSubscriber { pub fn new_with_verbosity(verbosity: usize, color_config: &ColorConfig) -> Self { let config = TracingConfig::new(verbosity, color_config); - let stderr = config.stderr_subscriber(); + let (stderr, stderr_guard) = config.stderr_subscriber(None); + debug_assert!( + stderr_guard.is_none(), + "no guard should be created when writing to stderr" + ); + let (stderr, stderr_update) = reload::Layer::new(stderr); + let stderr = stderr.boxed(); // we set this layer to None to start with, effectively disabling it let (logrotate, daemon_update) = reload::Layer::new(None); @@ -103,6 +122,8 @@ impl TurboSubscriber { Self { config, + stderr_update, + file_guard: Mutex::default(), daemon_update, daemon_guard: Mutex::new(None), chrome_update, @@ -159,6 +180,16 @@ impl TurboSubscriber { Ok(()) } + pub fn redirect_logs_to_file(&self, repo_root: &AbsoluteSystemPath) -> Result<(), Error> { + let (log_writer_layer, log_file_guard) = self.config.stderr_subscriber(Some(repo_root)); + + self.stderr_update.reload(log_writer_layer)?; + let mut file_guard_lock = self.file_guard.lock().expect("file guard lock poisoned"); + *file_guard_lock = log_file_guard; + + Ok(()) + } + fn env_filter(level: LevelFilter, level_override: Option) -> EnvFilter { let filter = EnvFilter::builder() .with_default_directive(level.into()) @@ -234,12 +265,49 @@ impl TracingConfig { } } - pub fn stderr_subscriber(&self) -> DynamicLayer { - fmt::layer() - .with_writer(io::stderr) - .event_format(TurboFormatter::new_with_ansi(self.emit_ansi)) - .with_filter(self.env_filter(LevelFilter::WARN)) - .boxed() + pub fn stderr_subscriber( + &self, + repo_root: Option<&AbsoluteSystemPath>, + ) -> (DynamicLayer, Option) { + let layer = fmt::layer().event_format(TurboFormatter::new_with_ansi(self.emit_ansi)); + let filter = self.env_filter(LevelFilter::WARN); + if let Some((log_path, log_file)) = repo_root.and_then(|path| { + Self::open_log_file(path) + .inspect_err(|e| tracing::warn!("unable to setup log file: {e}")) + .ok() + }) { + let (non_blocking, guard) = tracing_appender::non_blocking(log_file); + ( + layer.with_writer(non_blocking).with_filter(filter).boxed(), + Some(LogFileGuard { log_path, guard }), + ) + } else { + ( + layer.with_writer(io::stderr).with_filter(filter).boxed(), + None, + ) + } + } + + fn open_log_file( + repo_root: &AbsoluteSystemPath, + ) -> io::Result<(AbsoluteSystemPathBuf, fs::File)> { + let uuid = Uuid::new_v4().hyphenated().to_string(); + let log_path = repo_root.join_components(&[".turbo", "logs", uuid.as_str()]); + log_path.ensure_dir()?; + let mut open_opts = fs::OpenOptions::new(); + let file = open_opts + .create(true) + .write(true) + .truncate(true) + .open(&log_path)?; + Ok((log_path, file)) + } +} + +impl Drop for LogFileGuard { + fn drop(&mut self) { + eprintln!("turbo logs written to {}", self.log_path); } } From d9539ead35553740a7bd9ba57e1609e0d5d96eb1 Mon Sep 17 00:00:00 2001 From: Chris Olszewski Date: Tue, 20 Aug 2024 09:26:28 -0400 Subject: [PATCH 7/7] feat(tui): redirect logs to file on TUI startup (WIP) --- crates/turborepo-lib/src/cli/mod.rs | 4 ++-- crates/turborepo-lib/src/commands/run.rs | 15 ++++++++++++--- crates/turborepo-lib/src/run/mod.rs | 9 ++++++++- crates/turborepo-lib/src/run/watch.rs | 16 ++++++++++------ crates/turborepo-lib/src/tracing.rs | 2 +- 5 files changed, 33 insertions(+), 13 deletions(-) diff --git a/crates/turborepo-lib/src/cli/mod.rs b/crates/turborepo-lib/src/cli/mod.rs index 0495fe64f2457..a8176f3340928 100644 --- a/crates/turborepo-lib/src/cli/mod.rs +++ b/crates/turborepo-lib/src/cli/mod.rs @@ -1270,7 +1270,7 @@ pub async fn run( } run_args.track(&event); - let exit_code = run::run(base, event).await.inspect(|code| { + let exit_code = run::run(base, event, logger).await.inspect(|code| { if *code != 0 { error!("run failed: command exited ({code})"); } @@ -1282,7 +1282,7 @@ pub async fn run( event.track_call(); let base = CommandBase::new(cli_args, repo_root, version, color_config); - let mut client = WatchClient::new(base, event).await?; + let mut client = WatchClient::new(base, event, logger).await?; client.start().await?; // We only exit if we get a signal, so we return a non-zero exit code return Ok(1); diff --git a/crates/turborepo-lib/src/commands/run.rs b/crates/turborepo-lib/src/commands/run.rs index fe9c251eedbe7..d7467ec0b7bad 100644 --- a/crates/turborepo-lib/src/commands/run.rs +++ b/crates/turborepo-lib/src/commands/run.rs @@ -3,7 +3,12 @@ use std::future::Future; use tracing::error; use turborepo_telemetry::events::command::CommandEventBuilder; -use crate::{commands::CommandBase, run, run::builder::RunBuilder, signal::SignalHandler}; +use crate::{ + commands::CommandBase, + run::{self, builder::RunBuilder}, + signal::SignalHandler, + tracing::TurboSubscriber, +}; #[cfg(windows)] pub fn get_signal() -> Result>, run::Error> { @@ -31,7 +36,11 @@ pub fn get_signal() -> Result>, run::Error> { }) } -pub async fn run(base: CommandBase, telemetry: CommandEventBuilder) -> Result { +pub async fn run( + base: CommandBase, + telemetry: CommandEventBuilder, + logger: &TurboSubscriber, +) -> Result { let signal = get_signal()?; let handler = SignalHandler::new(signal); @@ -44,7 +53,7 @@ pub async fn run(base: CommandBase, telemetry: CommandEventBuilder) -> Result Result>)>, Error> { // Print prelude here as this needs to happen before the UI is started if self.should_print_prelude { @@ -205,6 +207,11 @@ impl Run { if task_names.is_empty() { return Ok(None); } + // Redirect any tracing logs to a file to prevent logs from corrupting + // the TUI + if let Err(e) = logger.redirect_logs_to_file(&self.repo_root) { + warn!("failed to redirect logs to file: {e}"); + } let (sender, receiver) = AppSender::new(); let handle = tokio::task::spawn_blocking(move || tui::run_app(task_names, receiver)); diff --git a/crates/turborepo-lib/src/run/watch.rs b/crates/turborepo-lib/src/run/watch.rs index 721f50c260d81..2e7ae848c689f 100644 --- a/crates/turborepo-lib/src/run/watch.rs +++ b/crates/turborepo-lib/src/run/watch.rs @@ -14,12 +14,12 @@ use turborepo_ui::{tui, tui::AppSender}; use crate::{ cli::{Command, RunArgs}, - commands, - commands::CommandBase, + commands::{self, CommandBase}, daemon::{proto, DaemonConnectorError, DaemonError}, - get_version, opts, run, - run::{builder::RunBuilder, scope::target_selector::InvalidSelectorError, Run}, + get_version, opts, + run::{self, builder::RunBuilder, scope::target_selector::InvalidSelectorError, Run}, signal::SignalHandler, + tracing::TurboSubscriber, DaemonConnector, DaemonPaths, }; @@ -103,7 +103,11 @@ pub enum Error { } impl WatchClient { - pub async fn new(base: CommandBase, telemetry: CommandEventBuilder) -> Result { + pub async fn new( + base: CommandBase, + telemetry: CommandEventBuilder, + logger: &TurboSubscriber, + ) -> Result { let signal = commands::run::get_signal()?; let handler = SignalHandler::new(signal); @@ -123,7 +127,7 @@ impl WatchClient { let watched_packages = run.get_relevant_packages(); - let (sender, handle) = run.start_experimental_ui()?.unzip(); + let (sender, handle) = run.start_experimental_ui(logger)?.unzip(); let connector = DaemonConnector { can_start_server: true, diff --git a/crates/turborepo-lib/src/tracing.rs b/crates/turborepo-lib/src/tracing.rs index ed61a8bccc9d6..b57dc94c99c89 100644 --- a/crates/turborepo-lib/src/tracing.rs +++ b/crates/turborepo-lib/src/tracing.rs @@ -278,7 +278,7 @@ impl TracingConfig { }) { let (non_blocking, guard) = tracing_appender::non_blocking(log_file); ( - layer.with_writer(non_blocking).with_filter(filter).boxed(), + layer.with_writer(non_blocking).boxed(), Some(LogFileGuard { log_path, guard }), ) } else {