Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions crates/runtime-factors/src/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ impl RuntimeFactorsBuilder for FactorsBuilder {
executor.add_hooks(StdioLoggingExecutorHooks::new(
config.follow_components.clone(),
runtime_config.log_dir(),
config.truncate_logs,
));
executor.add_hooks(SqlStatementExecutorHook::new(
args.sqlite_statements.clone(),
Expand Down
11 changes: 11 additions & 0 deletions crates/trigger/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ pub use stdio::StdioLoggingExecutorHooks;
pub use summary::{KeyValueDefaultStoreSummaryHook, SqliteDefaultStoreSummaryHook};

pub const APP_LOG_DIR: &str = "APP_LOG_DIR";
pub const SPIN_TRUNCATE_LOGS: &str = "SPIN_TRUNCATE_LOGS";
pub const DISABLE_WASMTIME_CACHE: &str = "DISABLE_WASMTIME_CACHE";
pub const FOLLOW_LOG_OPT: &str = "FOLLOW_ID";
pub const WASMTIME_CACHE_FILE: &str = "WASMTIME_CACHE_FILE";
Expand Down Expand Up @@ -54,6 +55,13 @@ pub struct FactorsTriggerCommand<T: Trigger<B::Factors>, B: RuntimeFactorsBuilde
)]
pub log: Option<PathBuf>,

/// If set, Spin truncates the log files before starting the application.
#[clap(
name = SPIN_TRUNCATE_LOGS,
long = "truncate-logs",
)]
pub truncate_logs: bool,

/// Disable Wasmtime cache.
#[clap(
name = DISABLE_WASMTIME_CACHE,
Expand Down Expand Up @@ -139,6 +147,8 @@ pub struct FactorsConfig {
pub follow_components: FollowComponents,
/// Log directory for component stdout/stderr.
pub log_dir: UserProvidedPath,
/// If set, Spin truncates the log files before starting the application.
pub truncate_logs: bool,
}

/// An empty implementation of clap::Args to be used as TriggerExecutor::RunConfig
Expand Down Expand Up @@ -220,6 +230,7 @@ impl<T: Trigger<B::Factors>, B: RuntimeFactorsBuilder> FactorsTriggerCommand<T,
local_app_dir: local_app_dir.clone(),
follow_components,
log_dir,
truncate_logs: self.truncate_logs,
};

let run_fut = builder
Expand Down
37 changes: 34 additions & 3 deletions crates/trigger/src/cli/stdio.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ use spin_factors::RuntimeFactors;
use spin_factors_executor::ExecutorHooks;
use tokio::io::AsyncWrite;

pub const STDOUT_LOG_FILE_SUFFIX: &str = "stdout";
pub const STDERR_LOG_FILE_SUFFIX: &str = "stderr";

/// Which components should have their logs followed on stdout/stderr.
#[derive(Clone, Debug, Default)]
pub enum FollowComponents {
Expand Down Expand Up @@ -39,13 +42,19 @@ impl FollowComponents {
pub struct StdioLoggingExecutorHooks {
follow_components: FollowComponents,
log_dir: Option<PathBuf>,
truncate_log: bool,
}

impl StdioLoggingExecutorHooks {
pub fn new(follow_components: FollowComponents, log_dir: Option<PathBuf>) -> Self {
pub fn new(
follow_components: FollowComponents,
log_dir: Option<PathBuf>,
truncate_log: bool,
) -> Self {
Self {
follow_components,
log_dir,
truncate_log,
}
}

Expand Down Expand Up @@ -86,6 +95,23 @@ impl StdioLoggingExecutorHooks {
_ => Ok(()),
}
}

fn truncate_log_files(log_dir: &Path) {
if let Ok(entries) = log_dir.read_dir() {
for entry in entries.flatten() {
let path = entry.path();
let Some(name) = path.file_name().and_then(|n| n.to_str()) else {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this need the file_name()? It seems like the ends_with tests would work just as well on the full path.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it does. Apparently, ends_with under path() only checks path segment equality as compared to file_path() which checks str match

continue;
};

if name.ends_with(&format!("{STDOUT_LOG_FILE_SUFFIX}.txt"))
|| name.ends_with(&format!("{STDERR_LOG_FILE_SUFFIX}.txt"))
{
_ = std::fs::File::create(path)
}
}
}
}
}

#[async_trait]
Expand All @@ -95,11 +121,16 @@ impl<F: RuntimeFactors, U> ExecutorHooks<F, U> for StdioLoggingExecutorHooks {
configured_app: &spin_factors::ConfiguredApp<F>,
) -> anyhow::Result<()> {
self.validate_follows(configured_app.app())?;

if let Some(dir) = &self.log_dir {
// Ensure log dir exists if set
std::fs::create_dir_all(dir)
.with_context(|| format!("Failed to create log dir {}", quoted_path(dir)))?;

if self.truncate_log {
Self::truncate_log_files(dir);
}

println!("Logging component stdio to {}", quoted_path(dir.join("")))
}
Ok(())
Expand All @@ -115,12 +146,12 @@ impl<F: RuntimeFactors, U> ExecutorHooks<F, U> for StdioLoggingExecutorHooks {
};
wasi_builder.stdout_pipe(self.component_stdio_writer(
&component_id,
"stdout",
STDOUT_LOG_FILE_SUFFIX,
self.log_dir.as_deref(),
)?);
wasi_builder.stderr_pipe(self.component_stdio_writer(
&component_id,
"stderr",
STDERR_LOG_FILE_SUFFIX,
self.log_dir.as_deref(),
)?);
Ok(())
Expand Down