Skip to content

Commit 6bb1293

Browse files
feat: add --log-file option for persistent log output
- Add --log-file CLI option to write tracing output to a file (topgrade-rs#779) - File logger appends to existing file, includes targets, no ANSI codes - Creates parent directories automatically if needed Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent e80d5a9 commit 6bb1293

3 files changed

Lines changed: 30 additions & 3 deletions

File tree

src/config.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1078,6 +1078,10 @@ pub struct CommandLineArgs {
10781078
/// List available steps and exit
10791079
#[arg(long = "list-steps")]
10801080
list_steps: bool,
1081+
1082+
/// Write log output to a file (in addition to stderr)
1083+
#[arg(long = "log-file", value_name = "PATH")]
1084+
log_file: Option<PathBuf>,
10811085
}
10821086

10831087
fn env_args_parser(arg: &str) -> Result<(String, String)> {
@@ -1120,6 +1124,11 @@ impl CommandLineArgs {
11201124

11211125
ret
11221126
}
1127+
1128+
/// Get the log file path (for early tracing setup before Config is loaded).
1129+
pub fn log_file_path(&self) -> Option<&Path> {
1130+
self.log_file.as_deref()
1131+
}
11231132
}
11241133

11251134
/// Represents the application configuration
@@ -1997,6 +2006,11 @@ impl Config {
19972006
self.opt.list_steps
19982007
}
19992008

2009+
/// Get the log file path if specified via CLI.
2010+
pub fn log_file(&self) -> Option<&Path> {
2011+
self.opt.log_file.as_deref()
2012+
}
2013+
20002014
/// Get the GitHub token for self-update, checking config file and environment variables.
20012015
pub fn github_token(&self) -> Option<String> {
20022016
self.config_file

src/main.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ fn run() -> Result<()> {
8080
//
8181
// For more info, see the comments in `CommandLineArgs::tracing_filter_directives()`
8282
// and `Config::tracing_filter_directives()`.
83-
let reload_handle = install_tracing(&opt.tracing_filter_directives())?;
83+
let reload_handle = install_tracing(&opt.tracing_filter_directives(), opt.log_file_path())?;
8484

8585
// Get current system locale and set it as the default locale
8686
let system_locale = sys_locale::get_locale().unwrap_or("en".to_string());

src/utils.rs

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -286,7 +286,7 @@ pub fn check_is_python_2_or_shim(ctx: &ExecutionContext, python: PathBuf) -> Res
286286
/// # Return value
287287
/// A reload handle will be returned so that we can change the log level at
288288
/// runtime.
289-
pub fn install_tracing(filter_directives: &str) -> Result<Handle<EnvFilter, Registry>> {
289+
pub fn install_tracing(filter_directives: &str, log_file: Option<&Path>) -> Result<Handle<EnvFilter, Registry>> {
290290
let env_filter = EnvFilter::try_new(filter_directives)
291291
.or_else(|_| EnvFilter::try_from_default_env())
292292
.or_else(|_| EnvFilter::try_new(DEFAULT_LOG_LEVEL))?;
@@ -295,7 +295,20 @@ pub fn install_tracing(filter_directives: &str) -> Result<Handle<EnvFilter, Regi
295295

296296
let (filter, reload_handle) = Layer::new(env_filter);
297297

298-
registry().with(filter).with(fmt_layer).init();
298+
if let Some(log_path) = log_file {
299+
// Create parent directories if needed
300+
if let Some(parent) = log_path.parent() {
301+
std::fs::create_dir_all(parent)?;
302+
}
303+
let file = std::fs::OpenOptions::new().create(true).append(true).open(log_path)?;
304+
let file_layer = fmt::layer()
305+
.with_target(true)
306+
.with_ansi(false)
307+
.with_writer(std::sync::Mutex::new(file));
308+
registry().with(filter).with(fmt_layer).with(file_layer).init();
309+
} else {
310+
registry().with(filter).with(fmt_layer).init();
311+
}
299312

300313
Ok(reload_handle)
301314
}

0 commit comments

Comments
 (0)