diff --git a/app/buck2_client/src/commands/log/options.rs b/app/buck2_client/src/commands/log/options.rs index fbf37459a32f5..103692d55fed6 100644 --- a/app/buck2_client/src/commands/log/options.rs +++ b/app/buck2_client/src/commands/log/options.rs @@ -11,6 +11,7 @@ use std::process::Stdio; use buck2_client_ctx::client_ctx::ClientCommandContext; use buck2_client_ctx::path_arg::PathArg; +use buck2_common::init::LogDownloadMethod; use buck2_common::temp_path::TempPath; use buck2_core::fs::fs_util; use buck2_core::fs::paths::abs_path::AbsPathBuf; @@ -30,8 +31,8 @@ use rand::Rng; #[derive(Debug, buck2_error::Error)] #[buck2(tag = Tier0)] enum EventLogOptionsError { - #[error("Manifold failed; stderr:\n{}", indent(" ", _0))] - ManifoldFailed(String), + #[error("{0} failed; stderr:\n{}", indent(" ", _1))] + DownloadFailed(String, String), #[error("Log not found locally by trace id `{0}`")] LogNotFoundLocally(TraceId), } @@ -97,7 +98,7 @@ impl EventLogOptions { trace_id: &TraceId, ctx: &ClientCommandContext<'_>, ) -> buck2_error::Result { - let manifold_file_name = FileNameBuf::try_from(format!( + let log_file_name = FileNameBuf::try_from(format!( "{}{}", trace_id, // TODO(nga): hardcoded default, should at least use the same default buck2 uses, @@ -108,7 +109,7 @@ impl EventLogOptions { let log_path = ctx .paths()? .log_dir() - .join(FileName::new(&format!("dl-{}", manifold_file_name))?); + .join(FileName::new(&format!("dl-{}", log_file_name))?); if fs_util::try_exists(&log_path)? { return Ok(log_path.into_abs_path_buf()); @@ -118,34 +119,69 @@ impl EventLogOptions { fs_util::create_dir_all(&tmp_dir)?; let temp_path = tmp_dir.join(FileName::new(&format!( "dl.{}.{}.tmp", - manifold_file_name, + log_file_name, Self::random_string() ))?); // Delete the file on failure. let temp_path = TempPath::new_path(temp_path); - - let args = [ - "get", - &format!("buck2_logs/flat/{}", manifold_file_name), - temp_path - .path() - .as_os_str() - .to_str() - .buck_error_context("temp_path is not valid UTF-8")?, - ]; - buck2_client_ctx::eprintln!("Spawning: manifold {}", args.join(" "))?; - let command = async_background_command("manifold") - .args(args) - .stdin(Stdio::null()) - .stdout(Stdio::null()) - .stderr(Stdio::piped()) - .spawn()?; + let (command_name, command) = match ctx.log_download_method() { + LogDownloadMethod::Manifold => { + let args = [ + "get", + &format!("buck2_logs/flat/{}", log_file_name), + temp_path + .path() + .as_os_str() + .to_str() + .buck_error_context("temp_path is not valid UTF-8")?, + ]; + buck2_client_ctx::eprintln!("Spawning: manifold {}", args.join(" "))?; + ( + "Manifold", + async_background_command("manifold") + .args(args) + .stdin(Stdio::null()) + .stdout(Stdio::null()) + .stderr(Stdio::piped()) + .spawn()?, + ) + } + LogDownloadMethod::Curl(log_url) => { + let log_url = log_url.trim_end_matches('/'); + + let args = [ + "--fail", + "-L", + &format!("{}/v1/logs/get/{}", log_url, trace_id), + "-o", + temp_path + .path() + .as_os_str() + .to_str() + .buck_error_context("temp_path is not valid UTF-8")?, + ]; + buck2_client_ctx::eprintln!("Spawning: curl {}", args.join(" "))?; + ( + "Curl", + async_background_command("curl") + .args(&args) + .stdin(Stdio::null()) + .stdout(Stdio::null()) + .stderr(Stdio::piped()) + .spawn()?, + ) + } + LogDownloadMethod::None => { + return Err(EventLogOptionsError::LogNotFoundLocally(trace_id.dupe()).into()); + } + }; // No timeout here, just press Ctrl-C if you want it to cancel. let result = command.wait_with_output().await?; if !result.status.success() { - return Err(EventLogOptionsError::ManifoldFailed( + return Err(EventLogOptionsError::DownloadFailed( + command_name.to_owned(), String::from_utf8_lossy(&result.stderr).into_owned(), ) .into()); diff --git a/app/buck2_client_ctx/src/client_ctx.rs b/app/buck2_client_ctx/src/client_ctx.rs index 3f52bbab710a4..663f21bea0df5 100644 --- a/app/buck2_client_ctx/src/client_ctx.rs +++ b/app/buck2_client_ctx/src/client_ctx.rs @@ -14,6 +14,7 @@ use buck2_cli_proto::client_context::HostPlatformOverride as GrpcHostPlatformOve use buck2_cli_proto::client_context::PreemptibleWhen as GrpcPreemptibleWhen; use buck2_cli_proto::ClientContext; use buck2_common::argv::Argv; +use buck2_common::init::LogDownloadMethod; use buck2_common::invocation_paths::InvocationPaths; use buck2_common::invocation_paths_result::InvocationPathsResult; use buck2_core::error::buck2_hard_error_env; @@ -305,4 +306,12 @@ impl<'a> ClientCommandContext<'a> { pub fn async_cleanup_context(&self) -> &AsyncCleanupContext<'a> { &self.async_cleanup } + + pub fn log_download_method(&self) -> LogDownloadMethod { + self.immediate_config + .daemon_startup_config() + .unwrap() + .log_download_method + .clone() + } } diff --git a/app/buck2_common/src/init.rs b/app/buck2_common/src/init.rs index 6d25cc8361661..6e79f91da4316 100644 --- a/app/buck2_common/src/init.rs +++ b/app/buck2_common/src/init.rs @@ -294,6 +294,13 @@ impl ResourceControlConfig { } } +#[derive(Allocative, Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] +pub enum LogDownloadMethod { + Manifold, + Curl(String), + None, +} + /// Configurations that are used at startup by the daemon. Those are actually read by the client, /// and passed on to the daemon. /// @@ -313,11 +320,46 @@ pub struct DaemonStartupConfig { pub materializations: Option, pub http: HttpConfig, pub resource_control: ResourceControlConfig, + pub log_download_method: LogDownloadMethod, } impl DaemonStartupConfig { pub fn new(config: &LegacyBuckConfig) -> buck2_error::Result { // Intepreted client side because we need the value here. + + let log_download_method = { + // Determine the log download method to use. Only default to + // manifold in fbcode contexts, or when specifically asked. + let use_manifold_default = cfg!(fbcode_build); + let use_manifold = config + .parse(BuckconfigKeyRef { + section: "buck2", + property: "log_use_manifold", + })? + .unwrap_or(use_manifold_default); + + if use_manifold { + Ok(LogDownloadMethod::Manifold) + } else { + let log_url = config.get(BuckconfigKeyRef { + section: "buck2", + property: "log_url", + }); + if let Some(log_url) = log_url { + if log_url.is_empty() { + Err(buck2_error::buck2_error!( + buck2_error::ErrorTag::Input, + "log_url is empty, but log_use_manifold is false" + )) + } else { + Ok(LogDownloadMethod::Curl(log_url.to_owned())) + } + } else { + Ok(LogDownloadMethod::None) + } + } + }?; + Ok(Self { daemon_buster: config .get(BuckconfigKeyRef { @@ -346,6 +388,7 @@ impl DaemonStartupConfig { .map(ToOwned::to_owned), http: HttpConfig::from_config(config)?, resource_control: ResourceControlConfig::from_config(config)?, + log_download_method, }) } @@ -366,6 +409,11 @@ impl DaemonStartupConfig { materializations: None, http: HttpConfig::default(), resource_control: ResourceControlConfig::default(), + log_download_method: if cfg!(fbcode_build) { + LogDownloadMethod::Manifold + } else { + LogDownloadMethod::None + }, } } }