Skip to content

Commit

Permalink
#42: Add --log-as and --log-cmd-as options
Browse files Browse the repository at this point in the history
  • Loading branch information
mtkennerly committed Dec 3, 2023
1 parent 4f7ef76 commit 40b9eb5
Show file tree
Hide file tree
Showing 5 changed files with 148 additions and 18 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

* Added: `--log-rotate` option to control how often the log file rotates.
* Added: `--log-retain` option to control how many old log files are retained.
* Added: `--log-as` option to change the base name of the main log file.
* Added: `--log-cmd-as` option to log the wrapped command's stdout/stderr in a separate file.

## v1.3.0 (2023-10-01)

Expand Down
48 changes: 48 additions & 0 deletions src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,20 @@ pub struct CommonOpts {
#[clap(long, value_name = "path", parse(try_from_str = parse_ensured_directory))]
pub log_dir: Option<String>,

/// Use a different name for the main log file.
/// Set this to just the desired base name of the log file.
/// For example, `--log-as shawl` would result in a log file named `shawl_rCURRENT.log`
/// instead of the normal `shawl_for_<name>_rCURRENT.log` pattern.
#[clap(long)]
pub log_as: Option<String>,

/// Use a separate log file for the wrapped command's stdout and stderr.
/// Set this to just the desired base name of the log file.
/// For example, `--log-cmd-as foo` would result in a log file named `foo_rCURRENT.log`.
/// The output will be logged as-is without any additional log template.
#[clap(long)]
pub log_cmd_as: Option<String>,

/// Threshold for rotating log files. Valid options:
/// `daily`, `hourly`, `bytes=n` (every N bytes)
/// [default: bytes=2097152]
Expand Down Expand Up @@ -772,6 +786,40 @@ speculate::speculate! {
);
}

it "accepts --log-as" {
check_args(
&["shawl", "run", "--log-as", "foo", "--", "foo"],
Cli {
sub: Subcommand::Run {
name: s("Shawl"),
cwd: None,
common: CommonOpts {
log_as: Some("foo".to_string()),
command: vec![s("foo")],
..Default::default()
}
}
},
);
}

it "accepts --log-cmd-as" {
check_args(
&["shawl", "run", "--log-cmd-as", "foo", "--", "foo"],
Cli {
sub: Subcommand::Run {
name: s("Shawl"),
cwd: None,
common: CommonOpts {
log_cmd_as: Some("foo".to_string()),
command: vec![s("foo")],
..Default::default()
}
}
},
);
}

it "accepts --log-rotate bytes=n" {
check_args(
&["shawl", "run", "--log-rotate", "bytes=123", "--", "foo"],
Expand Down
36 changes: 36 additions & 0 deletions src/control.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,14 @@ fn construct_shawl_run_args(name: &str, cwd: &Option<String>, opts: &CommonOpts)
shawl_args.push("--log-dir".to_string());
shawl_args.push(quote(log_dir));
}
if let Some(log_as) = &opts.log_as {
shawl_args.push("--log-as".to_string());
shawl_args.push(quote(log_as));
}
if let Some(log_cmd_as) = &opts.log_cmd_as {
shawl_args.push("--log-cmd-as".to_string());
shawl_args.push(quote(log_cmd_as));
}
if let Some(log_rotate) = &opts.log_rotate {
shawl_args.push("--log-rotate".to_string());
shawl_args.push(log_rotate.to_cli());
Expand Down Expand Up @@ -366,6 +374,34 @@ speculate::speculate! {
);
}

it "handles --log-as" {
assert_eq!(
construct_shawl_run_args(
&s("shawl"),
&None,
&CommonOpts {
log_as: Some("foo".to_string()),
..Default::default()
}
),
vec!["run", "--name", "shawl", "--log-as", "foo"],
);
}

it "handles --log-cmd-as" {
assert_eq!(
construct_shawl_run_args(
&s("shawl"),
&None,
&CommonOpts {
log_cmd_as: Some("foo".to_string()),
..Default::default()
}
),
vec!["run", "--name", "shawl", "--log-cmd-as", "foo"],
);
}

it "handles --log-dir without spaces" {
assert_eq!(
construct_shawl_run_args(
Expand Down
63 changes: 47 additions & 16 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,29 +12,37 @@ fn prepare_logging(
console: bool,
rotation: cli::LogRotation,
retention: usize,
log_as: Option<&String>,
log_cmd_as: Option<&String>,
) -> Result<(), Box<dyn std::error::Error>> {
let mut exe_dir = std::env::current_exe()?;
exe_dir.pop();

let log_dir = match log_dir {
Some(log_dir) => log_dir.to_string(),
None => exe_dir.to_string_lossy().to_string(),
}
.replace("\\\\?\\", "");

let rotation = match rotation {
cli::LogRotation::Bytes(bytes) => flexi_logger::Criterion::Size(bytes),
cli::LogRotation::Daily => flexi_logger::Criterion::Age(flexi_logger::Age::Day),
cli::LogRotation::Hourly => flexi_logger::Criterion::Age(flexi_logger::Age::Hour),
};

let mut logger = flexi_logger::Logger::try_with_env_or_str("debug")?
.log_to_file(
flexi_logger::FileSpec::default()
.directory(
match log_dir {
Some(log_dir) => log_dir.to_string(),
None => exe_dir.to_string_lossy().to_string(),
}
.replace("\\\\?\\", ""),
)
.discriminant(format!("for_{}", name)),
)
.log_to_file({
let spec = flexi_logger::FileSpec::default().directory(log_dir.clone());

if let Some(log_as) = log_as {
spec.basename(log_as)
} else {
spec.discriminant(format!("for_{}", name))
}
})
.append()
.rotate(
match rotation {
cli::LogRotation::Bytes(bytes) => flexi_logger::Criterion::Size(bytes),
cli::LogRotation::Daily => flexi_logger::Criterion::Age(flexi_logger::Age::Day),
cli::LogRotation::Hourly => flexi_logger::Criterion::Age(flexi_logger::Age::Hour),
},
rotation,
flexi_logger::Naming::Timestamps,
flexi_logger::Cleanup::KeepLogFiles(retention),
)
Expand All @@ -53,6 +61,27 @@ fn prepare_logging(
logger = logger.duplicate_to_stderr(flexi_logger::Duplicate::Info);
}

if let Some(log_cmd_as) = log_cmd_as {
logger = logger.add_writer(
"shawl-cmd",
Box::new(
flexi_logger::writers::FileLogWriter::builder(
flexi_logger::FileSpec::default()
.directory(log_dir)
.basename(log_cmd_as),
)
.append()
.rotate(
rotation,
flexi_logger::Naming::Timestamps,
flexi_logger::Cleanup::KeepLogFiles(retention),
)
.format(|w, _now, record| write!(w, "{}", &record.args()))
.try_build()?,
),
);
}

logger.start()?;
Ok(())
}
Expand All @@ -78,6 +107,8 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
console,
common.log_rotate.unwrap_or_default(),
common.log_retain.unwrap_or(2),
common.log_as.as_ref(),
common.log_cmd_as.as_ref(),
)?;
}

Expand Down
17 changes: 15 additions & 2 deletions src/service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,7 @@ pub fn run_service(start_arguments: Vec<std::ffi::OsString>) -> windows_service:
};

// Log stdout.
let output_logs_need_target = opts.log_cmd_as.is_some();
let stdout_option = child.stdout.take();
let stdout_logger = std::thread::spawn(move || {
if !should_log_cmd {
Expand All @@ -218,7 +219,13 @@ pub fn run_service(start_arguments: Vec<std::ffi::OsString>) -> windows_service:
std::io::BufReader::new(stdout)
.lines()
.for_each(|line| match line {
Ok(ref x) if !x.is_empty() => debug!("stdout: {:?}", x),
Ok(ref x) if !x.is_empty() => {
if output_logs_need_target {
debug!(target: "{shawl-cmd}", "{}", x);
} else {
debug!("stdout: {:?}", x);
}
}
_ => (),
});
}
Expand All @@ -234,7 +241,13 @@ pub fn run_service(start_arguments: Vec<std::ffi::OsString>) -> windows_service:
std::io::BufReader::new(stderr)
.lines()
.for_each(|line| match line {
Ok(ref x) if !x.is_empty() => debug!("stderr: {:?}", x),
Ok(ref x) if !x.is_empty() => {
if output_logs_need_target {
debug!(target: "{shawl-cmd}", "{}", x);
} else {
debug!("stderr: {:?}", x);
}
}
_ => (),
});
}
Expand Down

0 comments on commit 40b9eb5

Please sign in to comment.